You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tsfile.apache.org by co...@apache.org on 2026/03/27 08:47:05 UTC
(tsfile) branch read_opt created (now 5ceeebad4)
This is an automated email from the ASF dual-hosted git repository.
colinlee pushed a change to branch read_opt
in repository https://gitbox.apache.org/repos/asf/tsfile.git
at 5ceeebad4 Read Opt.
This branch includes the following new commits:
new 5ceeebad4 Read Opt.
The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
(tsfile) 01/01: Read Opt.
Posted by co...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
colinlee pushed a commit to branch read_opt
in repository https://gitbox.apache.org/repos/asf/tsfile.git
commit 5ceeebad47be247bbdc43a3d286d26476db204bf
Author: ColinLee <sh...@163.com>
AuthorDate: Fri Mar 27 16:46:16 2026 +0800
Read Opt.
---
cpp/.gitignore | 1 +
cpp/CMakeLists.txt | 9 +-
cpp/docs/design/mem-constrained-writer/design.md | 398 +
cpp/examples/CMakeLists.txt | 30 +-
cpp/examples/cpp_examples/CMakeLists.txt | 16 +-
cpp/examples/cpp_examples/bench_read.cpp | 641 +
.../{examples.cc => cpp_examples/bench_read.h} | 30 +-
cpp/examples/examples.cc | 28 +-
.../CMakeLists.txt | 7 +-
cpp/experiment/design.md | 649 +
cpp/experiment/read_perf_optimization.md | 342 +
...05\245\346\234\272\345\210\266-202603250503.md" | 343 +
cpp/src/CMakeLists.txt | 10 +
cpp/src/common/thread_pool.h | 102 +
cpp/src/common/tsblock/tsblock.h | 24 +
cpp/src/common/tsblock/vector/vector.h | 3 +
cpp/src/common/tsfile_common.h | 45 +
cpp/src/encoding/decoder.h | 119 +
cpp/src/encoding/gorilla_decoder.h | 421 +-
cpp/src/encoding/plain_decoder.h | 122 +
cpp/src/encoding/ts2diff_decoder.h | 590 +-
cpp/src/file/tsfile_io_reader.cc | 124 +
cpp/src/file/tsfile_io_reader.h | 5 +
cpp/src/reader/aligned_chunk_reader.cc | 1189 +-
cpp/src/reader/aligned_chunk_reader.h | 115 +-
.../reader/block/single_device_tsblock_reader.cc | 380 +-
.../reader/block/single_device_tsblock_reader.h | 30 +-
cpp/src/reader/chunk_reader.cc | 336 +-
cpp/src/reader/chunk_reader.h | 22 +-
cpp/src/reader/filter/and_filter.h | 12 +
cpp/src/reader/filter/filter.h | 14 +
cpp/src/reader/filter/or_filter.h | 12 +
cpp/src/reader/filter/time_operator.cc | 274 +
cpp/src/reader/filter/time_operator.h | 18 +
cpp/src/reader/result_set.h | 2 +-
cpp/src/reader/tsfile_series_scan_iterator.cc | 146 +-
cpp/src/reader/tsfile_series_scan_iterator.h | 31 +-
cpp/src/simd_ts_2diff.h | 408 +
cpp/src/ts_2diff.h | 267 +
cpp/src/utils.h | 55 +
cpp/test/encoding/gorilla_codec_test.cc | 188 +
.../simde-0.8.4-rc3/.all-contributorsrc | 789 +
cpp/third_party/simde-0.8.4-rc3/.appveyor.yml | 121 +
.../simde-0.8.4-rc3/.azure-pipelines.yml | 84 +
.../simde-0.8.4-rc3/.circleci/config.yml | 263 +
cpp/third_party/simde-0.8.4-rc3/.cirrus.yml | 24 +
cpp/third_party/simde-0.8.4-rc3/.dockerignore | 4 +
.../simde-0.8.4-rc3/.drone.star.disabled | 119 +
cpp/third_party/simde-0.8.4-rc3/.editorconfig | 18 +
.../.github/actionlint-matcher.json | 17 +
.../simde-0.8.4-rc3/.github/dependabot.yml | 11 +
.../.github/workflows/actionlint.yml | 14 +
.../simde-0.8.4-rc3/.github/workflows/ci.yml | 1057 +
.../simde-0.8.4-rc3/.github/workflows/cmake.yml | 39 +
.../.github/workflows/codeql-analysis.yml | 43 +
.../simde-0.8.4-rc3/.github/workflows/deploy.yml | 49 +
.../.github/workflows/gcc-snapshot.yml | 338 +
.../.github/workflows/ossar-analysis.yml | 45 +
.../.github/workflows/pr-cleanup.yml | 30 +
cpp/third_party/simde-0.8.4-rc3/.gitignore | 4 +
cpp/third_party/simde-0.8.4-rc3/.gitmodules | 3 +
cpp/third_party/simde-0.8.4-rc3/.mailmap | 11 +
cpp/third_party/simde-0.8.4-rc3/.no-test/README.md | 10 +
cpp/third_party/simde-0.8.4-rc3/.packit.yml | 30 +
cpp/third_party/simde-0.8.4-rc3/.packit/README.md | 45 +
cpp/third_party/simde-0.8.4-rc3/.packit/ci.sh | 215 +
cpp/third_party/simde-0.8.4-rc3/.packit/simde.spec | 81 +
.../simde-0.8.4-rc3/.semaphore/semaphore.yml | 55 +
cpp/third_party/simde-0.8.4-rc3/.travis.yml | 50 +
cpp/third_party/simde-0.8.4-rc3/CMakeLists.txt | 78 +
cpp/third_party/simde-0.8.4-rc3/CONTRIBUTING.md | 109 +
cpp/third_party/simde-0.8.4-rc3/COPYING | 20 +
cpp/third_party/simde-0.8.4-rc3/README.md | 521 +
cpp/third_party/simde-0.8.4-rc3/amalgamate.py | 59 +
cpp/third_party/simde-0.8.4-rc3/codecov.yml | 3 +
cpp/third_party/simde-0.8.4-rc3/docker/Dockerfile | 120 +
.../simde-0.8.4-rc3/docker/Dockerfile.ubuntu | 110 +
.../docker/Dockerfile.ubuntu_bionic | 110 +
cpp/third_party/simde-0.8.4-rc3/docker/README.md | 50 +
.../simde-0.8.4-rc3/docker/bin/arch2gcc.sh | 65 +
.../simde-0.8.4-rc3/docker/bin/simde-check-all.sh | 12 +
.../docker/bin/simde-reset-build.sh | 30 +
.../docker/cross-files/aarch64+sve-clang-10.cross | 20 +
.../docker/cross-files/aarch64+sve-clang-11.cross | 20 +
.../docker/cross-files/aarch64+sve-clang-12.cross | 20 +
.../docker/cross-files/aarch64+sve-clang-13.cross | 20 +
.../docker/cross-files/aarch64+sve-clang-14.cross | 20 +
.../docker/cross-files/aarch64+sve-clang-15.cross | 20 +
.../docker/cross-files/aarch64+sve-gcc-10.cross | 18 +
.../docker/cross-files/aarch64+sve-gcc-11.cross | 18 +
.../docker/cross-files/aarch64+sve-gcc-12.cross | 18 +
.../docker/cross-files/aarch64-clang-10.cross | 20 +
.../docker/cross-files/aarch64-clang-11.cross | 20 +
.../docker/cross-files/aarch64-clang-12.cross | 20 +
.../cross-files/aarch64-clang-13-debflags.cross | 20 +
.../docker/cross-files/aarch64-clang-13.cross | 20 +
.../cross-files/aarch64-clang-14-debflags.cross | 20 +
.../docker/cross-files/aarch64-clang-14.cross | 20 +
.../cross-files/aarch64-clang-15-ccache.cross | 20 +
.../cross-files/aarch64-clang-15-debflags.cross | 20 +
.../docker/cross-files/aarch64-clang-15.cross | 20 +
.../cross-files/aarch64-clang-16-ccache.cross | 20 +
.../cross-files/aarch64-clang-17-ccache.cross | 20 +
.../cross-files/aarch64-clang-18-ccache.cross | 20 +
.../aarch64-clang-18-no-extras-ccache.cross | 20 +
.../cross-files/aarch64-clang-19-ccache.cross | 21 +
.../aarch64-clang-19-no-extras-ccache.cross | 21 +
.../cross-files/aarch64-clang-20-ccache.cross | 21 +
.../aarch64-clang-20-no-extras-ccache.cross | 21 +
.../cross-files/aarch64-clang-21-ccache.cross | 21 +
.../aarch64-clang-21-no-extras-ccache.cross | 21 +
.../cross-files/aarch64-clang-22-ccache.cross | 21 +
.../aarch64-clang-22-no-extras-ccache.cross | 21 +
.../cross-files/aarch64-clang-23-ccache.cross | 21 +
.../aarch64-clang-23-no-extras-ccache.cross | 21 +
.../docker/cross-files/aarch64-clang-6.cross | 20 +
.../docker/cross-files/aarch64-clang-7.cross | 20 +
.../docker/cross-files/aarch64-clang-8.cross | 20 +
.../docker/cross-files/aarch64-clang-9.cross | 20 +
.../docker/cross-files/aarch64-gcc-10.cross | 18 +
.../docker/cross-files/aarch64-gcc-11-ccache.cross | 18 +
.../cross-files/aarch64-gcc-11-debflags.cross | 20 +
.../docker/cross-files/aarch64-gcc-11.cross | 18 +
.../docker/cross-files/aarch64-gcc-12-ccache.cross | 18 +
.../cross-files/aarch64-gcc-12-debflags.cross | 20 +
.../docker/cross-files/aarch64-gcc-12.cross | 18 +
.../docker/cross-files/aarch64-gcc-13-ccache.cross | 18 +
.../docker/cross-files/aarch64-gcc-14-ccache.cross | 18 +
.../aarch64-gcc-14-no-extras-ccache.cross | 18 +
.../docker/cross-files/aarch64-gcc-15-ccache.cross | 18 +
.../aarch64-gcc-15-no-extras-ccache.cross | 18 +
.../docker/cross-files/aarch64-gcc-16-ccache.cross | 18 +
.../aarch64-gcc-16-no-extras-ccache.cross | 18 +
.../docker/cross-files/aarch64-gcc-7.cross | 18 +
.../docker/cross-files/aarch64-gcc-8.cross | 18 +
.../docker/cross-files/aarch64-gcc-9.cross | 18 +
.../docker/cross-files/armel-clang-15-ccache.cross | 20 +
.../docker/cross-files/armel-clang-16-ccache.cross | 20 +
.../docker/cross-files/armel-clang-17-ccache.cross | 20 +
.../docker/cross-files/armel-clang-18-ccache.cross | 20 +
.../docker/cross-files/armel-clang-19-ccache.cross | 21 +
.../docker/cross-files/armel-clang-20-ccache.cross | 21 +
.../docker/cross-files/armel-clang-21-ccache.cross | 21 +
.../docker/cross-files/armel-clang-22-ccache.cross | 21 +
.../docker/cross-files/armel-clang-23-ccache.cross | 21 +
.../docker/cross-files/armel-gcc-11-debflags.cross | 20 +
.../docker/cross-files/armel-gcc-12-ccache.cross | 18 +
.../docker/cross-files/armel-gcc-12-debflags.cross | 20 +
.../docker/cross-files/armel-gcc-13-ccache.cross | 18 +
.../docker/cross-files/armel-gcc-14-ccache.cross | 18 +
.../docker/cross-files/armel-gcc-15-ccache.cross | 18 +
.../docker/cross-files/armel-gcc-16-ccache.cross | 18 +
.../docker/cross-files/armhf-gcc-11-debflags.cross | 20 +
.../docker/cross-files/armhf-gcc-12-debflags.cross | 20 +
.../docker/cross-files/armv7-clang-11.cross | 20 +
.../docker/cross-files/armv7-clang-12.cross | 20 +
.../docker/cross-files/armv7-clang-13.cross | 20 +
.../docker/cross-files/armv7-clang-14.cross | 20 +
.../docker/cross-files/armv7-clang-15-ccache.cross | 20 +
.../docker/cross-files/armv7-clang-15.cross | 20 +
.../docker/cross-files/armv7-clang-16-ccache.cross | 20 +
.../docker/cross-files/armv7-clang-17-ccache.cross | 20 +
.../docker/cross-files/armv7-clang-18-ccache.cross | 20 +
.../docker/cross-files/armv7-clang-19-ccache.cross | 21 +
.../docker/cross-files/armv7-clang-20-ccache.cross | 21 +
.../docker/cross-files/armv7-clang-21-ccache.cross | 21 +
.../docker/cross-files/armv7-clang-22-ccache.cross | 21 +
.../docker/cross-files/armv7-clang-23-ccache.cross | 21 +
.../docker/cross-files/armv7-clang-6.cross | 20 +
.../docker/cross-files/armv7-gcc-10.cross | 18 +
.../docker/cross-files/armv7-gcc-11.cross | 18 +
.../docker/cross-files/armv7-gcc-12-ccache.cross | 18 +
.../docker/cross-files/armv7-gcc-12.cross | 18 +
.../docker/cross-files/armv7-gcc-13-ccache.cross | 18 +
.../docker/cross-files/armv7-gcc-14-ccache.cross | 18 +
.../docker/cross-files/armv7-gcc-15-ccache.cross | 18 +
.../docker/cross-files/armv7-gcc-16-ccache.cross | 18 +
.../docker/cross-files/armv7-gcc-8.cross | 18 +
.../docker/cross-files/armv8+mve-gcc-10.cross | 18 +
.../docker/cross-files/armv8+mve-gcc-11.cross | 18 +
.../docker/cross-files/armv8+mve-gcc-12.cross | 18 +
.../docker/cross-files/armv8-clang-10.cross | 20 +
.../docker/cross-files/armv8-clang-11.cross | 20 +
.../docker/cross-files/armv8-clang-12.cross | 20 +
.../docker/cross-files/armv8-clang-13.cross | 20 +
.../docker/cross-files/armv8-clang-14.cross | 20 +
.../docker/cross-files/armv8-clang-15.cross | 20 +
.../docker/cross-files/armv8-clang-6.cross | 20 +
.../docker/cross-files/armv8-clang-8.cross | 20 +
.../docker/cross-files/armv8-clang-9.cross | 20 +
.../docker/cross-files/armv8-gcc-10.cross | 18 +
.../docker/cross-files/armv8-gcc-11.cross | 18 +
.../cross-files/armv8-gcc-12-32bit-ccache.cross | 18 +
.../docker/cross-files/armv8-gcc-12.cross | 18 +
.../cross-files/armv8-gcc-13-32bit-ccache.cross | 18 +
.../cross-files/armv8-gcc-14-32bit-ccache.cross | 18 +
.../cross-files/armv8-gcc-15-32bit-ccache.cross | 18 +
.../cross-files/armv8-gcc-16-32bit-ccache.cross | 18 +
.../docker/cross-files/armv8-gcc-9.cross | 18 +
.../docker/cross-files/clang-10.cross | 11 +
.../docker/cross-files/clang-11-debflags.cross | 11 +
.../docker/cross-files/clang-11.cross | 11 +
.../docker/cross-files/clang-12.cross | 11 +
.../docker/cross-files/clang-13.cross | 11 +
.../docker/cross-files/clang-14.cross | 11 +
.../docker/cross-files/clang-15.cross | 11 +
.../docker/cross-files/clang-16.cross | 11 +
.../docker/cross-files/clang-17.cross | 11 +
.../docker/cross-files/clang-3.9.cross | 11 +
.../docker/cross-files/clang-4.cross | 11 +
.../docker/cross-files/clang-5.cross | 11 +
.../docker/cross-files/clang-6.cross | 11 +
.../docker/cross-files/clang-7.cross | 11 +
.../docker/cross-files/clang-8.cross | 11 +
.../docker/cross-files/clang-9.cross | 11 +
.../docker/cross-files/emscripten32-relaxed.cross | 22 +
.../docker/cross-files/emscripten32.cross | 22 +
.../docker/cross-files/emscripten64-relaxed.cross | 26 +
.../docker/cross-files/emscripten64.cross | 26 +
.../docker/cross-files/gcc-10-fast-math.cross | 11 +
.../docker/cross-files/gcc-10.cross | 11 +
.../docker/cross-files/gcc-11-debflags.cross | 13 +
.../docker/cross-files/gcc-11-fast-math.cross | 11 +
.../docker/cross-files/gcc-11.cross | 11 +
.../docker/cross-files/gcc-12-debflags.cross | 13 +
.../docker/cross-files/gcc-12-fast-math.cross | 11 +
.../docker/cross-files/gcc-12-native.cross | 11 +
.../docker/cross-files/gcc-12.cross | 11 +
.../simde-0.8.4-rc3/docker/cross-files/gcc-5.cross | 11 +
.../simde-0.8.4-rc3/docker/cross-files/gcc-6.cross | 11 +
.../simde-0.8.4-rc3/docker/cross-files/gcc-7.cross | 11 +
.../simde-0.8.4-rc3/docker/cross-files/gcc-8.cross | 11 +
.../simde-0.8.4-rc3/docker/cross-files/gcc-9.cross | 11 +
.../docker/cross-files/i686-all-gcc-10.cross | 18 +
.../docker/cross-files/i686-all-gcc-11.cross | 18 +
.../docker/cross-files/i686-all-gcc-12.cross | 18 +
.../docker/cross-files/i686-all-gcc-9.cross | 18 +
.../docker/cross-files/i686-clang-10.cross | 19 +
.../docker/cross-files/i686-clang-11.cross | 19 +
.../docker/cross-files/i686-clang-12.cross | 19 +
.../docker/cross-files/i686-clang-13.cross | 19 +
.../docker/cross-files/i686-clang-14.cross | 19 +
.../docker/cross-files/i686-clang-15.cross | 19 +
.../docker/cross-files/i686-gcc-10-debflags.cross | 19 +
.../docker/cross-files/i686-gcc-10.cross | 17 +
.../docker/cross-files/i686-gcc-11-debflags.cross | 19 +
.../cross-files/i686-gcc-11-qemu-ccache.cross | 21 +
.../docker/cross-files/i686-gcc-11-qemu.cross | 21 +
.../docker/cross-files/i686-gcc-11.cross | 20 +
.../docker/cross-files/i686-gcc-12-debflags.cross | 19 +
.../cross-files/i686-gcc-12-qemu-ccache.cross | 21 +
.../docker/cross-files/i686-gcc-12.cross | 17 +
.../docker/cross-files/i686-gcc-14.cross | 17 +
.../docker/cross-files/i686-gcc-9.cross | 17 +
.../docker/cross-files/i686-gcc-qemu-ccache.cross | 21 +
.../docker/cross-files/i686-gcc-qemu.cross | 21 +
.../simde-0.8.4-rc3/docker/cross-files/icc.cross | 13 +
.../simde-0.8.4-rc3/docker/cross-files/icx.cross | 11 +
.../docker/cross-files/intel-all-clang-10.cross | 21 +
.../docker/cross-files/intel-all-clang-11.cross | 18 +
.../docker/cross-files/intel-all-clang-12.cross | 18 +
.../docker/cross-files/intel-all-clang-13.cross | 18 +
.../docker/cross-files/intel-all-clang-14.cross | 18 +
.../docker/cross-files/intel-all-clang-15.cross | 18 +
.../docker/cross-files/intel-all-clang-7.cross | 18 +
.../docker/cross-files/intel-all-clang-8.cross | 18 +
.../docker/cross-files/intel-all-clang-9.cross | 21 +
.../docker/cross-files/intel-all-gcc-10.cross | 18 +
.../docker/cross-files/intel-all-gcc-11.cross | 18 +
.../docker/cross-files/intel-all-gcc-12.cross | 18 +
.../docker/cross-files/intel-all-gcc-8.cross | 18 +
.../docker/cross-files/intel-all-icc.cross | 20 +
.../docker/cross-files/intel-all-icx.cross | 18 +
.../cross-files/loongarch64-clang-18-ccache.cross | 20 +
.../cross-files/loongarch64-clang-19-ccache.cross | 21 +
.../cross-files/loongarch64-clang-20-ccache.cross | 21 +
.../cross-files/loongarch64-clang-21-ccache.cross | 21 +
.../cross-files/loongarch64-clang-22-ccache.cross | 21 +
.../cross-files/loongarch64-clang-23-ccache.cross | 21 +
.../cross-files/loongarch64-gcc-13-ccache.cross | 18 +
.../docker/cross-files/loongarch64-gcc-13.cross | 18 +
.../cross-files/loongarch64-gcc-14-ccache.cross | 20 +
.../cross-files/loongarch64-gcc-15-ccache.cross | 20 +
.../cross-files/loongarch64-gcc-16-ccache.cross | 20 +
.../docker/cross-files/loongson-gcc-10.cross | 18 +
.../docker/cross-files/loongson-gcc-11.cross | 18 +
.../docker/cross-files/loongson-gcc-12.cross | 18 +
.../docker/cross-files/loongson-gcc-ccache.cross | 18 +
.../docker/cross-files/loongson-gcc.cross | 18 +
.../docker/cross-files/mips64el+msa-gcc-10.cross | 18 +
.../docker/cross-files/mips64el+msa-gcc-11.cross | 18 +
.../docker/cross-files/mips64el+msa-gcc-12.cross | 18 +
.../docker/cross-files/mips64el-clang-11.cross | 20 +
.../docker/cross-files/mips64el-clang-12.cross | 20 +
.../docker/cross-files/mips64el-clang-13.cross | 20 +
.../docker/cross-files/mips64el-clang-14.cross | 20 +
.../docker/cross-files/mips64el-clang-15.cross | 20 +
.../cross-files/mips64el-clang-16-ccache.cross | 20 +
.../docker/cross-files/mips64el-clang-16.cross | 20 +
.../cross-files/mips64el-clang-17-ccache.cross | 20 +
.../cross-files/mips64el-clang-18-ccache.cross | 20 +
.../docker/cross-files/mips64el-gcc-10.cross | 18 +
.../docker/cross-files/mips64el-gcc-11.cross | 18 +
.../cross-files/mips64el-gcc-12-ccache.cross | 18 +
.../docker/cross-files/mips64el-gcc-12.cross | 18 +
.../cross-files/mips64el-gcc-13-ccache.cross | 18 +
.../cross-files/mips64el-gcc-14-ccache.cross | 18 +
.../cross-files/mips64el-gcc-15-ccache.cross | 18 +
.../cross-files/mips64el-gcc-16-ccache.cross | 18 +
.../docker/cross-files/power8-clang-9.cross | 20 +
.../docker/cross-files/power8-gcc-10.cross | 18 +
.../docker/cross-files/power9-clang-10.cross | 20 +
.../docker/cross-files/power9-clang-11.cross | 20 +
.../docker/cross-files/power9-clang-12.cross | 20 +
.../docker/cross-files/power9-clang-13.cross | 20 +
.../docker/cross-files/power9-clang-14.cross | 20 +
.../cross-files/power9-clang-15-ccache.cross | 20 +
.../docker/cross-files/power9-clang-15.cross | 20 +
.../docker/cross-files/power9-gcc-10.cross | 18 +
.../docker/cross-files/power9-gcc-11.cross | 18 +
.../docker/cross-files/power9-gcc-12-ccache.cross | 18 +
.../docker/cross-files/power9-gcc-12.cross | 18 +
.../docker/cross-files/power9-gcc-13-ccache.cross | 18 +
.../docker/cross-files/power9-gcc-14-ccache.cross | 18 +
.../docker/cross-files/power9-gcc-15-ccache.cross | 18 +
.../docker/cross-files/power9-gcc-16-ccache.cross | 18 +
.../docker/cross-files/ppc64el-clang-14.cross | 20 +
.../cross-files/ppc64el-clang-15-ccache.cross | 20 +
.../docker/cross-files/ppc64el-clang-15.cross | 20 +
.../cross-files/ppc64el-clang-16-ccache.cross | 20 +
.../cross-files/ppc64el-clang-17-ccache.cross | 20 +
.../cross-files/ppc64el-clang-18-ccache.cross | 20 +
.../cross-files/ppc64el-clang-19-ccache.cross | 21 +
.../cross-files/ppc64el-clang-20-ccache.cross | 21 +
.../cross-files/ppc64el-clang-21-ccache.cross | 21 +
.../cross-files/ppc64el-clang-22-ccache.cross | 21 +
.../cross-files/ppc64el-clang-23-ccache.cross | 21 +
.../cross-files/ppc64el-gcc-10-debflags.cross | 20 +
.../docker/cross-files/ppc64el-gcc-10.cross | 18 +
.../cross-files/ppc64el-gcc-11-debflags.cross | 20 +
.../docker/cross-files/ppc64el-gcc-11.cross | 18 +
.../cross-files/ppc64el-gcc-12-debflags.cross | 20 +
.../docker/cross-files/ppc64el-gcc-12.cross | 18 +
...iscv64+rvv_vlen128_elen64-clang-17-ccache.cross | 37 +
...iscv64+rvv_vlen128_elen64-clang-18-ccache.cross | 37 +
...4+rvv_vlen128_elen64_zvfh-clang-17-ccache.cross | 37 +
...4+rvv_vlen128_elen64_zvfh-clang-18-ccache.cross | 37 +
...iscv64+rvv_vlen256_elen64-clang-17-ccache.cross | 37 +
...iscv64+rvv_vlen256_elen64-clang-18-ccache.cross | 37 +
...4+rvv_vlen256_elen64_zvfh-clang-17-ccache.cross | 37 +
...4+rvv_vlen256_elen64_zvfh-clang-18-ccache.cross | 37 +
...iscv64+rvv_vlen512_elen64-clang-17-ccache.cross | 37 +
...iscv64+rvv_vlen512_elen64-clang-18-ccache.cross | 37 +
...4+rvv_vlen512_elen64_zvfh-clang-17-ccache.cross | 37 +
...4+rvv_vlen512_elen64_zvfh-clang-18-ccache.cross | 37 +
.../docker/cross-files/riscv64-clang-10.cross | 20 +
.../docker/cross-files/riscv64-clang-11.cross | 20 +
.../docker/cross-files/riscv64-clang-12.cross | 20 +
.../docker/cross-files/riscv64-clang-13.cross | 20 +
.../docker/cross-files/riscv64-clang-14.cross | 20 +
.../cross-files/riscv64-clang-15-ccache.cross | 20 +
.../docker/cross-files/riscv64-clang-15.cross | 20 +
.../cross-files/riscv64-clang-16-ccache.cross | 20 +
.../docker/cross-files/riscv64-clang-16.cross | 20 +
.../cross-files/riscv64-clang-17-ccache.cross | 20 +
.../docker/cross-files/riscv64-clang-17.cross | 20 +
.../cross-files/riscv64-clang-18-ccache.cross | 20 +
.../cross-files/riscv64-clang-19-ccache.cross | 21 +
.../cross-files/riscv64-clang-20-ccache.cross | 21 +
.../cross-files/riscv64-clang-21-ccache.cross | 21 +
.../cross-files/riscv64-clang-22-ccache.cross | 21 +
.../cross-files/riscv64-clang-23-ccache.cross | 21 +
.../docker/cross-files/riscv64-clang-9.cross | 20 +
.../docker/cross-files/riscv64-gcc-10.cross | 18 +
.../docker/cross-files/riscv64-gcc-11.cross | 18 +
.../cross-files/riscv64-gcc-12-O3-ccache.cross | 18 +
.../docker/cross-files/riscv64-gcc-12-ccache.cross | 18 +
.../docker/cross-files/riscv64-gcc-12.cross | 18 +
.../cross-files/riscv64-gcc-13-O3-ccache.cross | 18 +
.../docker/cross-files/riscv64-gcc-13-ccache.cross | 18 +
.../cross-files/riscv64-gcc-14-O3-ccache.cross | 18 +
.../docker/cross-files/riscv64-gcc-14-ccache.cross | 18 +
.../cross-files/riscv64-gcc-15-O3-ccache.cross | 18 +
.../docker/cross-files/riscv64-gcc-15-ccache.cross | 18 +
.../cross-files/riscv64-gcc-16-O3-ccache.cross | 18 +
.../docker/cross-files/riscv64-gcc-16-ccache.cross | 18 +
.../docker/cross-files/s390x-clang-11.cross | 20 +
.../docker/cross-files/s390x-clang-12.cross | 20 +
.../docker/cross-files/s390x-clang-13.cross | 20 +
.../docker/cross-files/s390x-clang-14.cross | 20 +
.../docker/cross-files/s390x-clang-15-ccache.cross | 20 +
.../docker/cross-files/s390x-clang-15.cross | 20 +
.../docker/cross-files/s390x-clang-16-ccache.cross | 20 +
.../docker/cross-files/s390x-clang-17-ccache.cross | 20 +
.../docker/cross-files/s390x-clang-18-ccache.cross | 20 +
.../docker/cross-files/s390x-clang-19-ccache.cross | 21 +
.../docker/cross-files/s390x-clang-20-ccache.cross | 21 +
.../docker/cross-files/s390x-clang-21-ccache.cross | 21 +
.../docker/cross-files/s390x-clang-22-ccache.cross | 21 +
.../docker/cross-files/s390x-clang-23-ccache.cross | 21 +
.../docker/cross-files/s390x-gcc-10.cross | 18 +
.../docker/cross-files/s390x-gcc-11-debflags.cross | 20 +
.../docker/cross-files/s390x-gcc-11.cross | 18 +
.../docker/cross-files/s390x-gcc-12-ccache.cross | 18 +
.../docker/cross-files/s390x-gcc-12-debflags.cross | 20 +
.../docker/cross-files/s390x-gcc-12.cross | 18 +
.../docker/cross-files/s390x-gcc-13-ccache.cross | 18 +
.../docker/cross-files/s390x-gcc-14-ccache.cross | 18 +
.../docker/cross-files/s390x-gcc-15-ccache.cross | 18 +
.../docker/cross-files/s390x-gcc-16-ccache.cross | 18 +
.../docker/cross-files/s390x-gcc-8.cross | 18 +
.../docker/cross-files/s390x-gcc-9.cross | 18 +
.../docker/cross-files/sleef-gcc-10.cross | 12 +
.../docker/cross-files/sleef-gcc-11.cross | 12 +
.../docker/cross-files/sleef-gcc-12.cross | 12 +
.../simde-0.8.4-rc3/docker/simde-dev.sh | 83 +
cpp/third_party/simde-0.8.4-rc3/meson.build | 566 +
cpp/third_party/simde-0.8.4-rc3/meson_options.txt | 9 +
.../simde-0.8.4-rc3/netlify-disable.toml | 38 +
cpp/third_party/simde-0.8.4-rc3/simde/arm/neon.h | 321 +
.../simde-0.8.4-rc3/simde/arm/neon/aba.h | 208 +
.../simde-0.8.4-rc3/simde/arm/neon/abal.h | 178 +
.../simde-0.8.4-rc3/simde/arm/neon/abal_high.h | 125 +
.../simde-0.8.4-rc3/simde/arm/neon/abd.h | 629 +
.../simde-0.8.4-rc3/simde/arm/neon/abdl.h | 147 +
.../simde-0.8.4-rc3/simde/arm/neon/abdl_high.h | 181 +
.../simde-0.8.4-rc3/simde/arm/neon/abs.h | 536 +
.../simde-0.8.4-rc3/simde/arm/neon/add.h | 975 +
.../simde-0.8.4-rc3/simde/arm/neon/addhn.h | 211 +
.../simde-0.8.4-rc3/simde/arm/neon/addhn_high.h | 124 +
.../simde-0.8.4-rc3/simde/arm/neon/addl.h | 170 +
.../simde-0.8.4-rc3/simde/arm/neon/addl_high.h | 182 +
.../simde-0.8.4-rc3/simde/arm/neon/addlv.h | 414 +
.../simde-0.8.4-rc3/simde/arm/neon/addv.h | 555 +
.../simde-0.8.4-rc3/simde/arm/neon/addw.h | 241 +
.../simde-0.8.4-rc3/simde/arm/neon/addw_high.h | 215 +
.../simde-0.8.4-rc3/simde/arm/neon/aes.h | 222 +
.../simde-0.8.4-rc3/simde/arm/neon/and.h | 584 +
.../simde-0.8.4-rc3/simde/arm/neon/bcax.h | 227 +
.../simde-0.8.4-rc3/simde/arm/neon/bic.h | 553 +
.../simde-0.8.4-rc3/simde/arm/neon/bsl.h | 914 +
.../simde-0.8.4-rc3/simde/arm/neon/cadd_rot270.h | 231 +
.../simde-0.8.4-rc3/simde/arm/neon/cadd_rot90.h | 231 +
.../simde-0.8.4-rc3/simde/arm/neon/cage.h | 192 +
.../simde-0.8.4-rc3/simde/arm/neon/cagt.h | 192 +
.../simde-0.8.4-rc3/simde/arm/neon/cale.h | 168 +
.../simde-0.8.4-rc3/simde/arm/neon/calt.h | 168 +
.../simde-0.8.4-rc3/simde/arm/neon/ceq.h | 872 +
.../simde-0.8.4-rc3/simde/arm/neon/ceqz.h | 481 +
.../simde-0.8.4-rc3/simde/arm/neon/cge.h | 909 +
.../simde-0.8.4-rc3/simde/arm/neon/cgez.h | 482 +
.../simde-0.8.4-rc3/simde/arm/neon/cgt.h | 905 +
.../simde-0.8.4-rc3/simde/arm/neon/cgtz.h | 484 +
.../simde-0.8.4-rc3/simde/arm/neon/cle.h | 935 +
.../simde-0.8.4-rc3/simde/arm/neon/clez.h | 545 +
.../simde-0.8.4-rc3/simde/arm/neon/cls.h | 148 +
.../simde-0.8.4-rc3/simde/arm/neon/clt.h | 913 +
.../simde-0.8.4-rc3/simde/arm/neon/cltz.h | 389 +
.../simde-0.8.4-rc3/simde/arm/neon/clz.h | 427 +
.../simde-0.8.4-rc3/simde/arm/neon/cmla.h | 223 +
.../simde-0.8.4-rc3/simde/arm/neon/cmla_lane.h | 386 +
.../simde-0.8.4-rc3/simde/arm/neon/cmla_rot180.h | 231 +
.../simde/arm/neon/cmla_rot180_lane.h | 422 +
.../simde-0.8.4-rc3/simde/arm/neon/cmla_rot270.h | 229 +
.../simde/arm/neon/cmla_rot270_lane.h | 421 +
.../simde-0.8.4-rc3/simde/arm/neon/cmla_rot90.h | 233 +
.../simde/arm/neon/cmla_rot90_lane.h | 420 +
.../simde-0.8.4-rc3/simde/arm/neon/cnt.h | 224 +
.../simde-0.8.4-rc3/simde/arm/neon/combine.h | 499 +
.../simde-0.8.4-rc3/simde/arm/neon/copy_lane.h | 1200 +
.../simde-0.8.4-rc3/simde/arm/neon/crc32.h | 295 +
.../simde-0.8.4-rc3/simde/arm/neon/create.h | 255 +
.../simde-0.8.4-rc3/simde/arm/neon/cvt.h | 2258 ++
.../simde-0.8.4-rc3/simde/arm/neon/cvt_n.h | 703 +
.../simde-0.8.4-rc3/simde/arm/neon/cvtm.h | 389 +
.../simde-0.8.4-rc3/simde/arm/neon/cvtn.h | 538 +
.../simde-0.8.4-rc3/simde/arm/neon/cvtp.h | 387 +
.../simde-0.8.4-rc3/simde/arm/neon/div.h | 202 +
.../simde-0.8.4-rc3/simde/arm/neon/dot.h | 300 +
.../simde-0.8.4-rc3/simde/arm/neon/dot_lane.h | 753 +
.../simde-0.8.4-rc3/simde/arm/neon/dup_lane.h | 1715 +
.../simde-0.8.4-rc3/simde/arm/neon/dup_n.h | 905 +
.../simde-0.8.4-rc3/simde/arm/neon/eor.h | 844 +
.../simde-0.8.4-rc3/simde/arm/neon/ext.h | 1203 +
.../simde-0.8.4-rc3/simde/arm/neon/fma.h | 217 +
.../simde-0.8.4-rc3/simde/arm/neon/fma_lane.h | 335 +
.../simde-0.8.4-rc3/simde/arm/neon/fma_n.h | 132 +
.../simde-0.8.4-rc3/simde/arm/neon/fmlal.h | 557 +
.../simde-0.8.4-rc3/simde/arm/neon/fmlsl.h | 397 +
.../simde-0.8.4-rc3/simde/arm/neon/fms.h | 195 +
.../simde-0.8.4-rc3/simde/arm/neon/fms_lane.h | 334 +
.../simde-0.8.4-rc3/simde/arm/neon/fms_n.h | 174 +
.../simde-0.8.4-rc3/simde/arm/neon/get_high.h | 435 +
.../simde-0.8.4-rc3/simde/arm/neon/get_lane.h | 727 +
.../simde-0.8.4-rc3/simde/arm/neon/get_low.h | 470 +
.../simde-0.8.4-rc3/simde/arm/neon/hadd.h | 370 +
.../simde-0.8.4-rc3/simde/arm/neon/hsub.h | 371 +
.../simde-0.8.4-rc3/simde/arm/neon/ld1.h | 713 +
.../simde-0.8.4-rc3/simde/arm/neon/ld1_dup.h | 548 +
.../simde-0.8.4-rc3/simde/arm/neon/ld1_lane.h | 527 +
.../simde-0.8.4-rc3/simde/arm/neon/ld1_x2.h | 491 +
.../simde-0.8.4-rc3/simde/arm/neon/ld1_x3.h | 514 +
.../simde-0.8.4-rc3/simde/arm/neon/ld1_x4.h | 545 +
.../simde-0.8.4-rc3/simde/arm/neon/ld1q_x2.h | 403 +
.../simde-0.8.4-rc3/simde/arm/neon/ld1q_x3.h | 404 +
.../simde-0.8.4-rc3/simde/arm/neon/ld1q_x4.h | 406 +
.../simde-0.8.4-rc3/simde/arm/neon/ld2.h | 1382 +
.../simde-0.8.4-rc3/simde/arm/neon/ld2_dup.h | 620 +
.../simde-0.8.4-rc3/simde/arm/neon/ld2_lane.h | 642 +
.../simde-0.8.4-rc3/simde/arm/neon/ld3.h | 1117 +
.../simde-0.8.4-rc3/simde/arm/neon/ld3_dup.h | 616 +
.../simde-0.8.4-rc3/simde/arm/neon/ld3_lane.h | 642 +
.../simde-0.8.4-rc3/simde/arm/neon/ld4.h | 921 +
.../simde-0.8.4-rc3/simde/arm/neon/ld4_dup.h | 617 +
.../simde-0.8.4-rc3/simde/arm/neon/ld4_lane.h | 829 +
.../simde-0.8.4-rc3/simde/arm/neon/max.h | 851 +
.../simde-0.8.4-rc3/simde/arm/neon/maxnm.h | 301 +
.../simde-0.8.4-rc3/simde/arm/neon/maxnmv.h | 203 +
.../simde-0.8.4-rc3/simde/arm/neon/maxv.h | 467 +
.../simde-0.8.4-rc3/simde/arm/neon/min.h | 858 +
.../simde-0.8.4-rc3/simde/arm/neon/minnm.h | 303 +
.../simde-0.8.4-rc3/simde/arm/neon/minnmv.h | 224 +
.../simde-0.8.4-rc3/simde/arm/neon/minv.h | 491 +
.../simde-0.8.4-rc3/simde/arm/neon/mla.h | 427 +
.../simde-0.8.4-rc3/simde/arm/neon/mla_lane.h | 241 +
.../simde-0.8.4-rc3/simde/arm/neon/mla_n.h | 354 +
.../simde-0.8.4-rc3/simde/arm/neon/mlal.h | 211 +
.../simde-0.8.4-rc3/simde/arm/neon/mlal_high.h | 211 +
.../simde/arm/neon/mlal_high_lane.h | 147 +
.../simde-0.8.4-rc3/simde/arm/neon/mlal_high_n.h | 157 +
.../simde-0.8.4-rc3/simde/arm/neon/mlal_lane.h | 120 +
.../simde-0.8.4-rc3/simde/arm/neon/mlal_n.h | 156 +
.../simde-0.8.4-rc3/simde/arm/neon/mls.h | 405 +
.../simde-0.8.4-rc3/simde/arm/neon/mls_lane.h | 240 +
.../simde-0.8.4-rc3/simde/arm/neon/mls_n.h | 252 +
.../simde-0.8.4-rc3/simde/arm/neon/mlsl.h | 179 +
.../simde-0.8.4-rc3/simde/arm/neon/mlsl_high.h | 191 +
.../simde/arm/neon/mlsl_high_lane.h | 147 +
.../simde-0.8.4-rc3/simde/arm/neon/mlsl_high_n.h | 161 +
.../simde-0.8.4-rc3/simde/arm/neon/mlsl_lane.h | 120 +
.../simde-0.8.4-rc3/simde/arm/neon/mlsl_n.h | 125 +
.../simde-0.8.4-rc3/simde/arm/neon/mmlaq.h | 158 +
.../simde-0.8.4-rc3/simde/arm/neon/movl.h | 257 +
.../simde-0.8.4-rc3/simde/arm/neon/movl_high.h | 126 +
.../simde-0.8.4-rc3/simde/arm/neon/movn.h | 208 +
.../simde-0.8.4-rc3/simde/arm/neon/movn_high.h | 125 +
.../simde-0.8.4-rc3/simde/arm/neon/mul.h | 774 +
.../simde-0.8.4-rc3/simde/arm/neon/mul_lane.h | 928 +
.../simde-0.8.4-rc3/simde/arm/neon/mul_n.h | 239 +
.../simde-0.8.4-rc3/simde/arm/neon/mull.h | 293 +
.../simde-0.8.4-rc3/simde/arm/neon/mull_high.h | 178 +
.../simde/arm/neon/mull_high_lane.h | 170 +
.../simde-0.8.4-rc3/simde/arm/neon/mull_high_n.h | 98 +
.../simde-0.8.4-rc3/simde/arm/neon/mull_lane.h | 120 +
.../simde-0.8.4-rc3/simde/arm/neon/mull_n.h | 158 +
.../simde-0.8.4-rc3/simde/arm/neon/mulx.h | 237 +
.../simde-0.8.4-rc3/simde/arm/neon/mulx_lane.h | 455 +
.../simde-0.8.4-rc3/simde/arm/neon/mulx_n.h | 69 +
.../simde-0.8.4-rc3/simde/arm/neon/mvn.h | 497 +
.../simde-0.8.4-rc3/simde/arm/neon/neg.h | 474 +
.../simde-0.8.4-rc3/simde/arm/neon/orn.h | 505 +
.../simde-0.8.4-rc3/simde/arm/neon/orr.h | 552 +
.../simde-0.8.4-rc3/simde/arm/neon/padal.h | 211 +
.../simde-0.8.4-rc3/simde/arm/neon/padd.h | 417 +
.../simde-0.8.4-rc3/simde/arm/neon/paddl.h | 347 +
.../simde-0.8.4-rc3/simde/arm/neon/pmax.h | 312 +
.../simde-0.8.4-rc3/simde/arm/neon/pmaxnm.h | 142 +
.../simde-0.8.4-rc3/simde/arm/neon/pmin.h | 337 +
.../simde-0.8.4-rc3/simde/arm/neon/pminnm.h | 142 +
.../simde-0.8.4-rc3/simde/arm/neon/qabs.h | 314 +
.../simde-0.8.4-rc3/simde/arm/neon/qadd.h | 798 +
.../simde-0.8.4-rc3/simde/arm/neon/qdmlal.h | 98 +
.../simde-0.8.4-rc3/simde/arm/neon/qdmlal_high.h | 69 +
.../simde/arm/neon/qdmlal_high_lane.h | 82 +
.../simde-0.8.4-rc3/simde/arm/neon/qdmlal_high_n.h | 69 +
.../simde-0.8.4-rc3/simde/arm/neon/qdmlal_lane.h | 122 +
.../simde-0.8.4-rc3/simde/arm/neon/qdmlal_n.h | 69 +
.../simde-0.8.4-rc3/simde/arm/neon/qdmlsl.h | 98 +
.../simde-0.8.4-rc3/simde/arm/neon/qdmlsl_high.h | 70 +
.../simde/arm/neon/qdmlsl_high_lane.h | 82 +
.../simde-0.8.4-rc3/simde/arm/neon/qdmlsl_high_n.h | 70 +
.../simde-0.8.4-rc3/simde/arm/neon/qdmlsl_lane.h | 122 +
.../simde-0.8.4-rc3/simde/arm/neon/qdmlsl_n.h | 69 +
.../simde-0.8.4-rc3/simde/arm/neon/qdmulh.h | 176 +
.../simde-0.8.4-rc3/simde/arm/neon/qdmulh_lane.h | 186 +
.../simde-0.8.4-rc3/simde/arm/neon/qdmulh_n.h | 80 +
.../simde-0.8.4-rc3/simde/arm/neon/qdmull.h | 170 +
.../simde-0.8.4-rc3/simde/arm/neon/qdmull_high.h | 69 +
.../simde/arm/neon/qdmull_high_lane.h | 107 +
.../simde-0.8.4-rc3/simde/arm/neon/qdmull_high_n.h | 70 +
.../simde-0.8.4-rc3/simde/arm/neon/qdmull_lane.h | 206 +
.../simde-0.8.4-rc3/simde/arm/neon/qdmull_n.h | 69 +
.../simde-0.8.4-rc3/simde/arm/neon/qmovn.h | 273 +
.../simde-0.8.4-rc3/simde/arm/neon/qmovn_high.h | 127 +
.../simde-0.8.4-rc3/simde/arm/neon/qmovun.h | 159 +
.../simde-0.8.4-rc3/simde/arm/neon/qmovun_high.h | 84 +
.../simde-0.8.4-rc3/simde/arm/neon/qneg.h | 301 +
.../simde-0.8.4-rc3/simde/arm/neon/qrdmlah.h | 186 +
.../simde-0.8.4-rc3/simde/arm/neon/qrdmlah_lane.h | 162 +
.../simde-0.8.4-rc3/simde/arm/neon/qrdmlsh.h | 186 +
.../simde-0.8.4-rc3/simde/arm/neon/qrdmlsh_lane.h | 162 +
.../simde-0.8.4-rc3/simde/arm/neon/qrdmulh.h | 175 +
.../simde-0.8.4-rc3/simde/arm/neon/qrdmulh_lane.h | 173 +
.../simde-0.8.4-rc3/simde/arm/neon/qrdmulh_n.h | 136 +
.../simde-0.8.4-rc3/simde/arm/neon/qrshl.h | 750 +
.../simde-0.8.4-rc3/simde/arm/neon/qrshrn_high_n.h | 189 +
.../simde-0.8.4-rc3/simde/arm/neon/qrshrn_n.h | 163 +
.../simde/arm/neon/qrshrun_high_n.h | 113 +
.../simde-0.8.4-rc3/simde/arm/neon/qrshrun_n.h | 114 +
.../simde-0.8.4-rc3/simde/arm/neon/qshl.h | 741 +
.../simde-0.8.4-rc3/simde/arm/neon/qshl_n.h | 513 +
.../simde-0.8.4-rc3/simde/arm/neon/qshlu_n.h | 485 +
.../simde-0.8.4-rc3/simde/arm/neon/qshrn_high_n.h | 101 +
.../simde-0.8.4-rc3/simde/arm/neon/qshrn_n.h | 164 +
.../simde-0.8.4-rc3/simde/arm/neon/qshrun_high_n.h | 113 +
.../simde-0.8.4-rc3/simde/arm/neon/qshrun_n.h | 102 +
.../simde-0.8.4-rc3/simde/arm/neon/qsub.h | 718 +
.../simde-0.8.4-rc3/simde/arm/neon/qtbl.h | 724 +
.../simde-0.8.4-rc3/simde/arm/neon/qtbx.h | 749 +
.../simde-0.8.4-rc3/simde/arm/neon/raddhn.h | 182 +
.../simde-0.8.4-rc3/simde/arm/neon/raddhn_high.h | 102 +
.../simde-0.8.4-rc3/simde/arm/neon/rax.h | 64 +
.../simde-0.8.4-rc3/simde/arm/neon/rbit.h | 209 +
.../simde-0.8.4-rc3/simde/arm/neon/recpe.h | 347 +
.../simde-0.8.4-rc3/simde/arm/neon/recps.h | 188 +
.../simde-0.8.4-rc3/simde/arm/neon/recpx.h | 133 +
.../simde-0.8.4-rc3/simde/arm/neon/reinterpret.h | 7638 +++++
.../simde-0.8.4-rc3/simde/arm/neon/rev16.h | 171 +
.../simde-0.8.4-rc3/simde/arm/neon/rev32.h | 302 +
.../simde-0.8.4-rc3/simde/arm/neon/rev64.h | 455 +
.../simde-0.8.4-rc3/simde/arm/neon/rhadd.h | 432 +
.../simde-0.8.4-rc3/simde/arm/neon/rnd.h | 208 +
.../simde-0.8.4-rc3/simde/arm/neon/rnd32x.h | 160 +
.../simde-0.8.4-rc3/simde/arm/neon/rnd32z.h | 160 +
.../simde-0.8.4-rc3/simde/arm/neon/rnd64x.h | 160 +
.../simde-0.8.4-rc3/simde/arm/neon/rnd64z.h | 160 +
.../simde-0.8.4-rc3/simde/arm/neon/rnda.h | 242 +
.../simde-0.8.4-rc3/simde/arm/neon/rndi.h | 200 +
.../simde-0.8.4-rc3/simde/arm/neon/rndm.h | 208 +
.../simde-0.8.4-rc3/simde/arm/neon/rndn.h | 228 +
.../simde-0.8.4-rc3/simde/arm/neon/rndp.h | 211 +
.../simde-0.8.4-rc3/simde/arm/neon/rndx.h | 194 +
.../simde-0.8.4-rc3/simde/arm/neon/rshl.h | 969 +
.../simde-0.8.4-rc3/simde/arm/neon/rshr_n.h | 528 +
.../simde-0.8.4-rc3/simde/arm/neon/rshrn_high_n.h | 101 +
.../simde-0.8.4-rc3/simde/arm/neon/rshrn_n.h | 101 +
.../simde-0.8.4-rc3/simde/arm/neon/rsqrte.h | 466 +
.../simde-0.8.4-rc3/simde/arm/neon/rsqrts.h | 218 +
.../simde-0.8.4-rc3/simde/arm/neon/rsra_n.h | 229 +
.../simde-0.8.4-rc3/simde/arm/neon/rsubhn.h | 209 +
.../simde-0.8.4-rc3/simde/arm/neon/rsubhn_high.h | 102 +
.../simde-0.8.4-rc3/simde/arm/neon/set_lane.h | 617 +
.../simde-0.8.4-rc3/simde/arm/neon/sha1.h | 208 +
.../simde-0.8.4-rc3/simde/arm/neon/sha256.h | 197 +
.../simde-0.8.4-rc3/simde/arm/neon/sha512.h | 157 +
.../simde-0.8.4-rc3/simde/arm/neon/shl.h | 1048 +
.../simde-0.8.4-rc3/simde/arm/neon/shl_n.h | 604 +
.../simde-0.8.4-rc3/simde/arm/neon/shll_high_n.h | 180 +
.../simde-0.8.4-rc3/simde/arm/neon/shll_n.h | 224 +
.../simde-0.8.4-rc3/simde/arm/neon/shr_n.h | 695 +
.../simde-0.8.4-rc3/simde/arm/neon/shrn_high_n.h | 114 +
.../simde-0.8.4-rc3/simde/arm/neon/shrn_n.h | 164 +
.../simde-0.8.4-rc3/simde/arm/neon/sli_n.h | 343 +
.../simde-0.8.4-rc3/simde/arm/neon/sm3.h | 267 +
.../simde-0.8.4-rc3/simde/arm/neon/sm4.h | 157 +
.../simde-0.8.4-rc3/simde/arm/neon/sqadd.h | 387 +
.../simde-0.8.4-rc3/simde/arm/neon/sqrt.h | 225 +
.../simde-0.8.4-rc3/simde/arm/neon/sra_n.h | 222 +
.../simde-0.8.4-rc3/simde/arm/neon/sri_n.h | 345 +
.../simde-0.8.4-rc3/simde/arm/neon/st1.h | 656 +
.../simde-0.8.4-rc3/simde/arm/neon/st1_lane.h | 530 +
.../simde-0.8.4-rc3/simde/arm/neon/st1_x2.h | 326 +
.../simde-0.8.4-rc3/simde/arm/neon/st1_x3.h | 341 +
.../simde-0.8.4-rc3/simde/arm/neon/st1_x4.h | 370 +
.../simde-0.8.4-rc3/simde/arm/neon/st1q_x2.h | 324 +
.../simde-0.8.4-rc3/simde/arm/neon/st1q_x3.h | 339 +
.../simde-0.8.4-rc3/simde/arm/neon/st1q_x4.h | 354 +
.../simde-0.8.4-rc3/simde/arm/neon/st2.h | 832 +
.../simde-0.8.4-rc3/simde/arm/neon/st2_lane.h | 622 +
.../simde-0.8.4-rc3/simde/arm/neon/st3.h | 1182 +
.../simde-0.8.4-rc3/simde/arm/neon/st3_lane.h | 622 +
.../simde-0.8.4-rc3/simde/arm/neon/st4.h | 924 +
.../simde-0.8.4-rc3/simde/arm/neon/st4_lane.h | 623 +
.../simde-0.8.4-rc3/simde/arm/neon/sub.h | 757 +
.../simde-0.8.4-rc3/simde/arm/neon/subhn.h | 211 +
.../simde-0.8.4-rc3/simde/arm/neon/subhn_high.h | 102 +
.../simde-0.8.4-rc3/simde/arm/neon/subl.h | 164 +
.../simde-0.8.4-rc3/simde/arm/neon/subl_high.h | 175 +
.../simde-0.8.4-rc3/simde/arm/neon/subw.h | 234 +
.../simde-0.8.4-rc3/simde/arm/neon/subw_high.h | 239 +
.../simde-0.8.4-rc3/simde/arm/neon/sudot_lane.h | 169 +
.../simde-0.8.4-rc3/simde/arm/neon/tbl.h | 338 +
.../simde-0.8.4-rc3/simde/arm/neon/tbx.h | 358 +
.../simde-0.8.4-rc3/simde/arm/neon/trn.h | 345 +
.../simde-0.8.4-rc3/simde/arm/neon/trn1.h | 692 +
.../simde-0.8.4-rc3/simde/arm/neon/trn2.h | 691 +
.../simde-0.8.4-rc3/simde/arm/neon/tst.h | 665 +
.../simde-0.8.4-rc3/simde/arm/neon/types.h | 1536 +
.../simde-0.8.4-rc3/simde/arm/neon/uqadd.h | 337 +
.../simde-0.8.4-rc3/simde/arm/neon/usdot.h | 95 +
.../simde-0.8.4-rc3/simde/arm/neon/usdot_lane.h | 169 +
.../simde-0.8.4-rc3/simde/arm/neon/uzp.h | 345 +
.../simde-0.8.4-rc3/simde/arm/neon/uzp1.h | 850 +
.../simde-0.8.4-rc3/simde/arm/neon/uzp2.h | 854 +
.../simde-0.8.4-rc3/simde/arm/neon/xar.h | 63 +
.../simde-0.8.4-rc3/simde/arm/neon/zip.h | 345 +
.../simde-0.8.4-rc3/simde/arm/neon/zip1.h | 864 +
.../simde-0.8.4-rc3/simde/arm/neon/zip2.h | 810 +
cpp/third_party/simde-0.8.4-rc3/simde/arm/sve.h | 47 +
.../simde-0.8.4-rc3/simde/arm/sve/add.h | 1350 +
.../simde-0.8.4-rc3/simde/arm/sve/and.h | 1003 +
.../simde-0.8.4-rc3/simde/arm/sve/cmplt.h | 503 +
.../simde-0.8.4-rc3/simde/arm/sve/cnt.h | 93 +
.../simde-0.8.4-rc3/simde/arm/sve/dup.h | 1133 +
.../simde-0.8.4-rc3/simde/arm/sve/ld1.h | 358 +
.../simde-0.8.4-rc3/simde/arm/sve/ptest.h | 71 +
.../simde-0.8.4-rc3/simde/arm/sve/ptrue.h | 157 +
.../simde-0.8.4-rc3/simde/arm/sve/qadd.h | 498 +
.../simde-0.8.4-rc3/simde/arm/sve/reinterpret.h | 754 +
.../simde-0.8.4-rc3/simde/arm/sve/sel.h | 626 +
.../simde-0.8.4-rc3/simde/arm/sve/st1.h | 307 +
.../simde-0.8.4-rc3/simde/arm/sve/sub.h | 1350 +
.../simde-0.8.4-rc3/simde/arm/sve/types.h | 915 +
.../simde-0.8.4-rc3/simde/arm/sve/whilelt.h | 843 +
cpp/third_party/simde-0.8.4-rc3/simde/check.h | 276 +
cpp/third_party/simde-0.8.4-rc3/simde/debug-trap.h | 85 +
cpp/third_party/simde-0.8.4-rc3/simde/hedley.h | 2049 ++
cpp/third_party/simde-0.8.4-rc3/simde/mips/msa.h | 44 +
.../simde-0.8.4-rc3/simde/mips/msa/add_a.h | 207 +
.../simde-0.8.4-rc3/simde/mips/msa/adds.h | 429 +
.../simde-0.8.4-rc3/simde/mips/msa/adds_a.h | 237 +
.../simde-0.8.4-rc3/simde/mips/msa/addv.h | 183 +
.../simde-0.8.4-rc3/simde/mips/msa/addvi.h | 187 +
.../simde-0.8.4-rc3/simde/mips/msa/and.h | 75 +
.../simde-0.8.4-rc3/simde/mips/msa/andi.h | 76 +
.../simde-0.8.4-rc3/simde/mips/msa/ld.h | 203 +
.../simde-0.8.4-rc3/simde/mips/msa/madd.h | 123 +
.../simde-0.8.4-rc3/simde/mips/msa/st.h | 98 +
.../simde-0.8.4-rc3/simde/mips/msa/subv.h | 183 +
.../simde-0.8.4-rc3/simde/mips/msa/types.h | 363 +
cpp/third_party/simde-0.8.4-rc3/simde/simde-aes.h | 270 +
.../simde-0.8.4-rc3/simde/simde-align.h | 450 +
cpp/third_party/simde-0.8.4-rc3/simde/simde-arch.h | 702 +
cpp/third_party/simde-0.8.4-rc3/simde/simde-bf16.h | 131 +
.../simde-0.8.4-rc3/simde/simde-common.h | 1256 +
.../simde-0.8.4-rc3/simde/simde-complex.h | 148 +
.../simde-0.8.4-rc3/simde/simde-constify.h | 397 +
.../simde-0.8.4-rc3/simde/simde-detect-clang.h | 145 +
.../simde-0.8.4-rc3/simde/simde-diagnostic.h | 458 +
cpp/third_party/simde-0.8.4-rc3/simde/simde-f16.h | 375 +
.../simde-0.8.4-rc3/simde/simde-features.h | 795 +
cpp/third_party/simde-0.8.4-rc3/simde/simde-math.h | 2066 ++
.../simde-0.8.4-rc3/simde/wasm/relaxed-simd.h | 607 +
.../simde-0.8.4-rc3/simde/wasm/simd128.h | 9318 ++++++
cpp/third_party/simde-0.8.4-rc3/simde/x86/aes.h | 431 +
cpp/third_party/simde-0.8.4-rc3/simde/x86/avx.h | 7061 +++++
cpp/third_party/simde-0.8.4-rc3/simde/x86/avx2.h | 6394 ++++
cpp/third_party/simde-0.8.4-rc3/simde/x86/avx512.h | 151 +
.../simde-0.8.4-rc3/simde/x86/avx512/2intersect.h | 249 +
.../simde-0.8.4-rc3/simde/x86/avx512/4dpwssd.h | 93 +
.../simde-0.8.4-rc3/simde/x86/avx512/4dpwssds.h | 93 +
.../simde-0.8.4-rc3/simde/x86/avx512/abs.h | 664 +
.../simde-0.8.4-rc3/simde/x86/avx512/add.h | 812 +
.../simde-0.8.4-rc3/simde/x86/avx512/adds.h | 641 +
.../simde-0.8.4-rc3/simde/x86/avx512/and.h | 529 +
.../simde-0.8.4-rc3/simde/x86/avx512/andnot.h | 193 +
.../simde-0.8.4-rc3/simde/x86/avx512/avg.h | 258 +
.../simde-0.8.4-rc3/simde/x86/avx512/bitshuffle.h | 228 +
.../simde-0.8.4-rc3/simde/x86/avx512/blend.h | 359 +
.../simde-0.8.4-rc3/simde/x86/avx512/broadcast.h | 1024 +
.../simde-0.8.4-rc3/simde/x86/avx512/cast.h | 357 +
.../simde-0.8.4-rc3/simde/x86/avx512/cmp.h | 1722 +
.../simde-0.8.4-rc3/simde/x86/avx512/cmpeq.h | 1200 +
.../simde-0.8.4-rc3/simde/x86/avx512/cmpge.h | 1434 +
.../simde-0.8.4-rc3/simde/x86/avx512/cmpgt.h | 212 +
.../simde-0.8.4-rc3/simde/x86/avx512/cmple.h | 1432 +
.../simde-0.8.4-rc3/simde/x86/avx512/cmplt.h | 123 +
.../simde-0.8.4-rc3/simde/x86/avx512/cmpneq.h | 1196 +
.../simde-0.8.4-rc3/simde/x86/avx512/compress.h | 1140 +
.../simde-0.8.4-rc3/simde/x86/avx512/conflict.h | 378 +
.../simde-0.8.4-rc3/simde/x86/avx512/copysign.h | 86 +
.../simde-0.8.4-rc3/simde/x86/avx512/cvt.h | 403 +
.../simde-0.8.4-rc3/simde/x86/avx512/cvts.h | 782 +
.../simde-0.8.4-rc3/simde/x86/avx512/cvtt.h | 130 +
.../simde-0.8.4-rc3/simde/x86/avx512/cvtus.h | 67 +
.../simde-0.8.4-rc3/simde/x86/avx512/dbsad.h | 414 +
.../simde-0.8.4-rc3/simde/x86/avx512/div.h | 162 +
.../simde-0.8.4-rc3/simde/x86/avx512/dpbf16.h | 308 +
.../simde-0.8.4-rc3/simde/x86/avx512/dpbusd.h | 318 +
.../simde-0.8.4-rc3/simde/x86/avx512/dpbusds.h | 370 +
.../simde-0.8.4-rc3/simde/x86/avx512/dpwssd.h | 295 +
.../simde-0.8.4-rc3/simde/x86/avx512/dpwssds.h | 325 +
.../simde-0.8.4-rc3/simde/x86/avx512/expand.h | 1032 +
.../simde-0.8.4-rc3/simde/x86/avx512/expandloadu.h | 548 +
.../simde-0.8.4-rc3/simde/x86/avx512/extract.h | 267 +
.../simde-0.8.4-rc3/simde/x86/avx512/fixupimm.h | 934 +
.../simde/x86/avx512/fixupimm_round.h | 714 +
.../simde/x86/avx512/flushsubnormal.h | 117 +
.../simde-0.8.4-rc3/simde/x86/avx512/fmadd.h | 136 +
.../simde-0.8.4-rc3/simde/x86/avx512/fmaddsub.h | 117 +
.../simde-0.8.4-rc3/simde/x86/avx512/fmsub.h | 276 +
.../simde-0.8.4-rc3/simde/x86/avx512/fnmadd.h | 108 +
.../simde-0.8.4-rc3/simde/x86/avx512/fnmsub.h | 108 +
.../simde-0.8.4-rc3/simde/x86/avx512/fpclass.h | 99 +
.../simde-0.8.4-rc3/simde/x86/avx512/gather.h | 313 +
.../simde-0.8.4-rc3/simde/x86/avx512/insert.h | 578 +
.../simde-0.8.4-rc3/simde/x86/avx512/kand.h | 53 +
.../simde-0.8.4-rc3/simde/x86/avx512/knot.h | 106 +
.../simde-0.8.4-rc3/simde/x86/avx512/kshift.h | 152 +
.../simde-0.8.4-rc3/simde/x86/avx512/kxor.h | 107 +
.../simde-0.8.4-rc3/simde/x86/avx512/load.h | 148 +
.../simde-0.8.4-rc3/simde/x86/avx512/loadu.h | 661 +
.../simde-0.8.4-rc3/simde/x86/avx512/lzcnt.h | 476 +
.../simde-0.8.4-rc3/simde/x86/avx512/madd.h | 157 +
.../simde-0.8.4-rc3/simde/x86/avx512/maddubs.h | 159 +
.../simde-0.8.4-rc3/simde/x86/avx512/max.h | 611 +
.../simde-0.8.4-rc3/simde/x86/avx512/min.h | 611 +
.../simde-0.8.4-rc3/simde/x86/avx512/mov.h | 1041 +
.../simde-0.8.4-rc3/simde/x86/avx512/mov_mask.h | 372 +
.../simde-0.8.4-rc3/simde/x86/avx512/movm.h | 461 +
.../simde-0.8.4-rc3/simde/x86/avx512/mul.h | 279 +
.../simde-0.8.4-rc3/simde/x86/avx512/mulhi.h | 65 +
.../simde-0.8.4-rc3/simde/x86/avx512/mulhrs.h | 65 +
.../simde-0.8.4-rc3/simde/x86/avx512/mullo.h | 169 +
.../simde-0.8.4-rc3/simde/x86/avx512/multishift.h | 197 +
.../simde-0.8.4-rc3/simde/x86/avx512/negate.h | 88 +
.../simde-0.8.4-rc3/simde/x86/avx512/or.h | 308 +
.../simde-0.8.4-rc3/simde/x86/avx512/packs.h | 122 +
.../simde-0.8.4-rc3/simde/x86/avx512/packus.h | 122 +
.../simde-0.8.4-rc3/simde/x86/avx512/permutex.h | 102 +
.../simde/x86/avx512/permutex2var.h | 1645 +
.../simde-0.8.4-rc3/simde/x86/avx512/permutexvar.h | 1206 +
.../simde-0.8.4-rc3/simde/x86/avx512/popcnt.h | 1373 +
.../simde-0.8.4-rc3/simde/x86/avx512/range.h | 773 +
.../simde-0.8.4-rc3/simde/x86/avx512/range_round.h | 713 +
.../simde-0.8.4-rc3/simde/x86/avx512/rcp.h | 65 +
.../simde-0.8.4-rc3/simde/x86/avx512/reduce.h | 355 +
.../simde-0.8.4-rc3/simde/x86/avx512/rol.h | 437 +
.../simde-0.8.4-rc3/simde/x86/avx512/rolv.h | 441 +
.../simde-0.8.4-rc3/simde/x86/avx512/ror.h | 437 +
.../simde-0.8.4-rc3/simde/x86/avx512/rorv.h | 417 +
.../simde-0.8.4-rc3/simde/x86/avx512/round.h | 311 +
.../simde-0.8.4-rc3/simde/x86/avx512/roundscale.h | 644 +
.../simde/x86/avx512/roundscale_round.h | 717 +
.../simde-0.8.4-rc3/simde/x86/avx512/sad.h | 77 +
.../simde-0.8.4-rc3/simde/x86/avx512/scalef.h | 416 +
.../simde-0.8.4-rc3/simde/x86/avx512/set.h | 572 +
.../simde-0.8.4-rc3/simde/x86/avx512/set1.h | 352 +
.../simde-0.8.4-rc3/simde/x86/avx512/set4.h | 140 +
.../simde-0.8.4-rc3/simde/x86/avx512/setone.h | 72 +
.../simde-0.8.4-rc3/simde/x86/avx512/setr.h | 144 +
.../simde-0.8.4-rc3/simde/x86/avx512/setr4.h | 140 +
.../simde-0.8.4-rc3/simde/x86/avx512/setzero.h | 105 +
.../simde-0.8.4-rc3/simde/x86/avx512/shldv.h | 184 +
.../simde-0.8.4-rc3/simde/x86/avx512/shuffle.h | 417 +
.../simde-0.8.4-rc3/simde/x86/avx512/sll.h | 247 +
.../simde-0.8.4-rc3/simde/x86/avx512/slli.h | 360 +
.../simde-0.8.4-rc3/simde/x86/avx512/sllv.h | 122 +
.../simde-0.8.4-rc3/simde/x86/avx512/sqrt.h | 127 +
.../simde-0.8.4-rc3/simde/x86/avx512/sra.h | 81 +
.../simde-0.8.4-rc3/simde/x86/avx512/srai.h | 96 +
.../simde-0.8.4-rc3/simde/x86/avx512/srav.h | 67 +
.../simde-0.8.4-rc3/simde/x86/avx512/srl.h | 216 +
.../simde-0.8.4-rc3/simde/x86/avx512/srli.h | 361 +
.../simde-0.8.4-rc3/simde/x86/avx512/srlv.h | 282 +
.../simde-0.8.4-rc3/simde/x86/avx512/store.h | 93 +
.../simde-0.8.4-rc3/simde/x86/avx512/storeu.h | 444 +
.../simde-0.8.4-rc3/simde/x86/avx512/sub.h | 382 +
.../simde-0.8.4-rc3/simde/x86/avx512/subs.h | 222 +
.../simde/x86/avx512/ternarylogic.h | 3769 +++
.../simde-0.8.4-rc3/simde/x86/avx512/test.h | 232 +
.../simde-0.8.4-rc3/simde/x86/avx512/testn.h | 63 +
.../simde-0.8.4-rc3/simde/x86/avx512/types.h | 995 +
.../simde-0.8.4-rc3/simde/x86/avx512/unpackhi.h | 753 +
.../simde-0.8.4-rc3/simde/x86/avx512/unpacklo.h | 752 +
.../simde-0.8.4-rc3/simde/x86/avx512/xor.h | 319 +
.../simde-0.8.4-rc3/simde/x86/avx512/xorsign.h | 72 +
cpp/third_party/simde-0.8.4-rc3/simde/x86/bmi.h | 172 +
cpp/third_party/simde-0.8.4-rc3/simde/x86/clmul.h | 402 +
cpp/third_party/simde-0.8.4-rc3/simde/x86/f16c.h | 279 +
cpp/third_party/simde-0.8.4-rc3/simde/x86/fma.h | 813 +
cpp/third_party/simde-0.8.4-rc3/simde/x86/gfni.h | 1311 +
cpp/third_party/simde-0.8.4-rc3/simde/x86/mmx.h | 2388 ++
cpp/third_party/simde-0.8.4-rc3/simde/x86/sse.h | 5004 +++
cpp/third_party/simde-0.8.4-rc3/simde/x86/sse2.h | 8746 ++++++
cpp/third_party/simde-0.8.4-rc3/simde/x86/sse3.h | 573 +
cpp/third_party/simde-0.8.4-rc3/simde/x86/sse4.1.h | 2558 ++
cpp/third_party/simde-0.8.4-rc3/simde/x86/sse4.2.h | 554 +
cpp/third_party/simde-0.8.4-rc3/simde/x86/ssse3.h | 1111 +
cpp/third_party/simde-0.8.4-rc3/simde/x86/svml.h | 12113 +++++++
cpp/third_party/simde-0.8.4-rc3/simde/x86/xop.h | 3751 +++
cpp/third_party/simde-0.8.4-rc3/test/.gitignore | 7 +
.../simde-0.8.4-rc3/test/arm/declare-suites.h | 2 +
.../simde-0.8.4-rc3/test/arm/meson.build | 2 +
.../simde-0.8.4-rc3/test/arm/neon/aba.c | 963 +
.../simde-0.8.4-rc3/test/arm/neon/abal.c | 418 +
.../simde-0.8.4-rc3/test/arm/neon/abal_high.c | 458 +
.../simde-0.8.4-rc3/test/arm/neon/abd.c | 1396 +
.../simde-0.8.4-rc3/test/arm/neon/abdl.c | 389 +
.../simde-0.8.4-rc3/test/arm/neon/abdl_high.c | 539 +
.../simde-0.8.4-rc3/test/arm/neon/abs.c | 824 +
.../simde-0.8.4-rc3/test/arm/neon/add.c | 2142 ++
.../simde-0.8.4-rc3/test/arm/neon/addhn.c | 390 +
.../simde-0.8.4-rc3/test/arm/neon/addhn_high.c | 580 +
.../simde-0.8.4-rc3/test/arm/neon/addl.c | 304 +
.../simde-0.8.4-rc3/test/arm/neon/addl_high.c | 336 +
.../simde-0.8.4-rc3/test/arm/neon/addlv.c | 615 +
.../simde-0.8.4-rc3/test/arm/neon/addv.c | 851 +
.../simde-0.8.4-rc3/test/arm/neon/addw.c | 303 +
.../simde-0.8.4-rc3/test/arm/neon/addw_high.c | 403 +
.../simde-0.8.4-rc3/test/arm/neon/aes.c | 438 +
.../simde-0.8.4-rc3/test/arm/neon/and.c | 798 +
.../simde-0.8.4-rc3/test/arm/neon/bcax.c | 664 +
.../simde-0.8.4-rc3/test/arm/neon/bic.c | 1027 +
.../simde-0.8.4-rc3/test/arm/neon/bsl.c | 2046 ++
.../simde-0.8.4-rc3/test/arm/neon/cadd_rot270.c | 347 +
.../simde-0.8.4-rc3/test/arm/neon/cadd_rot90.c | 349 +
.../simde-0.8.4-rc3/test/arm/neon/cage.c | 545 +
.../simde-0.8.4-rc3/test/arm/neon/cagt.c | 549 +
.../simde-0.8.4-rc3/test/arm/neon/cale.c | 544 +
.../simde-0.8.4-rc3/test/arm/neon/calt.c | 547 +
.../simde-0.8.4-rc3/test/arm/neon/ceq.c | 1975 ++
.../simde-0.8.4-rc3/test/arm/neon/ceqz.c | 1526 +
.../simde-0.8.4-rc3/test/arm/neon/cge.c | 1884 ++
.../simde-0.8.4-rc3/test/arm/neon/cgez.c | 1014 +
.../simde-0.8.4-rc3/test/arm/neon/cgt.c | 1803 ++
.../simde-0.8.4-rc3/test/arm/neon/cgtz.c | 1014 +
.../simde-0.8.4-rc3/test/arm/neon/cle.c | 1878 ++
.../simde-0.8.4-rc3/test/arm/neon/clez.c | 951 +
.../simde-0.8.4-rc3/test/arm/neon/cls.c | 708 +
.../simde-0.8.4-rc3/test/arm/neon/clt.c | 1797 ++
.../simde-0.8.4-rc3/test/arm/neon/cltz.c | 926 +
.../simde-0.8.4-rc3/test/arm/neon/clz.c | 1038 +
.../simde-0.8.4-rc3/test/arm/neon/cmla.c | 422 +
.../simde-0.8.4-rc3/test/arm/neon/cmla_lane.c | 853 +
.../simde-0.8.4-rc3/test/arm/neon/cmla_rot180.c | 422 +
.../test/arm/neon/cmla_rot180_lane.c | 848 +
.../simde-0.8.4-rc3/test/arm/neon/cmla_rot270.c | 422 +
.../test/arm/neon/cmla_rot270_lane.c | 860 +
.../simde-0.8.4-rc3/test/arm/neon/cmla_rot90.c | 422 +
.../test/arm/neon/cmla_rot90_lane.c | 850 +
.../simde-0.8.4-rc3/test/arm/neon/cnt.c | 369 +
.../simde-0.8.4-rc3/test/arm/neon/combine.c | 961 +
.../simde-0.8.4-rc3/test/arm/neon/copy_lane.c | 7174 +++++
.../simde-0.8.4-rc3/test/arm/neon/crc32.c | 516 +
.../simde-0.8.4-rc3/test/arm/neon/create.c | 733 +
.../simde-0.8.4-rc3/test/arm/neon/cvt.c | 4216 +++
.../simde-0.8.4-rc3/test/arm/neon/cvt_n.c | 3384 ++
.../simde-0.8.4-rc3/test/arm/neon/cvtm.c | 1199 +
.../simde-0.8.4-rc3/test/arm/neon/cvtn.c | 968 +
.../simde-0.8.4-rc3/test/arm/neon/cvtp.c | 1187 +
.../simde-0.8.4-rc3/test/arm/neon/div.c | 508 +
.../simde-0.8.4-rc3/test/arm/neon/dot.c | 491 +
.../simde-0.8.4-rc3/test/arm/neon/dot_lane.c | 1054 +
.../simde-0.8.4-rc3/test/arm/neon/dup_lane.c | 4480 +++
.../simde-0.8.4-rc3/test/arm/neon/dup_n.c | 1536 +
.../simde-0.8.4-rc3/test/arm/neon/eor.c | 1872 ++
.../simde-0.8.4-rc3/test/arm/neon/ext.c | 2264 ++
.../simde-0.8.4-rc3/test/arm/neon/fma.c | 501 +
.../simde-0.8.4-rc3/test/arm/neon/fma_lane.c | 1651 +
.../simde-0.8.4-rc3/test/arm/neon/fma_n.c | 441 +
.../simde-0.8.4-rc3/test/arm/neon/fmlal.c | 1517 +
.../simde-0.8.4-rc3/test/arm/neon/fmlsl.c | 833 +
.../simde-0.8.4-rc3/test/arm/neon/fms.c | 348 +
.../simde-0.8.4-rc3/test/arm/neon/fms_lane.c | 1239 +
.../simde-0.8.4-rc3/test/arm/neon/fms_n.c | 304 +
.../simde-0.8.4-rc3/test/arm/neon/get_high.c | 799 +
.../simde-0.8.4-rc3/test/arm/neon/get_lane.c | 1454 +
.../simde-0.8.4-rc3/test/arm/neon/get_low.c | 799 +
.../simde-0.8.4-rc3/test/arm/neon/hadd.c | 789 +
.../simde-0.8.4-rc3/test/arm/neon/hsub.c | 789 +
.../simde-0.8.4-rc3/test/arm/neon/ld1_dup.c | 1802 ++
.../simde-0.8.4-rc3/test/arm/neon/ld1_lane.c | 1790 ++
.../simde-0.8.4-rc3/test/arm/neon/ld1_x2.c | 1004 +
.../simde-0.8.4-rc3/test/arm/neon/ld1_x3.c | 1199 +
.../simde-0.8.4-rc3/test/arm/neon/ld1_x4.c | 1363 +
.../simde-0.8.4-rc3/test/arm/neon/ld1q_x2.c | 1175 +
.../simde-0.8.4-rc3/test/arm/neon/ld1q_x3.c | 1483 +
.../simde-0.8.4-rc3/test/arm/neon/ld1q_x4.c | 1915 ++
.../simde-0.8.4-rc3/test/arm/neon/ld2.c | 2363 ++
.../simde-0.8.4-rc3/test/arm/neon/ld2_dup.c | 1936 ++
.../simde-0.8.4-rc3/test/arm/neon/ld2_lane.c | 2198 ++
.../simde-0.8.4-rc3/test/arm/neon/ld3.c | 315 +
.../simde-0.8.4-rc3/test/arm/neon/ld3_dup.c | 2303 ++
.../simde-0.8.4-rc3/test/arm/neon/ld3_lane.c | 2732 ++
.../simde-0.8.4-rc3/test/arm/neon/ld4.c | 293 +
.../simde-0.8.4-rc3/test/arm/neon/ld4_dup.c | 2620 ++
.../simde-0.8.4-rc3/test/arm/neon/ld4_lane.c | 3071 ++
.../simde-0.8.4-rc3/test/arm/neon/max.c | 1459 +
.../simde-0.8.4-rc3/test/arm/neon/maxnm.c | 501 +
.../simde-0.8.4-rc3/test/arm/neon/maxnmv.c | 283 +
.../simde-0.8.4-rc3/test/arm/neon/maxv.c | 874 +
.../simde-0.8.4-rc3/test/arm/neon/meson.build | 37 +
.../simde-0.8.4-rc3/test/arm/neon/min.c | 1500 +
.../simde-0.8.4-rc3/test/arm/neon/minnm.c | 501 +
.../simde-0.8.4-rc3/test/arm/neon/minnmv.c | 277 +
.../simde-0.8.4-rc3/test/arm/neon/minv.c | 889 +
.../simde-0.8.4-rc3/test/arm/neon/mla.c | 978 +
.../simde-0.8.4-rc3/test/arm/neon/mla_lane.c | 1756 ++
.../simde-0.8.4-rc3/test/arm/neon/mla_n.c | 752 +
.../simde-0.8.4-rc3/test/arm/neon/mlal.c | 459 +
.../simde-0.8.4-rc3/test/arm/neon/mlal_high.c | 497 +
.../simde-0.8.4-rc3/test/arm/neon/mlal_high_lane.c | 605 +
.../simde-0.8.4-rc3/test/arm/neon/mlal_high_n.c | 317 +
.../simde-0.8.4-rc3/test/arm/neon/mlal_lane.c | 1039 +
.../simde-0.8.4-rc3/test/arm/neon/mlal_n.c | 317 +
.../simde-0.8.4-rc3/test/arm/neon/mls.c | 1235 +
.../simde-0.8.4-rc3/test/arm/neon/mls_lane.c | 1601 +
.../simde-0.8.4-rc3/test/arm/neon/mls_n.c | 742 +
.../simde-0.8.4-rc3/test/arm/neon/mlsl.c | 461 +
.../simde-0.8.4-rc3/test/arm/neon/mlsl_high.c | 493 +
.../simde-0.8.4-rc3/test/arm/neon/mlsl_high_lane.c | 605 +
.../simde-0.8.4-rc3/test/arm/neon/mlsl_high_n.c | 317 +
.../simde-0.8.4-rc3/test/arm/neon/mlsl_lane.c | 1043 +
.../simde-0.8.4-rc3/test/arm/neon/mlsl_n.c | 315 +
.../simde-0.8.4-rc3/test/arm/neon/mmlaq.c | 470 +
.../simde-0.8.4-rc3/test/arm/neon/modify_c.txt | 83 +
.../simde-0.8.4-rc3/test/arm/neon/movl.c | 246 +
.../simde-0.8.4-rc3/test/arm/neon/movl_high.c | 259 +
.../simde-0.8.4-rc3/test/arm/neon/movn.c | 314 +
.../simde-0.8.4-rc3/test/arm/neon/movn_high.c | 405 +
.../simde-0.8.4-rc3/test/arm/neon/mul.c | 1725 +
.../simde-0.8.4-rc3/test/arm/neon/mul_lane.c | 2369 ++
.../simde-0.8.4-rc3/test/arm/neon/mul_n.c | 815 +
.../simde-0.8.4-rc3/test/arm/neon/mull.c | 548 +
.../simde-0.8.4-rc3/test/arm/neon/mull_high.c | 611 +
.../simde-0.8.4-rc3/test/arm/neon/mull_high_lane.c | 667 +
.../simde-0.8.4-rc3/test/arm/neon/mull_high_n.c | 292 +
.../simde-0.8.4-rc3/test/arm/neon/mull_lane.c | 888 +
.../simde-0.8.4-rc3/test/arm/neon/mull_n.c | 273 +
.../simde-0.8.4-rc3/test/arm/neon/mulx.c | 598 +
.../simde-0.8.4-rc3/test/arm/neon/mulx_lane.c | 1285 +
.../simde-0.8.4-rc3/test/arm/neon/mulx_n.c | 169 +
.../simde-0.8.4-rc3/test/arm/neon/mvn.c | 748 +
.../simde-0.8.4-rc3/test/arm/neon/neg.c | 734 +
.../simde-0.8.4-rc3/test/arm/neon/orn.c | 1028 +
.../simde-0.8.4-rc3/test/arm/neon/orr.c | 798 +
.../simde-0.8.4-rc3/test/arm/neon/padal.c | 756 +
.../simde-0.8.4-rc3/test/arm/neon/padd.c | 1425 +
.../simde-0.8.4-rc3/test/arm/neon/paddl.c | 468 +
.../simde-0.8.4-rc3/test/arm/neon/pmax.c | 1206 +
.../simde-0.8.4-rc3/test/arm/neon/pmaxnm.c | 440 +
.../simde-0.8.4-rc3/test/arm/neon/pmin.c | 1221 +
.../simde-0.8.4-rc3/test/arm/neon/pminnm.c | 455 +
.../simde-0.8.4-rc3/test/arm/neon/qabs.c | 631 +
.../simde-0.8.4-rc3/test/arm/neon/qadd.c | 1477 +
.../simde-0.8.4-rc3/test/arm/neon/qdmlal.c | 296 +
.../simde-0.8.4-rc3/test/arm/neon/qdmlal_high.c | 177 +
.../test/arm/neon/qdmlal_high_lane.c | 364 +
.../simde-0.8.4-rc3/test/arm/neon/qdmlal_high_n.c | 167 +
.../simde-0.8.4-rc3/test/arm/neon/qdmlal_lane.c | 714 +
.../simde-0.8.4-rc3/test/arm/neon/qdmlal_n.c | 158 +
.../simde-0.8.4-rc3/test/arm/neon/qdmlsl.c | 296 +
.../simde-0.8.4-rc3/test/arm/neon/qdmlsl_high.c | 177 +
.../test/arm/neon/qdmlsl_high_lane.c | 366 +
.../simde-0.8.4-rc3/test/arm/neon/qdmlsl_high_n.c | 167 +
.../simde-0.8.4-rc3/test/arm/neon/qdmlsl_lane.c | 715 +
.../simde-0.8.4-rc3/test/arm/neon/qdmlsl_n.c | 158 +
.../simde-0.8.4-rc3/test/arm/neon/qdmulh.c | 378 +
.../simde-0.8.4-rc3/test/arm/neon/qdmulh_lane.c | 1215 +
.../simde-0.8.4-rc3/test/arm/neon/qdmulh_n.c | 153 +
.../simde-0.8.4-rc3/test/arm/neon/qdmull.c | 242 +
.../simde-0.8.4-rc3/test/arm/neon/qdmull_high.c | 142 +
.../test/arm/neon/qdmull_high_lane.c | 280 +
.../simde-0.8.4-rc3/test/arm/neon/qdmull_high_n.c | 134 +
.../simde-0.8.4-rc3/test/arm/neon/qdmull_lane.c | 722 +
.../simde-0.8.4-rc3/test/arm/neon/qdmull_n.c | 126 +
.../simde-0.8.4-rc3/test/arm/neon/qmovn.c | 327 +
.../simde-0.8.4-rc3/test/arm/neon/qmovn_high.c | 415 +
.../simde-0.8.4-rc3/test/arm/neon/qmovun.c | 180 +
.../simde-0.8.4-rc3/test/arm/neon/qmovun_high.c | 267 +
.../simde-0.8.4-rc3/test/arm/neon/qneg.c | 625 +
.../simde-0.8.4-rc3/test/arm/neon/qrdmlah.c | 447 +
.../simde-0.8.4-rc3/test/arm/neon/qrdmlah_lane.c | 1305 +
.../simde-0.8.4-rc3/test/arm/neon/qrdmlsh.c | 499 +
.../simde-0.8.4-rc3/test/arm/neon/qrdmlsh_lane.c | 1305 +
.../simde-0.8.4-rc3/test/arm/neon/qrdmulh.c | 288 +
.../simde-0.8.4-rc3/test/arm/neon/qrdmulh_lane.c | 1260 +
.../simde-0.8.4-rc3/test/arm/neon/qrdmulh_n.c | 288 +
.../simde-0.8.4-rc3/test/arm/neon/qrshl.c | 5456 ++++
.../simde-0.8.4-rc3/test/arm/neon/qrshrn_high_n.c | 714 +
.../simde-0.8.4-rc3/test/arm/neon/qrshrn_n.c | 1149 +
.../simde-0.8.4-rc3/test/arm/neon/qrshrun_high_n.c | 374 +
.../simde-0.8.4-rc3/test/arm/neon/qrshrun_n.c | 619 +
.../simde-0.8.4-rc3/test/arm/neon/qshl.c | 4437 +++
.../simde-0.8.4-rc3/test/arm/neon/qshl_n.c | 1295 +
.../simde-0.8.4-rc3/test/arm/neon/qshlu_n.c | 2152 ++
.../simde-0.8.4-rc3/test/arm/neon/qshrn_high_n.c | 713 +
.../simde-0.8.4-rc3/test/arm/neon/qshrn_n.c | 1197 +
.../simde-0.8.4-rc3/test/arm/neon/qshrun_high_n.c | 481 +
.../simde-0.8.4-rc3/test/arm/neon/qshrun_n.c | 635 +
.../simde-0.8.4-rc3/test/arm/neon/qsub.c | 1477 +
.../simde-0.8.4-rc3/test/arm/neon/qtbl.c | 2533 ++
.../simde-0.8.4-rc3/test/arm/neon/qtbx.c | 2749 ++
.../simde-0.8.4-rc3/test/arm/neon/raddhn.c | 396 +
.../simde-0.8.4-rc3/test/arm/neon/raddhn_high.c | 469 +
.../simde-0.8.4-rc3/test/arm/neon/rax.c | 95 +
.../simde-0.8.4-rc3/test/arm/neon/rbit.c | 376 +
.../simde-0.8.4-rc3/test/arm/neon/recpe.c | 573 +
.../simde-0.8.4-rc3/test/arm/neon/recps.c | 582 +
.../simde-0.8.4-rc3/test/arm/neon/recpx.c | 183 +
.../simde-0.8.4-rc3/test/arm/neon/reinterpret.c | 20798 ++++++++++++
.../simde-0.8.4-rc3/test/arm/neon/rev16.c | 376 +
.../simde-0.8.4-rc3/test/arm/neon/rev32.c | 656 +
.../simde-0.8.4-rc3/test/arm/neon/rev64.c | 1046 +
.../simde-0.8.4-rc3/test/arm/neon/rhadd.c | 803 +
.../simde-0.8.4-rc3/test/arm/neon/rnd.c | 419 +
.../simde-0.8.4-rc3/test/arm/neon/rnd32x.c | 249 +
.../simde-0.8.4-rc3/test/arm/neon/rnd32z.c | 249 +
.../simde-0.8.4-rc3/test/arm/neon/rnd64x.c | 249 +
.../simde-0.8.4-rc3/test/arm/neon/rnd64z.c | 249 +
.../simde-0.8.4-rc3/test/arm/neon/rnda.c | 404 +
.../simde-0.8.4-rc3/test/arm/neon/rndi.c | 423 +
.../simde-0.8.4-rc3/test/arm/neon/rndm.c | 423 +
.../simde-0.8.4-rc3/test/arm/neon/rndn.c | 395 +
.../simde-0.8.4-rc3/test/arm/neon/rndp.c | 419 +
.../simde-0.8.4-rc3/test/arm/neon/rndx.c | 406 +
.../simde-0.8.4-rc3/test/arm/neon/rshl.c | 1476 +
.../simde-0.8.4-rc3/test/arm/neon/rshr_n.c | 1954 ++
.../simde-0.8.4-rc3/test/arm/neon/rshrn_high_n.c | 671 +
.../simde-0.8.4-rc3/test/arm/neon/rshrn_n.c | 644 +
.../simde-0.8.4-rc3/test/arm/neon/rsqrte.c | 530 +
.../simde-0.8.4-rc3/test/arm/neon/rsqrts.c | 540 +
.../simde-0.8.4-rc3/test/arm/neon/rsra_n.c | 1814 ++
.../simde-0.8.4-rc3/test/arm/neon/rsubhn.c | 396 +
.../simde-0.8.4-rc3/test/arm/neon/rsubhn_high.c | 469 +
.../simde-0.8.4-rc3/test/arm/neon/run-tests-neon.h | 3 +
.../simde-0.8.4-rc3/test/arm/neon/run-tests.c | 30 +
.../simde-0.8.4-rc3/test/arm/neon/run-tests.h | 8 +
.../simde-0.8.4-rc3/test/arm/neon/set_lane.c | 2284 ++
.../simde-0.8.4-rc3/test/arm/neon/sha1.c | 431 +
.../simde-0.8.4-rc3/test/arm/neon/sha256.c | 311 +
.../simde-0.8.4-rc3/test/arm/neon/sha512.c | 311 +
.../simde-0.8.4-rc3/test/arm/neon/shl.c | 797 +
.../simde-0.8.4-rc3/test/arm/neon/shl_n.c | 1412 +
.../simde-0.8.4-rc3/test/arm/neon/shll_high_n.c | 783 +
.../simde-0.8.4-rc3/test/arm/neon/shll_n.c | 777 +
.../simde-0.8.4-rc3/test/arm/neon/shr_n.c | 1662 +
.../simde-0.8.4-rc3/test/arm/neon/shrn_high_n.c | 1066 +
.../simde-0.8.4-rc3/test/arm/neon/shrn_n.c | 642 +
.../simde-0.8.4-rc3/test/arm/neon/skel-single.c | 668 +
.../simde-0.8.4-rc3/test/arm/neon/skel-triple.c | 828 +
.../simde-0.8.4-rc3/test/arm/neon/skel.c | 748 +
.../simde-0.8.4-rc3/test/arm/neon/sli_n.c | 2750 ++
.../simde-0.8.4-rc3/test/arm/neon/sm3.c | 588 +
.../simde-0.8.4-rc3/test/arm/neon/sm4.c | 155 +
.../simde-0.8.4-rc3/test/arm/neon/sqadd.c | 751 +
.../simde-0.8.4-rc3/test/arm/neon/sqrt.c | 312 +
.../simde-0.8.4-rc3/test/arm/neon/sra_n.c | 1859 ++
.../simde-0.8.4-rc3/test/arm/neon/sri_n.c | 2752 ++
.../simde-0.8.4-rc3/test/arm/neon/st1_lane.c | 1718 +
.../simde-0.8.4-rc3/test/arm/neon/st1_x2.c | 1062 +
.../simde-0.8.4-rc3/test/arm/neon/st1_x3.c | 1359 +
.../simde-0.8.4-rc3/test/arm/neon/st1_x4.c | 1568 +
.../simde-0.8.4-rc3/test/arm/neon/st1q_x2.c | 1374 +
.../simde-0.8.4-rc3/test/arm/neon/st1q_x3.c | 1759 ++
.../simde-0.8.4-rc3/test/arm/neon/st1q_x4.c | 2112 ++
.../simde-0.8.4-rc3/test/arm/neon/st2.c | 2121 ++
.../simde-0.8.4-rc3/test/arm/neon/st2_lane.c | 1872 ++
.../simde-0.8.4-rc3/test/arm/neon/st3.c | 6524 ++++
.../simde-0.8.4-rc3/test/arm/neon/st3_lane.c | 1991 ++
.../simde-0.8.4-rc3/test/arm/neon/st4.c | 8082 +++++
.../simde-0.8.4-rc3/test/arm/neon/st4_lane.c | 2208 ++
.../simde-0.8.4-rc3/test/arm/neon/sub.c | 1135 +
.../simde-0.8.4-rc3/test/arm/neon/subhn.c | 390 +
.../simde-0.8.4-rc3/test/arm/neon/subhn_high.c | 581 +
.../simde-0.8.4-rc3/test/arm/neon/subl.c | 387 +
.../simde-0.8.4-rc3/test/arm/neon/subl_high.c | 421 +
.../simde-0.8.4-rc3/test/arm/neon/subw.c | 387 +
.../simde-0.8.4-rc3/test/arm/neon/subw_high.c | 403 +
.../simde-0.8.4-rc3/test/arm/neon/sudot_lane.c | 512 +
.../simde-0.8.4-rc3/test/arm/neon/tbl.c | 1010 +
.../simde-0.8.4-rc3/test/arm/neon/tbx.c | 1078 +
.../test/arm/neon/test-neon-footer.h | 29 +
.../simde-0.8.4-rc3/test/arm/neon/test-neon.h | 759 +
.../simde-0.8.4-rc3/test/arm/neon/trn.c | 1994 ++
.../simde-0.8.4-rc3/test/arm/neon/trn1.c | 1563 +
.../simde-0.8.4-rc3/test/arm/neon/trn2.c | 1563 +
.../simde-0.8.4-rc3/test/arm/neon/tst.c | 1494 +
.../simde-0.8.4-rc3/test/arm/neon/uqadd.c | 771 +
.../simde-0.8.4-rc3/test/arm/neon/usdot.c | 212 +
.../simde-0.8.4-rc3/test/arm/neon/usdot_lane.c | 562 +
.../simde-0.8.4-rc3/test/arm/neon/uzp.c | 1955 ++
.../simde-0.8.4-rc3/test/arm/neon/uzp1.c | 1567 +
.../simde-0.8.4-rc3/test/arm/neon/uzp2.c | 1555 +
.../simde-0.8.4-rc3/test/arm/neon/xar.c | 106 +
.../simde-0.8.4-rc3/test/arm/neon/zip.c | 1940 ++
.../simde-0.8.4-rc3/test/arm/neon/zip1.c | 1491 +
.../simde-0.8.4-rc3/test/arm/neon/zip2.c | 1491 +
.../simde-0.8.4-rc3/test/arm/run-tests.c | 22 +
.../simde-0.8.4-rc3/test/arm/run-tests.h | 10 +
cpp/third_party/simde-0.8.4-rc3/test/arm/sve/add.c | 7739 +++++
cpp/third_party/simde-0.8.4-rc3/test/arm/sve/and.c | 3901 +++
.../simde-0.8.4-rc3/test/arm/sve/cmplt.c | 2134 ++
cpp/third_party/simde-0.8.4-rc3/test/arm/sve/dup.c | 4406 +++
.../simde-0.8.4-rc3/test/arm/sve/meson.build | 32 +
.../simde-0.8.4-rc3/test/arm/sve/qadd.c | 3007 ++
.../simde-0.8.4-rc3/test/arm/sve/run-tests-neon.h | 3 +
.../simde-0.8.4-rc3/test/arm/sve/run-tests.c | 30 +
.../simde-0.8.4-rc3/test/arm/sve/run-tests.h | 8 +
cpp/third_party/simde-0.8.4-rc3/test/arm/sve/sel.c | 4607 +++
cpp/third_party/simde-0.8.4-rc3/test/arm/sve/sub.c | 9434 ++++++
.../simde-0.8.4-rc3/test/arm/sve/test-sve-footer.h | 29 +
.../simde-0.8.4-rc3/test/arm/sve/test-sve.h | 50 +
.../simde-0.8.4-rc3/test/arm/test-arm.h | 0
cpp/third_party/simde-0.8.4-rc3/test/arm64cl.txt | 17 +
.../simde-0.8.4-rc3/test/check-flags.sh | 92 +
.../test/cmake-packaging/CMakeLists.txt | 27 +
.../cmake-fetchcontent/CMakeLists.txt | 16 +
.../test/cmake-packaging/cmake-fetchcontent/main.c | 9 +
.../cmake-packaging/cmake-fetchcontent/main.cpp | 9 +
.../cmake-find-package/CMakeLists.txt | 15 +
.../test/cmake-packaging/cmake-find-package/main.c | 9 +
.../cmake-packaging/cmake-find-package/main.cpp | 9 +
.../simde-0.8.4-rc3/test/common/common.c | 119 +
.../simde-0.8.4-rc3/test/common/meson.build | 29 +
.../simde-0.8.4-rc3/test/declare-suites.h | 4 +
.../test/docker/Dockerfile.Ubuntu1404 | 12 +
.../test/docker/Dockerfile.Ubuntu1604 | 34 +
.../test/docker/Dockerfile.Ubuntu1804 | 11 +
.../simde-0.8.4-rc3/test/docker/Dockerfile.arm64 | 26 +
.../simde-0.8.4-rc3/test/docker/Dockerfile.arm7 | 30 +
.../simde-0.8.4-rc3/test/docker/Dockerfile.icc | 21 +
.../simde-0.8.4-rc3/test/docker/Dockerfile.mipsel | 9 +
.../simde-0.8.4-rc3/test/docker/Dockerfile.ppc64el | 26 +
.../test/docker/Dockerfile.qemu5.s390x | 22 +
.../simde-0.8.4-rc3/test/docker/Dockerfile.s390x | 24 +
.../simde-0.8.4-rc3/test/download-iig.sh | 12 +
.../simde-0.8.4-rc3/test/download-sde.sh | 9 +
cpp/third_party/simde-0.8.4-rc3/test/meson.build | 67 +
.../simde-0.8.4-rc3/test/mips/declare-suites.h | 1 +
.../simde-0.8.4-rc3/test/mips/meson.build | 1 +
.../simde-0.8.4-rc3/test/mips/msa/add_a.c | 295 +
.../simde-0.8.4-rc3/test/mips/msa/adds.c | 555 +
.../simde-0.8.4-rc3/test/mips/msa/adds_a.c | 295 +
.../simde-0.8.4-rc3/test/mips/msa/addv.c | 295 +
.../simde-0.8.4-rc3/test/mips/msa/addvi.c | 359 +
.../simde-0.8.4-rc3/test/mips/msa/and.c | 118 +
.../simde-0.8.4-rc3/test/mips/msa/andi.c | 140 +
cpp/third_party/simde-0.8.4-rc3/test/mips/msa/ld.c | 230 +
.../simde-0.8.4-rc3/test/mips/msa/madd.c | 177 +
.../simde-0.8.4-rc3/test/mips/msa/meson.build | 33 +
.../simde-0.8.4-rc3/test/mips/msa/run-tests-msa.h | 3 +
.../simde-0.8.4-rc3/test/mips/msa/run-tests.c | 30 +
.../simde-0.8.4-rc3/test/mips/msa/run-tests.h | 8 +
cpp/third_party/simde-0.8.4-rc3/test/mips/msa/st.c | 292 +
.../simde-0.8.4-rc3/test/mips/msa/subv.c | 295 +
.../test/mips/msa/test-msa-footer.h | 29 +
.../simde-0.8.4-rc3/test/mips/msa/test-msa.h | 161 +
.../simde-0.8.4-rc3/test/mips/run-tests.c | 48 +
.../simde-0.8.4-rc3/test/mips/run-tests.h | 35 +
.../simde-0.8.4-rc3/test/native-aliases.sh | 75 +
cpp/third_party/simde-0.8.4-rc3/test/run-tests.c | 23 +
cpp/third_party/simde-0.8.4-rc3/test/run-tests.h | 10 +
cpp/third_party/simde-0.8.4-rc3/test/test.h | 1243 +
.../simde-0.8.4-rc3/test/wasm/declare-suites.h | 2 +
.../simde-0.8.4-rc3/test/wasm/meson.build | 2 +
.../test/wasm/relaxed-simd/laneselect.c | 354 +
.../simde-0.8.4-rc3/test/wasm/relaxed-simd/madd.c | 186 +
.../simde-0.8.4-rc3/test/wasm/relaxed-simd/max.c | 149 +
.../test/wasm/relaxed-simd/meson.build | 41 +
.../simde-0.8.4-rc3/test/wasm/relaxed-simd/min.c | 149 +
.../simde-0.8.4-rc3/test/wasm/relaxed-simd/nmadd.c | 186 +
.../test/wasm/relaxed-simd/run-tests-wasm.h | 3 +
.../test/wasm/relaxed-simd/run-tests.c | 30 +
.../test/wasm/relaxed-simd/run-tests.h | 8 +
.../test/wasm/relaxed-simd/swizzle.c | 118 +
.../wasm/relaxed-simd/test-relaxed-simd-footer.h | 29 +
.../test/wasm/relaxed-simd/test-relaxed-simd.h | 12 +
.../simde-0.8.4-rc3/test/wasm/relaxed-simd/trunc.c | 226 +
.../simde-0.8.4-rc3/test/wasm/run-tests.c | 22 +
.../simde-0.8.4-rc3/test/wasm/run-tests.h | 10 +
.../simde-0.8.4-rc3/test/wasm/simd128/abs.c | 368 +
.../simde-0.8.4-rc3/test/wasm/simd128/add.c | 427 +
.../simde-0.8.4-rc3/test/wasm/simd128/add_sat.c | 322 +
.../simde-0.8.4-rc3/test/wasm/simd128/all_true.c | 263 +
.../simde-0.8.4-rc3/test/wasm/simd128/and.c | 91 +
.../simde-0.8.4-rc3/test/wasm/simd128/andnot.c | 91 +
.../simde-0.8.4-rc3/test/wasm/simd128/any_true.c | 96 +
.../simde-0.8.4-rc3/test/wasm/simd128/avgr.c | 178 +
.../simde-0.8.4-rc3/test/wasm/simd128/bitmask.c | 230 +
.../simde-0.8.4-rc3/test/wasm/simd128/bitselect.c | 103 +
.../simde-0.8.4-rc3/test/wasm/simd128/ceil.c | 138 +
.../simde-0.8.4-rc3/test/wasm/simd128/const.c | 617 +
.../simde-0.8.4-rc3/test/wasm/simd128/convert.c | 222 +
.../simde-0.8.4-rc3/test/wasm/simd128/demote.c | 86 +
.../simde-0.8.4-rc3/test/wasm/simd128/div.c | 162 +
.../simde-0.8.4-rc3/test/wasm/simd128/dot.c | 94 +
.../simde-0.8.4-rc3/test/wasm/simd128/eq.c | 450 +
.../test/wasm/simd128/extadd_pairwise.c | 242 +
.../test/wasm/simd128/extend_high.c | 338 +
.../simde-0.8.4-rc3/test/wasm/simd128/extend_low.c | 338 +
.../test/wasm/simd128/extmul_high.c | 426 +
.../simde-0.8.4-rc3/test/wasm/simd128/extmul_low.c | 426 +
.../test/wasm/simd128/extract_lane.c | 678 +
.../simde-0.8.4-rc3/test/wasm/simd128/floor.c | 138 +
.../simde-0.8.4-rc3/test/wasm/simd128/ge.c | 675 +
.../simde-0.8.4-rc3/test/wasm/simd128/gt.c | 675 +
.../simde-0.8.4-rc3/test/wasm/simd128/le.c | 675 +
.../simde-0.8.4-rc3/test/wasm/simd128/load.c | 316 +
.../simde-0.8.4-rc3/test/wasm/simd128/load_lane.c | 338 +
.../simde-0.8.4-rc3/test/wasm/simd128/load_splat.c | 226 +
.../simde-0.8.4-rc3/test/wasm/simd128/load_zero.c | 126 +
.../simde-0.8.4-rc3/test/wasm/simd128/lt.c | 675 +
.../simde-0.8.4-rc3/test/wasm/simd128/make.c | 569 +
.../simde-0.8.4-rc3/test/wasm/simd128/max.c | 576 +
.../simde-0.8.4-rc3/test/wasm/simd128/meson.build | 94 +
.../simde-0.8.4-rc3/test/wasm/simd128/min.c | 576 +
.../simde-0.8.4-rc3/test/wasm/simd128/mul.c | 342 +
.../simde-0.8.4-rc3/test/wasm/simd128/narrow.c | 298 +
.../simde-0.8.4-rc3/test/wasm/simd128/ne.c | 450 +
.../simde-0.8.4-rc3/test/wasm/simd128/nearest.c | 146 +
.../simde-0.8.4-rc3/test/wasm/simd128/neg.c | 360 +
.../simde-0.8.4-rc3/test/wasm/simd128/not.c | 79 +
.../simde-0.8.4-rc3/test/wasm/simd128/or.c | 91 +
.../simde-0.8.4-rc3/test/wasm/simd128/pmax.c | 162 +
.../simde-0.8.4-rc3/test/wasm/simd128/pmin.c | 162 +
.../simde-0.8.4-rc3/test/wasm/simd128/popcnt.c | 106 +
.../simde-0.8.4-rc3/test/wasm/simd128/promote.c | 86 +
.../test/wasm/simd128/q15mulr_sat.c | 97 +
.../test/wasm/simd128/replace_lane.c | 392 +
.../test/wasm/simd128/run-tests-wasm.h | 3 +
.../simde-0.8.4-rc3/test/wasm/simd128/run-tests.c | 30 +
.../simde-0.8.4-rc3/test/wasm/simd128/run-tests.h | 8 +
.../simde-0.8.4-rc3/test/wasm/simd128/shl.c | 296 +
.../simde-0.8.4-rc3/test/wasm/simd128/shr.c | 558 +
.../simde-0.8.4-rc3/test/wasm/simd128/shuffle.c | 390 +
.../simde-0.8.4-rc3/test/wasm/simd128/splat.c | 510 +
.../simde-0.8.4-rc3/test/wasm/simd128/sqrt.c | 140 +
.../simde-0.8.4-rc3/test/wasm/simd128/store_lane.c | 282 +
.../simde-0.8.4-rc3/test/wasm/simd128/sub.c | 426 +
.../simde-0.8.4-rc3/test/wasm/simd128/sub_sat.c | 322 +
.../simde-0.8.4-rc3/test/wasm/simd128/swizzle.c | 118 +
.../test/wasm/simd128/test-simd128-footer.h | 29 +
.../test/wasm/simd128/test-simd128.h | 12 +
.../simde-0.8.4-rc3/test/wasm/simd128/trunc.c | 138 +
.../simde-0.8.4-rc3/test/wasm/simd128/trunc_sat.c | 270 +
.../simde-0.8.4-rc3/test/wasm/simd128/xor.c | 91 +
.../test/wasm/test-simd128-footer.h | 29 +
.../simde-0.8.4-rc3/test/wasm/test-simd128.h | 151 +
cpp/third_party/simde-0.8.4-rc3/test/x86/aes.c | 358 +
cpp/third_party/simde-0.8.4-rc3/test/x86/avx.c | 16836 ++++++++++
cpp/third_party/simde-0.8.4-rc3/test/x86/avx2.c | 17006 ++++++++++
.../simde-0.8.4-rc3/test/x86/avx512/2intersect.c | 405 +
.../simde-0.8.4-rc3/test/x86/avx512/4dpwssd.c | 519 +
.../simde-0.8.4-rc3/test/x86/avx512/4dpwssds.c | 519 +
.../simde-0.8.4-rc3/test/x86/avx512/abs.c | 3529 +++
.../simde-0.8.4-rc3/test/x86/avx512/add.c | 4490 +++
.../simde-0.8.4-rc3/test/x86/avx512/adds.c | 5553 ++++
.../simde-0.8.4-rc3/test/x86/avx512/and.c | 3008 ++
.../simde-0.8.4-rc3/test/x86/avx512/andnot.c | 1727 +
.../simde-0.8.4-rc3/test/x86/avx512/avg.c | 2630 ++
.../simde-0.8.4-rc3/test/x86/avx512/bitshuffle.c | 449 +
.../simde-0.8.4-rc3/test/x86/avx512/blend.c | 1881 ++
.../simde-0.8.4-rc3/test/x86/avx512/broadcast.c | 4031 +++
.../simde-0.8.4-rc3/test/x86/avx512/cast.c | 1531 +
.../simde-0.8.4-rc3/test/x86/avx512/cmp.c | 5994 ++++
.../simde-0.8.4-rc3/test/x86/avx512/cmpeq.c | 4718 +++
.../simde-0.8.4-rc3/test/x86/avx512/cmpge.c | 7068 +++++
.../simde-0.8.4-rc3/test/x86/avx512/cmpgt.c | 1085 +
.../simde-0.8.4-rc3/test/x86/avx512/cmple.c | 7067 +++++
.../simde-0.8.4-rc3/test/x86/avx512/cmplt.c | 757 +
.../simde-0.8.4-rc3/test/x86/avx512/cmpneq.c | 4574 +++
.../simde-0.8.4-rc3/test/x86/avx512/compress.c | 2757 ++
.../simde-0.8.4-rc3/test/x86/avx512/conflict.c | 1287 +
.../simde-0.8.4-rc3/test/x86/avx512/copysign.c | 247 +
.../simde-0.8.4-rc3/test/x86/avx512/cvt.c | 1365 +
.../simde-0.8.4-rc3/test/x86/avx512/cvts.c | 2608 ++
.../simde-0.8.4-rc3/test/x86/avx512/cvtt.c | 294 +
.../simde-0.8.4-rc3/test/x86/avx512/cvtus.c | 165 +
.../simde-0.8.4-rc3/test/x86/avx512/dbsad.c | 1760 ++
.../simde-0.8.4-rc3/test/x86/avx512/div.c | 824 +
.../simde-0.8.4-rc3/test/x86/avx512/dpbf16.c | 1230 +
.../simde-0.8.4-rc3/test/x86/avx512/dpbusd.c | 829 +
.../simde-0.8.4-rc3/test/x86/avx512/dpbusds.c | 829 +
.../simde-0.8.4-rc3/test/x86/avx512/dpwssd.c | 829 +
.../simde-0.8.4-rc3/test/x86/avx512/dpwssds.c | 993 +
.../simde-0.8.4-rc3/test/x86/avx512/expand.c | 3231 ++
.../simde-0.8.4-rc3/test/x86/avx512/expandloadu.c | 2654 ++
.../simde-0.8.4-rc3/test/x86/avx512/extract.c | 1625 +
.../simde-0.8.4-rc3/test/x86/avx512/fixupimm.c | 3787 +++
.../test/x86/avx512/fixupimm_round.c | 2179 ++
.../test/x86/avx512/flushsubnormal.c | 178 +
.../simde-0.8.4-rc3/test/x86/avx512/fmadd.c | 595 +
.../simde-0.8.4-rc3/test/x86/avx512/fmaddsub.c | 457 +
.../simde-0.8.4-rc3/test/x86/avx512/fmsub.c | 1351 +
.../simde-0.8.4-rc3/test/x86/avx512/fnmadd.c | 276 +
.../simde-0.8.4-rc3/test/x86/avx512/fnmsub.c | 276 +
.../simde-0.8.4-rc3/test/x86/avx512/fpclass.c | 230 +
.../simde-0.8.4-rc3/test/x86/avx512/gather.c | 834 +
.../simde-0.8.4-rc3/test/x86/avx512/insert.c | 3050 ++
.../simde-0.8.4-rc3/test/x86/avx512/kand.c | 91 +
.../simde-0.8.4-rc3/test/x86/avx512/knot.c | 260 +
.../simde-0.8.4-rc3/test/x86/avx512/kshift.c | 524 +
.../simde-0.8.4-rc3/test/x86/avx512/kxor.c | 316 +
.../simde-0.8.4-rc3/test/x86/avx512/load.c | 554 +
.../simde-0.8.4-rc3/test/x86/avx512/loadu.c | 3855 +++
.../simde-0.8.4-rc3/test/x86/avx512/lzcnt.c | 1137 +
.../simde-0.8.4-rc3/test/x86/avx512/madd.c | 692 +
.../simde-0.8.4-rc3/test/x86/avx512/maddubs.c | 1130 +
.../simde-0.8.4-rc3/test/x86/avx512/max.c | 4119 +++
.../simde-0.8.4-rc3/test/x86/avx512/meson.build | 30 +
.../simde-0.8.4-rc3/test/x86/avx512/min.c | 4119 +++
.../simde-0.8.4-rc3/test/x86/avx512/mov.c | 3875 +++
.../simde-0.8.4-rc3/test/x86/avx512/mov_mask.c | 801 +
.../simde-0.8.4-rc3/test/x86/avx512/movm.c | 787 +
.../simde-0.8.4-rc3/test/x86/avx512/mul.c | 1564 +
.../simde-0.8.4-rc3/test/x86/avx512/mulhi.c | 152 +
.../simde-0.8.4-rc3/test/x86/avx512/mulhrs.c | 156 +
.../simde-0.8.4-rc3/test/x86/avx512/mullo.c | 681 +
.../simde-0.8.4-rc3/test/x86/avx512/multishift.c | 1299 +
.../simde-0.8.4-rc3/test/x86/avx512/negate.c | 192 +
.../simde-0.8.4-rc3/test/x86/avx512/or.c | 1248 +
.../simde-0.8.4-rc3/test/x86/avx512/packs.c | 268 +
.../simde-0.8.4-rc3/test/x86/avx512/packus.c | 268 +
.../simde-0.8.4-rc3/test/x86/avx512/permutex.c | 258 +
.../simde-0.8.4-rc3/test/x86/avx512/permutex2var.c | 8916 ++++++
.../simde-0.8.4-rc3/test/x86/avx512/permutexvar.c | 5157 +++
.../simde-0.8.4-rc3/test/x86/avx512/popcnt.c | 3055 ++
.../simde-0.8.4-rc3/test/x86/avx512/range.c | 3016 ++
.../simde-0.8.4-rc3/test/x86/avx512/range_round.c | 1919 ++
.../simde-0.8.4-rc3/test/x86/avx512/rcp.c | 135 +
.../simde-0.8.4-rc3/test/x86/avx512/reduce.c | 938 +
.../simde-0.8.4-rc3/test/x86/avx512/rol.c | 2461 ++
.../simde-0.8.4-rc3/test/x86/avx512/rolv.c | 1445 +
.../simde-0.8.4-rc3/test/x86/avx512/ror.c | 2988 ++
.../simde-0.8.4-rc3/test/x86/avx512/rorv.c | 1447 +
.../simde-0.8.4-rc3/test/x86/avx512/round.c | 383 +
.../simde-0.8.4-rc3/test/x86/avx512/roundscale.c | 6811 ++++
.../test/x86/avx512/roundscale_round.c | 1873 ++
.../simde-0.8.4-rc3/test/x86/avx512/run-tests.c | 30 +
.../simde-0.8.4-rc3/test/x86/avx512/run-tests.h | 8 +
.../simde-0.8.4-rc3/test/x86/avx512/sad.c | 200 +
.../simde-0.8.4-rc3/test/x86/avx512/scalef.c | 2216 ++
.../simde-0.8.4-rc3/test/x86/avx512/set.c | 1456 +
.../simde-0.8.4-rc3/test/x86/avx512/set1.c | 1668 +
.../simde-0.8.4-rc3/test/x86/avx512/set4.c | 361 +
.../simde-0.8.4-rc3/test/x86/avx512/setone.c | 45 +
.../simde-0.8.4-rc3/test/x86/avx512/setr.c | 625 +
.../simde-0.8.4-rc3/test/x86/avx512/setr4.c | 361 +
.../simde-0.8.4-rc3/test/x86/avx512/setzero.c | 71 +
.../simde-0.8.4-rc3/test/x86/avx512/shldv.c | 105 +
.../simde-0.8.4-rc3/test/x86/avx512/shuffle.c | 4544 +++
.../simde-0.8.4-rc3/test/x86/avx512/skel.c | 35 +
.../simde-0.8.4-rc3/test/x86/avx512/sll.c | 798 +
.../simde-0.8.4-rc3/test/x86/avx512/slli.c | 1795 ++
.../simde-0.8.4-rc3/test/x86/avx512/sllv.c | 334 +
.../simde-0.8.4-rc3/test/x86/avx512/sqrt.c | 403 +
.../simde-0.8.4-rc3/test/x86/avx512/sra.c | 128 +
.../simde-0.8.4-rc3/test/x86/avx512/srai.c | 404 +
.../simde-0.8.4-rc3/test/x86/avx512/srav.c | 152 +
.../simde-0.8.4-rc3/test/x86/avx512/srl.c | 561 +
.../simde-0.8.4-rc3/test/x86/avx512/srli.c | 1649 +
.../simde-0.8.4-rc3/test/x86/avx512/srlv.c | 1010 +
.../simde-0.8.4-rc3/test/x86/avx512/store.c | 131 +
.../simde-0.8.4-rc3/test/x86/avx512/storeu.c | 2330 ++
.../simde-0.8.4-rc3/test/x86/avx512/sub.c | 3328 ++
.../simde-0.8.4-rc3/test/x86/avx512/subs.c | 3166 ++
.../simde-0.8.4-rc3/test/x86/avx512/ternarylogic.c | 6412 ++++
.../test/x86/avx512/test-avx512-footer.h | 29 +
.../simde-0.8.4-rc3/test/x86/avx512/test-avx512.h | 65 +
.../simde-0.8.4-rc3/test/x86/avx512/test.c | 1147 +
.../simde-0.8.4-rc3/test/x86/avx512/testn.c | 123 +
.../simde-0.8.4-rc3/test/x86/avx512/unpackhi.c | 4441 +++
.../simde-0.8.4-rc3/test/x86/avx512/unpacklo.c | 4689 +++
.../simde-0.8.4-rc3/test/x86/avx512/xor.c | 1180 +
.../simde-0.8.4-rc3/test/x86/avx512/xorsign.c | 247 +
cpp/third_party/simde-0.8.4-rc3/test/x86/bmi.c | 394 +
cpp/third_party/simde-0.8.4-rc3/test/x86/clmul.c | 391 +
cpp/third_party/simde-0.8.4-rc3/test/x86/f16c.c | 527 +
cpp/third_party/simde-0.8.4-rc3/test/x86/fma.c | 2439 ++
cpp/third_party/simde-0.8.4-rc3/test/x86/gfni.c | 7428 +++++
.../simde-0.8.4-rc3/test/x86/meson.build | 53 +
cpp/third_party/simde-0.8.4-rc3/test/x86/mmx.c | 5315 ++++
.../simde-0.8.4-rc3/test/x86/run-tests.c | 32 +
.../simde-0.8.4-rc3/test/x86/run-tests.h | 9 +
cpp/third_party/simde-0.8.4-rc3/test/x86/skel.c | 2274 ++
cpp/third_party/simde-0.8.4-rc3/test/x86/sse.c | 5988 ++++
cpp/third_party/simde-0.8.4-rc3/test/x86/sse2.c | 10919 +++++++
cpp/third_party/simde-0.8.4-rc3/test/x86/sse3.c | 813 +
cpp/third_party/simde-0.8.4-rc3/test/x86/sse4.1.c | 3743 +++
cpp/third_party/simde-0.8.4-rc3/test/x86/sse4.2.c | 3479 ++
cpp/third_party/simde-0.8.4-rc3/test/x86/ssse3.c | 1927 ++
cpp/third_party/simde-0.8.4-rc3/test/x86/svml.c | 31376 +++++++++++++++++++
.../simde-0.8.4-rc3/test/x86/test-avx.h | 305 +
.../simde-0.8.4-rc3/test/x86/test-mmx.h | 35 +
.../simde-0.8.4-rc3/test/x86/test-sse.h | 17 +
.../simde-0.8.4-rc3/test/x86/test-sse2.h | 33 +
.../simde-0.8.4-rc3/test/x86/test-x86-footer.h | 29 +
.../simde-0.8.4-rc3/test/x86/test-x86.h | 188 +
cpp/third_party/simde-0.8.4-rc3/test/x86/xop.c | 7450 +++++
1491 files changed, 985281 insertions(+), 153 deletions(-)
diff --git a/cpp/.gitignore b/cpp/.gitignore
index 3dc8c5baf..7f1e770ef 100644
--- a/cpp/.gitignore
+++ b/cpp/.gitignore
@@ -5,3 +5,4 @@ build/
.settings/
.vscode/
cmake-build-debug/
+third_party/simde-0.8.4-rc3
diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt
index 0774812d8..c85150d8f 100755
--- a/cpp/CMakeLists.txt
+++ b/cpp/CMakeLists.txt
@@ -1,4 +1,4 @@
-#[[
+#[[
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
@@ -58,13 +58,6 @@ if (${COV_ENABLED})
message("add_definitions -DCOV_ENABLED=1")
endif ()
-option(ENABLE_MEM_STAT "Enable memory status" ON)
-
-if (ENABLE_MEM_STAT)
- add_definitions(-DENABLE_MEM_STAT)
- message("add_definitions -DENABLE_MEM_STAT")
-endif ()
-
if (NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build." FORCE)
diff --git a/cpp/docs/design/mem-constrained-writer/design.md b/cpp/docs/design/mem-constrained-writer/design.md
new file mode 100644
index 000000000..f7c85dfb8
--- /dev/null
+++ b/cpp/docs/design/mem-constrained-writer/design.md
@@ -0,0 +1,398 @@
+# MemConstrainedWriter 设计文档
+
+## 1. 背景与需求
+
+### 1.1 问题描述
+
+TsFile C++ 在资源受限环境(嵌入式设备、容器限额、边缘计算节点)中运行时,用户面临以下困境:
+
+- 不知道每次应写入多少行(`max_rows`)
+- 不知道写入多少次后需要调用 `flush()`
+- 不知道何时内存将超出上限,需要 `close()` 当前文件并打开新文件
+
+现有的 `TsFileWriter` / `TsFileTableWriter` 将这三个决策完全交给用户,用户需要深入理解 TsFile 内存模型才能正确配置。
+
+### 1.2 内存组成分析
+
+实验(见 `experiment/TsFile 内存控制.pdf`)确认了写入过程的内存由三部分构成:
+
+```
+M_total = M_init + M_data(F) + M_meta(K)
+```
+
+| 组成 | 特征 | 公式 |
+|------|------|------|
+| M_init | 固定,程序启动后不变 | ≈ 900 KB |
+| M_data | 周期性,flush 后释放 | `s × F`(字节) |
+| M_meta | 单调递增,close 才写盘 | `K × D × b`(字节) |
+
+其中:
+
+- `F`:每次 flush 写入的行数
+- `K`:当前文件已 flush 的次数
+- `D`:当前文件已出现的设备数(唯一 TAG 组合数)
+- `s = s_tablet + s_data`:每行在内存中的综合字节数
+- `b`:每设备每次 flush 新增的元数据字节数
+
+对于 Table 模式,各量的计算方式:
+
+```
+s_tablet = 8 + Σ sizeof(col_type) // 所有列(TAG + FIELD)
+s_data = Σ (8 + sizeof(field_col_type)) // 仅 FIELD 列进 ChunkWriter
+s = s_tablet + s_data
+
+b = n_field_cols × 104 + 96 // 每设备每次 flush 的 chunk_meta + chunk_group_meta
+```
+
+### 1.3 内存约束下的最优策略
+
+在给定内存上限 `M_limit` 和写入规模(设备数 `D`,总行数 `R`)的情况下:
+
+**总量已知时**(EOQ 模型最优解):
+
+```
+F_opt = √(R × D × b / s)
+K_opt = R / F_opt
+M_min = M_init + 2 × √(R × s × D × b)
+```
+
+当 `M_min ≤ M_limit` 时,F_opt 即为最优批次大小,所有数据可写入单个文件。
+
+**总量未知时**(流式,预算对半分):
+
+```
+M_avail = M_limit - M_init
+F = M_avail / (2 × s) // 数据预算
+K = M_avail / (2 × D × b) // 元数据预算
+```
+
+此时 F × K 最大(单个文件容纳的数据量最多),是未知总量场景下的最优策略。
+
+---
+
+## 2. 设计目标
+
+1. 用户只需提供内存上限和 schema,不需要手动计算 F 和 K。
+2. 用户只需调用 `write_table()`,flush / close / reopen 全部自动处理。
+3. 当设备数未知时,通过动态追踪自适应调整,保证内存不超出上限。
+4. 接口风格与现有 `TsFileTableWriter` 保持一致,便于迁移。
+5. 零运行时内存监控开销:不依赖 `ModStat`,仅使用轻量的整数计算。
+
+---
+
+## 3. 关于内存监控方式的取舍
+
+### 3.1 ModStat 方案(已排除)
+
+`ModStat` 通过在每次 `mem_alloc` / `mem_free` 时执行一次 `ATOMIC_FAA` 来追踪各模块的内存占用。
+
+排除原因:
+- 写入热路径(数据页分配、ByteStream 扩容)中 `mem_alloc` 调用频繁
+- `ATOMIC_FAA` 在弱内存模型的嵌入式 CPU 上代价不可忽略
+- `ModStat` 用于诊断目的,仅在 `ENABLE_MEM_STAT` 编译选项下开启
+- `MemConstrainedWriter` 不需要字节精度,公式估算(5-15% 误差)+ 安全裕度已足够
+
+### 3.2 公式估算方案(采用)
+
+利用公式 `M_meta ≈ flush_count × D × b` 进行轻量估算:
+
+- **F** 在构造时由公式一次性计算,之后固定不变
+- **rotate 判断** 在每次 `flush()` 后执行一次整数乘法,开销可忽略
+- **D(设备数)** 通过 `Tablet::find_all_device_boundaries()` 高效获取
+
+`find_all_device_boundaries()` 按列进行逐元比较(非按行遍历),返回每个设备在 Tablet 中的行范围边界。`boundaries.size() - 1` 即为本次 Tablet 涉及的设备数。
+
+### 3.3 安全裕度
+
+由于公式存在 5-15% 的估算误差(主要来源:变长字符串开销、schema 结构的 list/map 节点),rotate 触发条件设置为:
+
+```
+if meta_estimate >= meta_budget × SAFETY_FACTOR: // SAFETY_FACTOR = 0.85
+ rotate_file()
+```
+
+提前 15% 触发 rotate,保证实际内存不超出上限。
+
+---
+
+## 4. 接口设计
+
+### 4.1 数据结构
+
+```cpp
+struct WritingStrategy {
+ int64_t rows_per_tablet; // 建议的 Tablet 行数 (F)
+ int64_t flushes_per_file; // 每个文件最多 flush 次数 (K),-1 = 不限
+ int64_t estimated_peak_mem; // 按此策略的预估峰值内存(bytes)
+};
+```
+
+### 4.2 类接口
+
+```cpp
+class MemConstrainedWriter {
+ public:
+ // n_devices = DEVICES_UNKNOWN(0) 时进入动态追踪模式
+ static constexpr int DEVICES_UNKNOWN = 0;
+
+ MemConstrainedWriter(
+ int64_t mem_limit_bytes, // 内存上限
+ const std::string& output_dir, // 输出目录
+ const std::string& file_prefix, // 文件名前缀
+ const std::shared_ptr<TableSchema>& table_schema,
+ int n_devices = DEVICES_UNKNOWN, // 设备数,0 = 未知
+ int64_t total_rows_per_device = -1 // 每设备总行数,-1 = 未知
+ );
+
+ ~MemConstrainedWriter();
+
+ // 写入接口(内部自动 flush / close / reopen)
+ int write_table(Tablet& tablet);
+
+ // 完成写入后调用
+ int close();
+
+ // 查询建议的 Tablet 行数,用于创建 Tablet
+ int64_t rows_per_tablet() const;
+
+ // 查询当前生效的写入策略
+ const WritingStrategy& strategy() const;
+
+ // 本次写入创建的所有文件路径
+ const std::vector<std::string>& created_files() const;
+
+ // 纯计算接口:不创建 writer,只返回建议策略
+ static WritingStrategy compute_strategy(
+ int64_t mem_limit_bytes,
+ const std::shared_ptr<TableSchema>& table_schema,
+ int n_devices,
+ int64_t total_rows_per_device = -1
+ );
+};
+```
+
+### 4.3 文件命名规则
+
+输出文件按 `{output_dir}/{file_prefix}_{index:04d}.tsfile` 命名:
+
+```
+/data/output/sensor_0000.tsfile
+/data/output/sensor_0001.tsfile
+```
+
+### 4.4 用户代码示例
+
+```cpp
+auto schema = std::make_shared<TableSchema>("sensor_table", columns);
+
+MemConstrainedWriter writer(
+ 10 * 1024 * 1024, // 10 MB 内存上限
+ "/data/output",
+ "sensor",
+ schema,
+ 50, // 50 个设备(已知时填写,未知时省略)
+ 100000 // 每设备总行数(可选)
+);
+
+int F = writer.rows_per_tablet();
+
+while (has_more_data) {
+ Tablet tablet(..., F);
+ fill(tablet);
+ writer.write_table(tablet); // flush / rotate 全自动
+}
+writer.close();
+
+for (const auto& path : writer.created_files()) {
+ // /data/output/sensor_0000.tsfile ...
+}
+```
+
+---
+
+## 5. 内部实现
+
+### 5.1 模块结构
+
+```
+src/writer/
+├── tsfile_writer.h/.cc (已有)
+├── tsfile_table_writer.h/.cc (已有)
+├── mem_constrained_writer.h (新增)
+└── mem_constrained_writer.cc (新增)
+```
+
+`MemConstrainedWriter` 内部持有一个 `TsFileWriter*`,在文件轮换时销毁并重建。直接包装 `TsFileWriter` 而非 `TsFileTableWriter`,以便在 rotate 时精确控制 `open()` 和 `register_table()`。
+
+### 5.2 内部状态
+
+```cpp
+// 固定参数(构造时确定,之后不变)
+int64_t mem_limit_bytes_;
+int64_t s_; // 每行综合字节数 = s_tablet + s_data
+int64_t b_per_device_; // 每设备每 flush 的元数据增量(字节)
+int64_t meta_budget_; // 分给元数据的内存预算 = M_avail - s_ × F
+WritingStrategy strategy_; // rows_per_tablet 固定
+
+// 运行时状态
+TsFileWriter* writer_;
+std::shared_ptr<TableSchema> table_schema_;
+int64_t flush_count_; // 当前文件已 flush 次数
+int file_index_; // 当前文件编号
+std::vector<std::string> created_files_;
+
+// 动态设备追踪(n_devices == DEVICES_UNKNOWN 时使用)
+bool dynamic_mode_;
+int64_t observed_max_devices_; // 单次 Tablet 内见过的最多设备数
+int64_t current_k_; // 动态计算的 K 值
+```
+
+### 5.3 write_table() 流程
+
+```
+write_table(tablet):
+ 1. writer_->write_table(tablet)
+ 2. writer_->flush()
+ 3. flush_count_++
+
+ 4. if dynamic_mode_:
+ d_in_tablet = tablet.find_all_device_boundaries().size() - 1
+ if d_in_tablet > observed_max_devices_:
+ observed_max_devices_ = d_in_tablet
+ recompute_k() // D 增大 → K 减小
+
+ 5. effective_k = dynamic_mode_ ? current_k_ : strategy_.flushes_per_file
+ meta_estimate = flush_count_ × D_effective × b_per_device_
+ if effective_k > 0 && meta_estimate >= meta_budget_ × SAFETY_FACTOR:
+ rotate_file()
+```
+
+> **为何同时保留 flush_count 判断和 meta_estimate 判断:**
+> `meta_estimate` 是主要判断条件(基于实际累积量),`effective_k` 作为硬上限保底,
+> 防止极端情况下(如估算偏差较大)超出预算。
+
+### 5.4 rotate_file() 流程
+
+```
+rotate_file():
+ 1. writer_->close()
+ 2. delete writer_
+ 3. file_index_++
+ 4. writer_ = new TsFileWriter()
+ 5. writer_->open(make_file_path(file_index_))
+ 6. writer_->register_table(table_schema_)
+ 7. created_files_.push_back(current path)
+ 8. flush_count_ = 0
+ 9. if dynamic_mode_: observed_max_devices_ = 0; recompute_k()
+```
+
+### 5.5 动态设备追踪
+
+```
+// 每次 write_table 后(仅在 dynamic_mode_ 时)
+d_in_tablet = tablet.find_all_device_boundaries().size() - 1
+
+// observed_max_devices_ 跟踪单个 Tablet 内见过的最多设备数
+// (rotate 后重置,重新从当前 Tablet 学习)
+if d_in_tablet > observed_max_devices_:
+ observed_max_devices_ = d_in_tablet
+ recompute_k()
+
+recompute_k():
+ D = observed_max_devices_
+ B = D × b_per_device_
+ current_k_ = max(1, meta_budget_ / B)
+```
+
+**动态模式的保守性来源**:`observed_max_devices_` 只增不减(单文件内),设备数越多 K 越小。安全裕度(×0.85)进一步确保估算偏低时不会超出上限。
+
+### 5.6 compute_strategy() 计算逻辑
+
+```
+inputs: mem_limit, schema, n_devices, total_rows (-1 = 未知)
+
+s_tablet = 8 + Σ sizeof(col_type) // 全部列(TAG + FIELD)
+s_data = Σ (8 + sizeof(field_col_type)) // 仅 FIELD 列
+s = s_tablet + s_data
+
+n_field_cols = count(FIELD columns in schema)
+b = n_field_cols × 104 + 96 // 每设备每 flush 的元数据
+
+M_avail = mem_limit - M_INIT_BYTES // M_INIT_BYTES = 900 KB
+
+if total_rows > 0 && n_devices > 0: // 总量已知:EOQ 最优
+ B = n_devices × b
+ F = √(total_rows × B / s)
+ K = total_rows / F
+ peak = M_INIT_BYTES + s×F + K×B
+ if peak ≤ mem_limit: return {F, K, peak}
+ // 超出上限,退化为对半分
+
+// 总量未知,或 EOQ 超限:对半分预算
+B = max(1, n_devices) × b // n_devices=0 时用 1 作为初始值
+F = M_avail / (2 × s)
+K = M_avail / (2 × B)
+peak = M_INIT_BYTES + s×F + K×B
+return {F, K, peak}
+```
+
+`sizeof_datatype()` 的映射:
+
+| TSDataType | 字节数 |
+|-----------|-------|
+| BOOLEAN | 1 |
+| INT32 / FLOAT | 4 |
+| INT64 / DOUBLE | 8 |
+| STRING | 32(保守估计,实际为变长)|
+
+---
+
+## 6. 两种模式行为对比
+
+以实验配置(50 设备,50 FIELD 列 int32,M_limit=12MB)为例:
+
+```
+s = 208 + 600 = 808 bytes/row
+b = 50×104 + 96 = 5296 bytes/设备/flush
+B = 50×5296 = 264,800 bytes/flush
+
+M_avail ≈ 11.5MB
+
+策略(对半分):F ≈ 7,500 行,K ≈ 22 次
+```
+
+**n_devices 已知(=50):**
+
+| flush 轮次 | meta_estimate | meta_budget×0.85 | 是否 rotate |
+|-----------|--------------|-----------------|------------|
+| 第 10 次 | 10×264800 = 2.5MB | 4.9MB | 否 |
+| 第 19 次 | 19×264800 = 4.8MB | 4.9MB | **是** |
+
+**n_devices 未知(动态追踪,每次 Tablet 含 50 设备):**
+
+| flush 轮次 | observed_D | K 重算 | meta_estimate | 是否 rotate |
+|-----------|-----------|-------|--------------|------------|
+| 第 1 次 | 50(首次发现即稳定)| 22 | 0.26MB | 否 |
+| 第 19 次 | 50 | 22 | 4.8MB | **是** |
+
+两种模式在设备数稳定后行为一致。
+
+---
+
+## 7. 约束与局限
+
+1. **STRING 列的内存估计误差**:STRING 类型使用固定 32 字节估算,实际为变长。通过 SAFETY_FACTOR=0.85 和 M_INIT_BYTES 的保守取值缓解此误差。
+
+2. **每次 write_table 后立即 flush**:当前设计在每次 `write_table` 后强制 flush。此行为与实验场景一致(每个 Tablet 写完即落盘),是内存受限场景的推荐模式。
+
+3. **Schema 同构假设**:`compute_strategy()` 假设所有设备使用相同的 FIELD 列结构。异构 schema 场景下应取最大 FIELD 列数作为保守估计。
+
+4. **单线程写入**:当前设计不考虑并发写入场景。
+
+---
+
+## 8. 待确认事项
+
+- [ ] `M_INIT_BYTES` 取值:实验测得约 900 KB,是否随注册的时间序列数量显著变化。
+- [ ] `b = n_field_cols × 104 + 96` 的精度:104 bytes/chunk_meta 和 96 bytes/chunk_group_meta 为近似值,需根据实际序列化大小校准。
+- [ ] `find_all_device_boundaries()` 返回值语义确认:`boundaries.size() - 1` 等于本次 Tablet 中唯一设备数。
diff --git a/cpp/examples/CMakeLists.txt b/cpp/examples/CMakeLists.txt
index ebe6c66c8..15206877a 100644
--- a/cpp/examples/CMakeLists.txt
+++ b/cpp/examples/CMakeLists.txt
@@ -10,7 +10,7 @@ with the License. You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
-software distributed under the License is distributed on an
+software distributed under the LICENSE is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
@@ -18,31 +18,29 @@ under the License.
]]
cmake_minimum_required(VERSION 3.10)
project(examples)
-message("Running in exampes directory")
+message("Running in examples directory")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
-# TsFile include dir
+# TsFile include dirs
set(SDK_INCLUDE_DIR ${PROJECT_SOURCE_DIR}/../src/)
-message("SDK_INCLUDE_DIR: ${SDK_INCLUDE_DIR}")
-
-# TsFile shared object dir
-set(SDK_LIB_DIR_RELEASE ${PROJECT_SOURCE_DIR}/../build/Release/lib)
-message("SDK_LIB_DIR_RELEASE: ${SDK_LIB_DIR_RELEASE}")
-
-set(SDK_LIB_DIR_DEBUG ${PROJECT_SOURCE_DIR}/../build/Debug/lib)
-message("SDK_LIB_DIR_DEBUG: ${SDK_LIB_DIR_DEBUG}")
+include_directories(${SDK_INCLUDE_DIR})
include_directories(${PROJECT_SOURCE_DIR}/../third_party/antlr4-cpp-runtime-4/runtime/src)
-set(BUILD_TYPE "Release")
-include_directories(${SDK_INCLUDE_DIR})
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3 -DNDEBUG")
-set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O0 -g")
-set(CMAKE_CXX_FLAGS_DEBUG" ${CMAKE_CXX_FLAGS} -O0 -g")
+# Arrow + Parquet are required (for bench_read)
+if(APPLE)
+ list(APPEND CMAKE_PREFIX_PATH
+ "/opt/homebrew/opt/apache-arrow/lib/cmake"
+ "/usr/local/opt/apache-arrow/lib/cmake")
+endif()
+find_package(Arrow CONFIG REQUIRED)
+find_package(Parquet CONFIG REQUIRED)
add_subdirectory(cpp_examples)
add_subdirectory(c_examples)
add_executable(examples examples.cc)
target_link_libraries(examples cpp_examples_obj c_examples_obj)
-target_link_libraries(examples tsfile)
\ No newline at end of file
+target_link_libraries(examples tsfile Arrow::arrow_shared Parquet::parquet_shared)
diff --git a/cpp/examples/cpp_examples/CMakeLists.txt b/cpp/examples/cpp_examples/CMakeLists.txt
index a2ac8d435..f7215c948 100644
--- a/cpp/examples/cpp_examples/CMakeLists.txt
+++ b/cpp/examples/cpp_examples/CMakeLists.txt
@@ -18,5 +18,17 @@ under the License.
]]
message("Running in examples/cpp_examples directory")
-aux_source_directory(. cpp_SRC_LIST)
-add_library(cpp_examples_obj OBJECT ${cpp_SRC_LIST})
+
+add_library(cpp_examples_obj OBJECT
+ demo_read.cpp
+ demo_write.cpp
+ bench_read.cpp)
+
+# bench_read.cpp requires C++17 (TsFile headers use [[maybe_unused]])
+# and Arrow/Parquet headers. Both are provided by the parent scope.
+set_target_properties(cpp_examples_obj PROPERTIES
+ CXX_STANDARD 17 CXX_STANDARD_REQUIRED ON)
+target_compile_options(cpp_examples_obj PRIVATE -std=c++17)
+target_link_libraries(cpp_examples_obj PRIVATE
+ Arrow::arrow_shared
+ Parquet::parquet_shared)
diff --git a/cpp/examples/cpp_examples/bench_read.cpp b/cpp/examples/cpp_examples/bench_read.cpp
new file mode 100644
index 000000000..92f9e5188
--- /dev/null
+++ b/cpp/examples/cpp_examples/bench_read.cpp
@@ -0,0 +1,641 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * License); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include "bench_read.h"
+
+#include <fcntl.h>
+#include <sys/stat.h>
+
+#include <chrono>
+#include <iomanip>
+#include <iostream>
+#include <memory>
+#include <numeric>
+#include <string>
+#include <vector>
+
+#include <arrow/api.h>
+#include <arrow/io/api.h>
+#include <parquet/arrow/reader.h>
+#include <parquet/arrow/writer.h>
+#include <parquet/metadata.h>
+#include <parquet/properties.h>
+#include <parquet/statistics.h>
+
+#include "common/schema.h"
+#include "common/tablet.h"
+#include "common/tsblock/tsblock.h"
+#include "common/tsblock/vector/fixed_length_vector.h"
+#include "common/tsblock/vector/vector.h"
+#include "file/write_file.h"
+#include "reader/filter/tag_filter.h"
+#include "reader/result_set.h"
+#include "reader/table_result_set.h"
+#include "reader/tsfile_reader.h"
+#include "utils/util_define.h"
+#include "writer/tsfile_table_writer.h"
+
+#define BENCH_HANDLE_ERROR(err_no) \
+ do { \
+ if ((err_no) != 0) { \
+ std::cerr << "tsfile err " \
+ << (err_no) << "\n"; \
+ return (err_no); \
+ } \
+ } while (0)
+
+#define BENCH_CHECK_RET_NEG1(expr) \
+ do { \
+ int _ts_err = (expr); \
+ if (_ts_err != 0) { \
+ std::cerr << "tsfile err " << _ts_err << "\n"; \
+ return -1; \
+ } \
+ } while (0)
+
+namespace {
+
+static const char* kTable = "bench_table";
+static const char* kTag2Val = "tag_b";
+static const int kNumDevices = 10;
+static const char* kFilterDevice = "device_0";
+
+static const std::vector<std::string> kReadCols{
+ "id1", "id2", "s1", "s2", "s3", "s4"};
+
+static std::string device_name(int i) {
+ return "device_" + std::to_string(i);
+}
+
+// ─── Cache drop ──────────────────────────────────────────────────────────────
+
+void bench_drop_cache() {
+#if defined(__APPLE__)
+ if (system("sudo purge") != 0) {
+ std::cerr << "[bench] purge failed or not available "
+ "(run `sudo purge` manually before bench_read)\n";
+ }
+#elif defined(__linux__)
+ if (system("sync && sudo sh -c 'echo 3 > /proc/sys/vm/drop_caches'") != 0) {
+ std::cerr << "[bench] drop_caches failed "
+ "(run `sudo sh -c 'echo 3 > /proc/sys/vm/drop_caches'` manually)\n";
+ }
+#else
+ std::cerr << "[bench] bench_drop_cache not supported on this platform\n";
+#endif
+}
+
+// ─── Write ────────────────────────────────────────────────────────────────────
+
+int write_tsfile(const std::string& path, int64_t row_count) {
+ storage::libtsfile_init();
+ storage::WriteFile file;
+ int flags = O_WRONLY | O_CREAT | O_TRUNC;
+#ifdef _WIN32
+ flags |= O_BINARY;
+#endif
+ BENCH_HANDLE_ERROR(file.create(path.c_str(), flags, 0666));
+
+ auto* schema = new storage::TableSchema(
+ std::string(kTable),
+ {
+ common::ColumnSchema("id1", common::STRING,
+ common::UNCOMPRESSED, common::PLAIN,
+ common::ColumnCategory::TAG),
+ common::ColumnSchema("id2", common::STRING,
+ common::UNCOMPRESSED, common::PLAIN,
+ common::ColumnCategory::TAG),
+ common::ColumnSchema("s1", common::INT64,
+ common::SNAPPY, common::PLAIN,
+ common::ColumnCategory::FIELD),
+ common::ColumnSchema("s2", common::DOUBLE,
+ common::SNAPPY, common::PLAIN,
+ common::ColumnCategory::FIELD),
+ common::ColumnSchema("s3", common::FLOAT,
+ common::SNAPPY, common::PLAIN,
+ common::ColumnCategory::FIELD),
+ common::ColumnSchema("s4", common::INT32,
+ common::SNAPPY, common::PLAIN,
+ common::ColumnCategory::FIELD),
+ });
+
+ auto* writer = new storage::TsFileTableWriter(&file, schema);
+ const uint32_t batch_cap = 65536;
+ int64_t rows_per_dev = row_count / kNumDevices;
+
+ for (int dev = 0; dev < kNumDevices; dev++) {
+ std::string dev_id = device_name(dev);
+ int64_t dev_base = dev * rows_per_dev;
+
+ for (int64_t off = 0; off < rows_per_dev;) {
+ uint32_t n = static_cast<uint32_t>(
+ std::min<int64_t>(batch_cap, rows_per_dev - off));
+ storage::Tablet tablet(
+ kTable,
+ {"id1", "id2", "s1", "s2", "s3", "s4"},
+ {common::STRING, common::STRING, common::INT64,
+ common::DOUBLE, common::FLOAT, common::INT32},
+ {common::ColumnCategory::TAG, common::ColumnCategory::TAG,
+ common::ColumnCategory::FIELD, common::ColumnCategory::FIELD,
+ common::ColumnCategory::FIELD, common::ColumnCategory::FIELD},
+ std::max(n, 1u));
+ for (uint32_t i = 0; i < n; i++) {
+ int64_t ts = dev_base + off + i;
+ BENCH_HANDLE_ERROR(tablet.add_timestamp(i, ts));
+ BENCH_HANDLE_ERROR(tablet.add_value(i, "id1", dev_id.c_str()));
+ BENCH_HANDLE_ERROR(tablet.add_value(i, "id2", kTag2Val));
+ BENCH_HANDLE_ERROR(tablet.add_value(i, "s1", ts));
+ BENCH_HANDLE_ERROR(tablet.add_value(i, "s2", ts * 1.1));
+ BENCH_HANDLE_ERROR(tablet.add_value(i, "s3", static_cast<float>(ts % 10000)));
+ BENCH_HANDLE_ERROR(tablet.add_value(i, "s4", static_cast<int32_t>(ts % 100000)));
+ }
+ BENCH_HANDLE_ERROR(writer->write_table(tablet));
+ off += n;
+ }
+ }
+ BENCH_HANDLE_ERROR(writer->flush());
+ BENCH_HANDLE_ERROR(writer->close());
+ delete writer;
+ delete schema;
+ return 0;
+}
+
+int write_parquet(const std::string& path, int64_t row_count) {
+ try {
+ auto schema = arrow::schema({
+ arrow::field("time", arrow::int64()),
+ arrow::field("id1", arrow::utf8()),
+ arrow::field("id2", arrow::utf8()),
+ arrow::field("s1", arrow::int64()),
+ arrow::field("s2", arrow::float64()),
+ arrow::field("s3", arrow::float32()),
+ arrow::field("s4", arrow::int32()),
+ });
+
+ auto writer_props = parquet::WriterProperties::Builder()
+ .compression(parquet::Compression::SNAPPY)
+ ->build();
+ auto arrow_props = parquet::ArrowWriterProperties::Builder().build();
+
+ const int64_t batch_cap = 65536;
+ int64_t rows_per_dev = row_count / kNumDevices;
+ arrow::MemoryPool* pool = arrow::default_memory_pool();
+
+ PARQUET_ASSIGN_OR_THROW(auto out,
+ arrow::io::FileOutputStream::Open(path));
+ PARQUET_ASSIGN_OR_THROW(
+ std::unique_ptr<parquet::arrow::FileWriter> pw,
+ parquet::arrow::FileWriter::Open(*schema, pool, out,
+ writer_props, arrow_props));
+
+ for (int dev = 0; dev < kNumDevices; dev++) {
+ std::string dev_id = device_name(dev);
+ int64_t dev_base = dev * rows_per_dev;
+
+ arrow::Int64Builder time_b;
+ arrow::StringBuilder id1_b;
+ arrow::StringBuilder id2_b;
+ arrow::Int64Builder s1_b;
+ arrow::DoubleBuilder s2_b;
+ arrow::FloatBuilder s3_b;
+ arrow::Int32Builder s4_b;
+
+ for (int64_t off = 0; off < rows_per_dev;) {
+ int64_t n = std::min(batch_cap, rows_per_dev - off);
+ time_b.Reset(); id1_b.Reset(); id2_b.Reset();
+ s1_b.Reset(); s2_b.Reset(); s3_b.Reset(); s4_b.Reset();
+ for (int64_t i = 0; i < n; i++) {
+ int64_t ts = dev_base + off + i;
+ PARQUET_THROW_NOT_OK(time_b.Append(ts));
+ PARQUET_THROW_NOT_OK(id1_b.Append(dev_id));
+ PARQUET_THROW_NOT_OK(id2_b.Append(kTag2Val));
+ PARQUET_THROW_NOT_OK(s1_b.Append(ts));
+ PARQUET_THROW_NOT_OK(s2_b.Append(ts * 1.1));
+ PARQUET_THROW_NOT_OK(s3_b.Append(static_cast<float>(ts % 10000)));
+ PARQUET_THROW_NOT_OK(s4_b.Append(static_cast<int32_t>(ts % 100000)));
+ }
+ PARQUET_ASSIGN_OR_THROW(auto a_time, time_b.Finish());
+ PARQUET_ASSIGN_OR_THROW(auto a_id1, id1_b.Finish());
+ PARQUET_ASSIGN_OR_THROW(auto a_id2, id2_b.Finish());
+ PARQUET_ASSIGN_OR_THROW(auto a_s1, s1_b.Finish());
+ PARQUET_ASSIGN_OR_THROW(auto a_s2, s2_b.Finish());
+ PARQUET_ASSIGN_OR_THROW(auto a_s3, s3_b.Finish());
+ PARQUET_ASSIGN_OR_THROW(auto a_s4, s4_b.Finish());
+ auto batch = arrow::RecordBatch::Make(
+ schema, n,
+ {a_time, a_id1, a_id2, a_s1, a_s2, a_s3, a_s4});
+ PARQUET_THROW_NOT_OK(pw->WriteRecordBatch(*batch));
+ off += n;
+ }
+ }
+ PARQUET_THROW_NOT_OK(pw->Close());
+ PARQUET_THROW_NOT_OK(out->Close());
+ return 0;
+ } catch (const std::exception& e) {
+ std::cerr << "parquet write: " << e.what() << "\n";
+ return 1;
+ }
+}
+
+// ─── Helpers ──────────────────────────────────────────────────────────────────
+
+static void print_result(const char* engine, double secs,
+ int64_t result_rows, int64_t checksum) {
+ std::cout << " " << std::left << std::setw(16) << engine
+ << std::fixed << std::setprecision(4) << secs << " s | "
+ << std::right << std::setw(12)
+ << static_cast<int64_t>(result_rows / secs) << " rows/s"
+ << " | sum_s1=" << checksum << "\n";
+}
+
+// ─── Scenario 1: Tag Filter ───────────────────────────────────────────────────
+
+int64_t tsfile_tag_filter(const std::string& path, int64_t row_count) {
+ storage::libtsfile_init();
+ storage::TsFileReader reader;
+ BENCH_CHECK_RET_NEG1(reader.open(path));
+
+ auto table_schema = reader.get_table_schema(std::string(kTable));
+ storage::Filter* tag_filter =
+ storage::TagFilterBuilder(table_schema.get()).eq("id1", kFilterDevice);
+
+ storage::ResultSet* rs = nullptr;
+ BENCH_CHECK_RET_NEG1(
+ reader.query(kTable, kReadCols, 0, row_count, rs, tag_filter));
+
+ int64_t sum = 0;
+ bool has_next = false;
+ int ret = common::E_OK;
+ while (IS_SUCC(ret = rs->next(has_next)) && has_next) {
+ if (!rs->is_null("s1")) {
+ sum += rs->get_value<int64_t>("s1");
+ }
+ }
+ rs->close();
+ reader.close();
+ delete tag_filter;
+ return sum;
+}
+
+// Collect row group indices whose statistics overlap the given string equality.
+// Equivalent to TsFile's device-level chunk pruning.
+static std::vector<int> rg_prune_string_eq(
+ const parquet::FileMetaData& meta, int col_idx, const std::string& target) {
+ std::vector<int> result;
+ for (int rg = 0; rg < meta.num_row_groups(); ++rg) {
+ auto stats = meta.RowGroup(rg)->ColumnChunk(col_idx)->statistics();
+ if (stats && stats->HasMinMax()) {
+ auto s = std::static_pointer_cast<parquet::ByteArrayStatistics>(stats);
+ std::string mn(reinterpret_cast<const char*>(s->min().ptr), s->min().len);
+ std::string mx(reinterpret_cast<const char*>(s->max().ptr), s->max().len);
+ if (target < mn || target > mx) continue; // prune
+ }
+ result.push_back(rg);
+ }
+ return result;
+}
+
+// Collect row group indices whose time range overlaps [ts_start, ts_end).
+// Equivalent to TsFile's page-level time statistics pruning.
+static std::vector<int> rg_prune_time_range(
+ const parquet::FileMetaData& meta, int col_idx,
+ int64_t ts_start, int64_t ts_end) {
+ std::vector<int> result;
+ for (int rg = 0; rg < meta.num_row_groups(); ++rg) {
+ auto stats = meta.RowGroup(rg)->ColumnChunk(col_idx)->statistics();
+ if (stats && stats->HasMinMax()) {
+ auto s = std::static_pointer_cast<parquet::Int64Statistics>(stats);
+ if (s->max() < ts_start || s->min() >= ts_end) continue; // prune
+ }
+ result.push_back(rg);
+ }
+ return result;
+}
+
+int64_t parquet_tag_filter(const std::string& path) {
+ try {
+ std::vector<std::string> cols{"time", "id1", "id2", "s1", "s2", "s3", "s4"};
+ arrow::MemoryPool* pool = arrow::default_memory_pool();
+ PARQUET_ASSIGN_OR_THROW(auto infile,
+ arrow::io::ReadableFile::Open(path));
+ PARQUET_ASSIGN_OR_THROW(
+ std::unique_ptr<parquet::arrow::FileReader> reader,
+ parquet::arrow::OpenFile(infile, pool));
+
+ std::shared_ptr<arrow::Schema> file_schema;
+ PARQUET_THROW_NOT_OK(reader->GetSchema(&file_schema));
+ std::vector<int> indices;
+ for (const auto& name : cols)
+ indices.push_back(file_schema->GetFieldIndex(name));
+
+ // Row group pruning via min/max statistics on id1 column.
+ auto& meta = *reader->parquet_reader()->metadata();
+ int id1_col = meta.schema()->ColumnIndex("id1");
+ auto matching_rgs = rg_prune_string_eq(meta, id1_col, kFilterDevice);
+
+ PARQUET_ASSIGN_OR_THROW(auto batch_reader,
+ reader->GetRecordBatchReader(matching_rgs, indices));
+
+ int64_t sum = 0;
+ std::shared_ptr<arrow::RecordBatch> batch;
+ while (batch_reader->ReadNext(&batch).ok() && batch) {
+ auto id1_arr = std::static_pointer_cast<arrow::StringArray>(
+ batch->GetColumnByName("id1"));
+ auto s1_arr = std::static_pointer_cast<arrow::Int64Array>(
+ batch->GetColumnByName("s1"));
+ for (int64_t i = 0; i < batch->num_rows(); ++i) {
+ if (!id1_arr->IsNull(i) &&
+ id1_arr->GetString(i) == kFilterDevice &&
+ !s1_arr->IsNull(i)) {
+ sum += s1_arr->Value(i);
+ }
+ }
+ }
+ return sum;
+ } catch (const std::exception& e) {
+ std::cerr << "parquet tag filter: " << e.what() << "\n";
+ return -1;
+ }
+}
+
+// ─── Scenario 2: Time Range Filter ───────────────────────────────────────────
+
+// TsFile query(start, end) is inclusive on both sides: [start, end].
+// Pass (ts_end - 1) to match Parquet's half-open [ts_start, ts_end) semantics.
+int64_t tsfile_time_filter(const std::string& path,
+ int64_t ts_start, int64_t ts_end) {
+ storage::libtsfile_init();
+ storage::TsFileReader reader;
+ BENCH_CHECK_RET_NEG1(reader.open(path));
+
+ storage::ResultSet* rs = nullptr;
+ BENCH_CHECK_RET_NEG1(
+ reader.query(kTable, kReadCols, ts_start, ts_end - 1, rs, nullptr));
+
+ int64_t sum = 0;
+ bool has_next = false;
+ int ret = common::E_OK;
+ while (IS_SUCC(ret = rs->next(has_next)) && has_next) {
+ if (!rs->is_null("s1")) sum += rs->get_value<int64_t>("s1");
+ }
+ rs->close();
+ reader.close();
+ return sum;
+}
+
+int64_t parquet_time_filter(const std::string& path,
+ int64_t ts_start, int64_t ts_end) {
+ try {
+ std::vector<std::string> cols{"time", "id1", "id2", "s1", "s2", "s3", "s4"};
+ arrow::MemoryPool* pool = arrow::default_memory_pool();
+ PARQUET_ASSIGN_OR_THROW(auto infile,
+ arrow::io::ReadableFile::Open(path));
+ PARQUET_ASSIGN_OR_THROW(
+ std::unique_ptr<parquet::arrow::FileReader> reader,
+ parquet::arrow::OpenFile(infile, pool));
+
+ std::shared_ptr<arrow::Schema> file_schema;
+ PARQUET_THROW_NOT_OK(reader->GetSchema(&file_schema));
+ std::vector<int> indices;
+ for (const auto& name : cols)
+ indices.push_back(file_schema->GetFieldIndex(name));
+
+ // Row group pruning via min/max statistics on time column.
+ auto& meta = *reader->parquet_reader()->metadata();
+ int time_col = meta.schema()->ColumnIndex("time");
+ auto matching_rgs = rg_prune_time_range(meta, time_col, ts_start, ts_end);
+
+ PARQUET_ASSIGN_OR_THROW(auto batch_reader,
+ reader->GetRecordBatchReader(matching_rgs, indices));
+
+ int64_t sum = 0;
+ std::shared_ptr<arrow::RecordBatch> batch;
+ while (batch_reader->ReadNext(&batch).ok() && batch) {
+ auto time_arr = std::static_pointer_cast<arrow::Int64Array>(
+ batch->GetColumnByName("time"));
+ auto s1_arr = std::static_pointer_cast<arrow::Int64Array>(
+ batch->GetColumnByName("s1"));
+ for (int64_t i = 0; i < batch->num_rows(); ++i) {
+ int64_t t = time_arr->Value(i);
+ if (t >= ts_start && t < ts_end && !s1_arr->IsNull(i))
+ sum += s1_arr->Value(i);
+ }
+ }
+ return sum;
+ } catch (const std::exception& e) {
+ std::cerr << "parquet time filter: " << e.what() << "\n";
+ return -1;
+ }
+}
+
+// ─── Optimized: Batch columnar read ──────────────────────────────────────────
+
+// Find the 0-based TsBlock vector index for a named column.
+// ResultSetMetadata prepends "time" as column 1 (1-indexed), so
+// TsBlock vector index = metadata column index - 1.
+static int find_vec_idx(storage::ResultSet* rs, const std::string& name) {
+ auto meta = rs->get_metadata();
+ for (int i = 1; i <= static_cast<int>(meta->get_column_count()); ++i) {
+ if (meta->get_column_name(i) == name) return i - 1;
+ }
+ return -1;
+}
+
+// Sum all INT64 values in a Vector, using direct buffer access for the
+// common no-null case to avoid per-element overhead.
+static int64_t sum_vec_int64(common::Vector* vec, uint32_t rows) {
+ int64_t sum = 0;
+ if (!vec->has_null()) {
+ // Fast path: dense int64_t array, single pointer scan.
+ const int64_t* p = reinterpret_cast<const int64_t*>(
+ vec->get_value_data().get_data());
+ for (uint32_t r = 0; r < rows; ++r) sum += p[r];
+ } else {
+ // Slow path: skip null rows; advance sequential cursor manually.
+ vec->reset_offset();
+ for (uint32_t r = 0; r < rows; ++r) {
+ if (!vec->is_null(r)) {
+ uint32_t len = 0;
+ bool null = false;
+ char* val = vec->read(&len, &null, r);
+ sum += *reinterpret_cast<int64_t*>(val);
+ vec->update_offset();
+ }
+ }
+ }
+ return sum;
+}
+
+// batch_size controls TsBlock capacity; 65536 rows/block matches write batches.
+static const int kBatchSize = 65536;
+
+int64_t tsfile_tag_filter_batch(const std::string& path, int64_t row_count) {
+ storage::libtsfile_init();
+ storage::TsFileReader reader;
+ BENCH_CHECK_RET_NEG1(reader.open(path));
+
+ auto table_schema = reader.get_table_schema(std::string(kTable));
+ storage::Filter* tag_filter =
+ storage::TagFilterBuilder(table_schema.get()).eq("id1", kFilterDevice);
+
+ storage::ResultSet* rs = nullptr;
+ BENCH_CHECK_RET_NEG1(
+ reader.query(kTable, kReadCols, 0, row_count, rs, tag_filter, kBatchSize));
+
+ const int s1_idx = find_vec_idx(rs, "s1");
+ int64_t sum = 0;
+ common::TsBlock* block = nullptr;
+ while (rs->get_next_tsblock(block) == common::E_OK && block) {
+ sum += sum_vec_int64(block->get_vector(s1_idx), block->get_row_count());
+ }
+ rs->close();
+ reader.close();
+ delete tag_filter;
+ return sum;
+}
+
+int64_t tsfile_time_filter_batch(const std::string& path,
+ int64_t ts_start, int64_t ts_end) {
+ storage::libtsfile_init();
+ storage::TsFileReader reader;
+ BENCH_CHECK_RET_NEG1(reader.open(path));
+
+ storage::ResultSet* rs = nullptr;
+ BENCH_CHECK_RET_NEG1(
+ reader.query(kTable, kReadCols, ts_start, ts_end - 1, rs, kBatchSize));
+
+ const int s1_idx = find_vec_idx(rs, "s1");
+ int64_t sum = 0;
+ common::TsBlock* block = nullptr;
+ while (rs->get_next_tsblock(block) == common::E_OK && block) {
+ sum += sum_vec_int64(block->get_vector(s1_idx), block->get_row_count());
+ }
+ rs->close();
+ reader.close();
+ return sum;
+}
+
+} // namespace
+
+// ─── Entry point ─────────────────────────────────────────────────────────────
+
+int bench_write(int64_t row_count, bool run_parquet) {
+ const std::string ts_path = "read_perf_bench.tsfile";
+ const std::string pq_path = "read_perf_bench.parquet";
+
+ std::cout << "rows_total=" << row_count
+ << " devices=" << kNumDevices
+ << " rows_per_device=" << row_count / kNumDevices
+ << "\ncolumns: time, id1, id2, s1(INT64), s2(DOUBLE),"
+ " s3(FLOAT), s4(INT32)\ncompression: SNAPPY\n";
+
+ {
+ using clock = std::chrono::high_resolution_clock;
+ auto t0 = clock::now();
+ if (write_tsfile(ts_path, row_count) != 0) return 1;
+ double s = std::chrono::duration<double>(clock::now() - t0).count();
+ std::cout << "write TsFile : " << std::fixed << std::setprecision(3)
+ << s << " s\n";
+ }
+ if (run_parquet) {
+ using clock = std::chrono::high_resolution_clock;
+ auto t0 = clock::now();
+ if (write_parquet(pq_path, row_count) != 0) return 1;
+ double s = std::chrono::duration<double>(clock::now() - t0).count();
+ std::cout << "write Parquet : " << std::fixed << std::setprecision(3)
+ << s << " s\n";
+ }
+ std::cout << "\n";
+ return 0;
+}
+
+int bench_read(int64_t row_count, bool run_parquet) {
+ int64_t rows_per_device = row_count / kNumDevices;
+ // TIME_FILTER: query the first 1/3 of the total time range.
+ // Timestamps are laid out as [0, row_count) across all devices.
+ int64_t time_range_start = 0;
+ int64_t time_range_end = row_count / 3; // ~333K rows for 1M total
+ int64_t time_result_rows = time_range_end - time_range_start;
+
+ const std::string ts_path = "read_perf_bench.tsfile";
+ const std::string pq_path = "read_perf_bench.parquet";
+
+ std::cout << "\n";
+
+ using clock = std::chrono::high_resolution_clock;
+
+ // ── Scenario 1: Tag Filter ────────────────────────────────────────────────
+ std::cout << "[TAG_FILTER] id1=\"" << kFilterDevice
+ << "\" result_rows=" << rows_per_device << "\n";
+
+ auto t0 = clock::now();
+ int64_t sum_ts_tag_row = tsfile_tag_filter(ts_path, row_count);
+ double sec_ts_tag_row = std::chrono::duration<double>(clock::now() - t0).count();
+ if (sum_ts_tag_row < 0) return 1;
+
+ auto t1 = clock::now();
+ int64_t sum_ts_tag_bat = tsfile_tag_filter_batch(ts_path, row_count);
+ double sec_ts_tag_bat = std::chrono::duration<double>(clock::now() - t1).count();
+ if (sum_ts_tag_bat < 0) return 1;
+
+ print_result("TsFile (row)", sec_ts_tag_row, rows_per_device, sum_ts_tag_row);
+ print_result("TsFile (batch)", sec_ts_tag_bat, rows_per_device, sum_ts_tag_bat);
+ if (run_parquet) {
+ auto t2 = clock::now();
+ int64_t sum_pq_tag = parquet_tag_filter(pq_path);
+ double sec_pq_tag = std::chrono::duration<double>(clock::now() - t2).count();
+ if (sum_pq_tag < 0) return 1;
+ print_result("Parquet+Arrow", sec_pq_tag, rows_per_device, sum_pq_tag);
+ if (sum_ts_tag_row != sum_pq_tag || sum_ts_tag_bat != sum_pq_tag)
+ std::cerr << " warning: tag filter checksum mismatch\n";
+ }
+ std::cout << "\n";
+
+ // ── Scenario 2: Time Range Filter ─────────────────────────────────────────
+ // Both TsFile and Parquet query the identical half-open interval
+ // [time_range_start, time_range_end). TsFile query() is inclusive on
+ // both ends, so pass (time_range_end - 1) as the upper bound.
+ std::cout << "[TIME_FILTER] time in [" << time_range_start
+ << ", " << time_range_end << ")"
+ << " result_rows=" << time_result_rows << "\n";
+
+ auto t3 = clock::now();
+ int64_t sum_ts_time_row = tsfile_time_filter(ts_path, time_range_start, time_range_end);
+ double sec_ts_time_row = std::chrono::duration<double>(clock::now() - t3).count();
+ if (sum_ts_time_row < 0) return 1;
+
+ auto t4 = clock::now();
+ int64_t sum_ts_time_bat = tsfile_time_filter_batch(ts_path, time_range_start, time_range_end);
+ double sec_ts_time_bat = std::chrono::duration<double>(clock::now() - t4).count();
+ if (sum_ts_time_bat < 0) return 1;
+
+ print_result("TsFile (row)", sec_ts_time_row, time_result_rows, sum_ts_time_row);
+ print_result("TsFile (batch)", sec_ts_time_bat, time_result_rows, sum_ts_time_bat);
+ if (run_parquet) {
+ auto t5 = clock::now();
+ int64_t sum_pq_time = parquet_time_filter(pq_path, time_range_start, time_range_end);
+ double sec_pq_time = std::chrono::duration<double>(clock::now() - t5).count();
+ if (sum_pq_time < 0) return 1;
+ print_result("Parquet+Arrow", sec_pq_time, time_result_rows, sum_pq_time);
+ if (sum_ts_time_row != sum_pq_time || sum_ts_time_bat != sum_pq_time)
+ std::cerr << " warning: time filter checksum mismatch\n";
+ }
+
+ return 0;
+}
diff --git a/cpp/examples/examples.cc b/cpp/examples/cpp_examples/bench_read.h
similarity index 50%
copy from cpp/examples/examples.cc
copy to cpp/examples/cpp_examples/bench_read.h
index edbd819a0..74786547b 100644
--- a/cpp/examples/examples.cc
+++ b/cpp/examples/cpp_examples/bench_read.h
@@ -16,18 +16,22 @@
* specific language governing permissions and limitations
* under the License.
*/
+#pragma once
+#include <cstdint>
-#include "c_examples/c_examples.h"
-#include "cpp_examples/cpp_examples.h"
+/**
+ * TsFile vs Parquet+Arrow baseline read benchmark.
+ * Writes bench files to cwd, then measures TAG_FILTER and TIME_FILTER.
+ * row_count must be a positive multiple of 10 (default: 1,000,000).
+ */
+// Write TsFile (and optionally Parquet) bench files to cwd.
+int bench_write(int64_t row_count = 1000000, bool run_parquet = true);
+
+// Best-effort OS page cache drop for the bench files.
+// On macOS: calls `purge` (requires sudo; harmless if it fails).
+// On Linux: writes to /proc/sys/vm/drop_caches (requires root).
+void bench_drop_cache();
-int main() {
- // C++ examples
- // std::cout << "begin write and read tsfile by cpp" << std::endl;
- demo_write();
- demo_read();
- std::cout << "begin write and read tsfile by c" << std::endl;
- // C examples
- write_tsfile();
- read_tsfile();
- return 0;
-}
\ No newline at end of file
+// Run read benchmarks against already-written bench files.
+// run_parquet: include Parquet+Arrow comparison (set false for TsFile-only profiling).
+int bench_read(int64_t row_count = 1000000, bool run_parquet = true);
diff --git a/cpp/examples/examples.cc b/cpp/examples/examples.cc
index edbd819a0..6020417df 100644
--- a/cpp/examples/examples.cc
+++ b/cpp/examples/examples.cc
@@ -19,15 +19,23 @@
#include "c_examples/c_examples.h"
#include "cpp_examples/cpp_examples.h"
+#include "cpp_examples/bench_read.h"
int main() {
- // C++ examples
- // std::cout << "begin write and read tsfile by cpp" << std::endl;
- demo_write();
- demo_read();
- std::cout << "begin write and read tsfile by c" << std::endl;
- // C examples
- write_tsfile();
- read_tsfile();
- return 0;
-}
\ No newline at end of file
+ // // C++ examples
+ // demo_write();
+ // demo_read();
+ // // C examples
+ // write_tsfile();
+ // read_tsfile();
+ const int64_t kRows = 1000000;
+ const bool kParquet = true;
+
+ // WRITE : generate bench files (read_perf_bench.tsfile / .parquet)
+ // READ : read existing bench files — run `sudo purge` first for cold cache
+ enum Mode { WRITE, READ };
+ const Mode kMode = READ;
+
+ if (kMode == WRITE) return bench_write(kRows, kParquet);
+ return bench_read(kRows, kParquet);
+}
diff --git a/cpp/examples/cpp_examples/CMakeLists.txt b/cpp/examples/read_perf_compare/CMakeLists.txt
similarity index 76%
copy from cpp/examples/cpp_examples/CMakeLists.txt
copy to cpp/examples/read_perf_compare/CMakeLists.txt
index a2ac8d435..8b5dd6cc2 100644
--- a/cpp/examples/cpp_examples/CMakeLists.txt
+++ b/cpp/examples/read_perf_compare/CMakeLists.txt
@@ -17,6 +17,7 @@ specific language governing permissions and limitations
under the License.
]]
-message("Running in examples/cpp_examples directory")
-aux_source_directory(. cpp_SRC_LIST)
-add_library(cpp_examples_obj OBJECT ${cpp_SRC_LIST})
+# bench_read.cpp and bench_read.h live here for organisation.
+# The parent examples/CMakeLists.txt is responsible for compiling
+# bench_read.cpp into the single `examples` executable.
+# No separate executable is built from this directory.
diff --git a/cpp/experiment/design.md b/cpp/experiment/design.md
new file mode 100644
index 000000000..41a740890
--- /dev/null
+++ b/cpp/experiment/design.md
@@ -0,0 +1,649 @@
+# ConstrainedTsFileWriter 设计文档
+
+## 1. 背景与需求
+
+### 1.1 核心问题:元数据的不可压缩性
+
+TsFile 写入过程中存在两类内存,性质截然不同:
+
+| 内存类型 | 来源 | 是否压缩 | 何时释放 | 能否控制 |
+|---------|------|---------|---------|---------|
+| M_data | ChunkWriter 数据页 | 是(写盘时压缩) | flush 后立即释放 | 控制批次大小 F |
+| M_meta | ChunkGroup/Chunk 元数据 | **否** | **只有 close 才释放** | 控制每文件 flush 次数 K |
+
+**M_meta 是核心约束来源**:只要文件不 close,每次 flush 都会向内存中追加一批不可压缩的元数据(chunk header、chunk group header、timeseries index 节点)。设备数越多、flush 次数越多,M_meta 积压越严重。
+
+因此,**文件轮转(close + 重新打开)的根本原因不是磁盘空间,而是为了释放 M_meta**。
+
+### 1.2 现有 API 的问题
+
+`TsFileWriter` / `TsFileTableWriter` 将所有决策完全交给用户:
+
+- 每批写多少行(F):需要理解内存模型才能计算
+- 何时 flush:需要实时感知 M_data
+- 何时轮转文件:需要追踪 M_meta 积累量
+
+用户在资源受限环境(嵌入式设备、容器限额、边缘节点)中无法做出这些决策。
+
+### 1.3 内存组成
+
+实验(见同目录 `TsFile 内存控制.pdf`)确认写入过程内存由三部分构成:
+
+```
+M_total = M_init + M_data + M_meta
+```
+
+| 组成 | 特征 | 公式 |
+|------|------|------|
+| M_init | 固定 | ≈ 900 KB |
+| M_data | 周期性,flush 后释放 | `s × F` |
+| M_meta | **单调递增,不可压缩,close 才释放** | `K × D × b` |
+
+其中:
+
+- `F`:每次 flush 写入的行数(批次大小)
+- `K`:当前文件已 flush 的次数
+- `D`:当前文件已出现的设备数(唯一 TAG 组合数)
+- `s = s_tablet + s_data`:每行在内存中的综合字节数
+- `b`:每设备每次 flush 新增的元数据字节数(不可压缩)
+
+对于 Table 模式:
+
+```
+s_tablet = 8 + Σ sizeof(col_type) // 所有列(TAG + FIELD)
+s_data = Σ (8 + sizeof(field_col_type)) // 仅 FIELD 列进 ChunkWriter
+s = s_tablet + s_data
+
+b = n_field_cols × 104 + 96 // chunk_meta + chunk_group_meta(字节)
+```
+
+### 1.4 最优策略
+
+**总量已知时**(EOQ 模型最优解):
+
+```
+F_opt = √(R × D × b / s)
+K_opt = R / F_opt
+M_min = M_init + 2 × √(R × s × D × b)
+```
+
+当 `M_min ≤ M_limit` 时,所有数据可写入单个文件。
+
+**总量未知时**(流式,预算对半分,F×K 最大):
+
+```
+M_avail = M_limit - M_init
+F = M_avail / (2 × s)
+K = M_avail / (2 × D × b)
+```
+
+### 1.5 实验验证
+
+配置:50 设备,50 FIELD 列 int32,总行数 100,000
+
+| max_rows | 实测内存 | 公式估算 | 误差 |
+|----------|---------|---------|------|
+| 10,000 | 11.7 MB | 11.05 MB | 5.5% |
+| 8,000 | 10.8 MB | 10.3 MB | 4.6% |
+| 6,000 | 10.6 MB | 9.63 MB | 9.1% |
+| 5,000 | 11.2 MB | 9.75 MB | 12.9% |
+
+公式误差 5–15%,主要来源:变长字符串开销、schema 结构节点、OS 内存分配粒度。
+
+---
+
+## 2. 设计目标
+
+1. **约束驱动**:用户声明内存上限和 schema,系统自动推导所有参数(F、K)。
+2. **零决策负担**:用户只调用 `write_table()`,flush / close / 文件轮转全部自动处理。
+3. **任意 Tablet 大小**:用户无需精确提供 F 行的 Tablet,系统自动拆分超大批次。
+4. **构造时可行性校验**:约束不可满足时立即报错,并说明最小可用配置。
+5. **运行时可观测**:提供估算的当前内存用量、已写行数、已创建文件。
+6. **零运行时监控开销**:不依赖 `ModStat`,所有估算仅使用轻量整数计算。
+
+---
+
+## 3. 内存控制机制
+
+### 3.1 两类内存的控制策略
+
+TsFileWriter 内部的 ChunkWriter **本身就是 M_data 的缓冲区**,每次 `write_table()` 都将数据写入 ChunkWriter,M_data 持续积累,直到显式调用 `flush()` 时才落盘并清零。
+
+```
+M_data 控制:在内部追踪累计写入行数,达到 F 时 flush()
+ → 用户提交任意大小 Tablet,系统计数,F 行后自动刷盘
+
+M_meta 控制:公式估算 flush_count × D × b,达到阈值时 rotate_file()
+ → 文件轮转是释放不可压缩元数据的唯一手段
+```
+
+```
+每次 write_table(tablet):
+ ChunkWriter 积累数据,M_data 增加
+ accumulated_rows += tablet.rows
+
+ if accumulated_rows >= F: // 达到数据预算上限
+ flush() // M_data 清零,M_meta 增加 D×b
+ flush_count++
+ accumulated_rows = 0
+ if flush_count×D×b >= rotate_thresh:
+ rotate_file() // M_meta 清零
+```
+
+两者结合,M_total 始终受控:
+
+```
+写入中(未达 F):M_total = M_init + s×accumulated + flush_count×D×b
+flush 后: M_total = M_init + flush_count×D×b
+rotate 后: M_total = M_init
+```
+
+### 3.2 关闭时内存峰值
+
+`close()` 调用时,M_data 已为 0(最后一次 flush 已完成)。峰值为:
+
+```
+M_at_close = M_init + M_meta_final
+```
+
+由于我们在 `flush_count × D × b >= meta_budget × SAFETY_FACTOR` 时触发 rotate:
+
+```
+M_meta_final ≤ meta_budget × SAFETY_FACTOR ≤ meta_budget ≤ M_avail
+→ M_at_close ≤ M_init + M_avail = M_limit ✓
+```
+
+关闭时不会超出内存上限,SAFETY_FACTOR=0.85 同时覆盖了序列化开销和公式误差。
+
+### 3.3 已有 auto-flush 机制(分析与结论)
+
+`TsFileWriter` 内部有自适应区间 M_data 检查机制,通过 `chunk_group_size_threshold_` 控制:
+
+```cpp
+if (record_count_since_last_flush_ >= record_count_for_next_mem_check_) {
+ mem_size = calculate_mem_size_for_all_group();
+ record_count_for_next_mem_check_ =
+ record_count_since_last_flush_ * chunk_group_size_threshold_ / mem_size;
+ if (mem_size > chunk_group_size_threshold_) flush();
+}
+```
+
+`ConstrainedTsFileWriter` 在 `accumulated_rows_ >= F` 时显式调用 `flush()`,M_data 控制已由上层保证。该内置机制作为**双重保险**保留(设置 `chunk_group_size_threshold_ = data_budget_`),防止单个超大 Tablet 导致 M_data 短暂超限。
+
+> `chunk_group_size_threshold_` 是进程全局配置,多个实例并发时会互相覆盖,当前设计仅支持单写入器场景。
+
+### 3.4 ModStat 方案(排除)
+
+`ModStat` 通过 `ATOMIC_FAA` 追踪各模块内存,排除原因:
+
+- 写入热路径(数据页分配、ByteStream 扩容)调用频繁,嵌入式 CPU 原子操作代价不可忽略
+- 仅在 `ENABLE_MEM_STAT` 编译选项下开启,不适合生产路径
+- 公式估算(5–15% 误差)加安全裕度已足够,无需字节精度
+
+---
+
+## 4. 策略计算
+
+### 4.1 约束规格
+
+```cpp
+struct WriteConstraints {
+ int64_t memory_limit_bytes; // 必填:内存上限
+
+ // 可选:数据规模提示(有则使用 EOQ 最优策略)
+ int n_devices = 0; // 0 = 未知,运行时动态追踪
+ int64_t total_rows_hint = -1; // 每设备总行数,-1 = 未知
+};
+```
+
+### 4.2 可行性检查
+
+构造时执行,失败则 `init_status()` 返回非 E_OK:
+
+```
+// 最小可行内存:F=1 行,K=1 次,D=1 设备
+M_min = M_INIT_BYTES + s + b
+if M_min > memory_limit_bytes:
+ ERROR "内存不足:schema 最小写入需 {M_min} 字节,当前限制 {memory_limit_bytes}"
+```
+
+### 4.3 策略推导
+
+```
+M_avail = memory_limit_bytes - M_INIT_BYTES
+D_eff = max(1, n_devices) // n_devices=0 时用 1 作为初始保守估计
+
+// 尝试 EOQ 最优(总量已知时)
+if total_rows_hint > 0 && n_devices > 0:
+ B = D_eff × b
+ F = √(total_rows_hint × B / s)
+ K = total_rows_hint / F
+ peak = M_INIT_BYTES + s×F + K×B
+ if peak ≤ memory_limit_bytes:
+ return {rows_per_flush=F, flushes_per_file=K, estimated_peak_mem=peak}
+ // 超出内存,退化为对半分
+
+// 对半分预算(流式最优,F×K 最大)
+F = M_avail / (2 × s)
+K = M_avail / (2 × D_eff × b)
+K = max(1LL, K)
+return {rows_per_flush=F, flushes_per_file=K, estimated_peak_mem=M_INIT_BYTES+s×F+K×D_eff×b}
+```
+
+### 4.4 安全裕度
+
+```
+data_budget = s × F
+meta_budget = M_avail - data_budget
+rotate_thresh = meta_budget × SAFETY_FACTOR // SAFETY_FACTOR = 0.85
+
+// 每次 flush 后判断
+if flush_count × D_eff × b >= rotate_thresh:
+ rotate_file()
+```
+
+`sizeof_datatype()` 映射:
+
+| TSDataType | 字节数 |
+|-----------|-------|
+| BOOLEAN | 1 |
+| INT32 / FLOAT | 4 |
+| INT64 / DOUBLE | 8 |
+| STRING | 32(保守估计)|
+
+---
+
+## 5. 接口设计
+
+### 5.1 数据结构
+
+```cpp
+struct WriteConstraints {
+ int64_t memory_limit_bytes;
+ int n_devices = 0;
+ int64_t total_rows_hint = -1;
+};
+
+struct WritingStrategy {
+ int64_t rows_per_flush; // F:每次 flush 的行数
+ int64_t flushes_per_file; // K:每文件最多 flush 次数
+ int64_t estimated_peak_mem; // 预估峰值内存(bytes)
+};
+
+struct WriteStats {
+ int64_t rows_written_total; // 累计写入行数(跨所有文件)
+ int64_t files_completed; // 已完成(已 close)的文件数
+ int64_t current_file_flush_count; // 当前文件已 flush 次数
+ int64_t estimated_current_mem_bytes; // 当前估算 M_meta(M_data 已 flush)
+};
+```
+
+### 5.2 类接口
+
+```cpp
+class ConstrainedTsFileWriter {
+ public:
+ // 构造:立即执行可行性校验
+ ConstrainedTsFileWriter(
+ const WriteConstraints& constraints,
+ const std::string& output_dir,
+ const std::string& file_prefix,
+ const std::shared_ptr<TableSchema>& schema
+ );
+
+ ~ConstrainedTsFileWriter();
+
+ int init_status() const; // E_OK 表示约束可满足,否则不可写入
+
+ // 写入任意大小 Tablet,内部自动拆分、flush、轮转
+ int write_table(Tablet& tablet);
+ int close();
+
+ // 观测接口
+ const WriteStats& stats() const;
+ const WritingStrategy& strategy() const;
+ const std::vector<std::string>& created_files() const;
+
+ // 纯计算接口:不创建文件,仅返回策略
+ static WritingStrategy compute_strategy(
+ const WriteConstraints& constraints,
+ const std::shared_ptr<TableSchema>& schema
+ );
+
+ // 可行性检查:返回 E_OK 或错误码,可选输出错误描述
+ static int check_feasibility(
+ const WriteConstraints& constraints,
+ const std::shared_ptr<TableSchema>& schema,
+ std::string* error_msg = nullptr
+ );
+};
+```
+
+### 5.3 文件命名规则
+
+```
+{output_dir}/{file_prefix}_{index:04d}.tsfile
+
+例:/data/output/sensor_0000.tsfile
+ /data/output/sensor_0001.tsfile
+```
+
+### 5.4 用户代码示例
+
+**基本用法**
+
+```cpp
+WriteConstraints c;
+c.memory_limit_bytes = 10 * 1024 * 1024; // 10 MB
+c.n_devices = 50;
+
+ConstrainedTsFileWriter writer(c, "/data/output", "sensor", schema);
+if (writer.init_status() != E_OK) {
+ return writer.init_status(); // 内存不足以写入此 schema
+}
+
+// 用户可提交任意大小的 Tablet,系统自动拆分
+while (has_more_data) {
+ Tablet tablet(...);
+ fill(tablet);
+ writer.write_table(tablet); // 拆分 + flush + rotate 全自动
+}
+writer.close();
+```
+
+**仅查询策略**
+
+```cpp
+auto s = ConstrainedTsFileWriter::compute_strategy(c, schema);
+// s.rows_per_flush = 每批次建议行数
+// s.flushes_per_file = 每文件建议 flush 次数
+// s.estimated_peak_mem
+```
+
+**运行时观测**
+
+```cpp
+const auto& st = writer.stats();
+printf("已写 %lld 行,当前文件 flush %lld 次,估算内存 %lld KB\n",
+ st.rows_written_total,
+ st.current_file_flush_count,
+ st.estimated_current_mem_bytes / 1024);
+```
+
+---
+
+## 6. 内部实现
+
+### 6.1 模块结构
+
+```
+src/writer/
+├── tsfile_writer.h/.cc (已有)
+├── tsfile_table_writer.h/.cc (已有)
+├── constrained_tsfile_writer.h (新增)
+└── constrained_tsfile_writer.cc (新增)
+```
+
+内部直接持有 `TsFileWriter*`(而非 `TsFileTableWriter`),以便在 rotate 时精确控制 `open()` 和 `register_table()`。
+
+### 6.2 内部状态
+
+```cpp
+// 固定参数(构造时确定)
+WriteConstraints constraints_;
+WritingStrategy strategy_;
+int64_t s_; // 每行综合字节数
+int64_t b_per_device_; // 每设备每 flush 的不可压缩元数据增量
+int64_t meta_budget_; // 元数据内存预算
+int64_t rotate_thresh_; // = meta_budget_ × SAFETY_FACTOR
+int init_status_;
+
+// 文件管理
+TsFileWriter* writer_;
+std::shared_ptr<TableSchema> table_schema_;
+int file_index_;
+std::vector<std::string> created_files_;
+std::string output_dir_;
+std::string file_prefix_;
+
+// 运行时状态
+WriteStats stats_;
+int64_t accumulated_rows_; // 上次 flush 后累计写入的行数
+
+// 动态设备追踪(n_devices == 0 时)
+bool dynamic_mode_;
+int64_t observed_max_devices_; // 本文件内见到的最多设备数(rotate 后重置)
+```
+
+### 6.3 构造与可行性检查
+
+```
+ConstrainedTsFileWriter(constraints, output_dir, prefix, schema):
+ 1. 从 schema 计算 s_, b_per_device_
+ 2. check_feasibility() → init_status_
+ if init_status_ != E_OK: return(不创建文件)
+ 3. compute_strategy() → strategy_
+ 4. data_budget_ = s_ × strategy_.rows_per_flush
+ meta_budget_ = M_avail - data_budget_
+ rotate_thresh_ = meta_budget_ × SAFETY_FACTOR
+ 5. g_config_value_.chunk_group_size_threshold_ = data_budget_
+ 6. open_next_file()
+```
+
+### 6.4 write_table() 流程
+
+用户提交任意大小的 Tablet,系统内部累计行数,达到 F 时自动 flush。
+
+```
+write_table(tablet):
+ if init_status_ != E_OK: return init_status_
+
+ writer_->write_table(tablet) // 数据写入 ChunkWriter,M_data 增加
+ accumulated_rows_ += tablet.cur_row_size_
+ stats_.rows_written_total += tablet.cur_row_size_
+
+ // 动态设备追踪(每个 Tablet 都更新,取本文件内最大值)
+ if dynamic_mode_:
+ d = tablet.find_all_device_boundaries().size() - 1
+ if d > observed_max_devices_:
+ observed_max_devices_ = d
+ recompute_rotate_thresh() // D 增大 → thresh 减小
+
+ if accumulated_rows_ >= strategy_.rows_per_flush:
+ do_flush()
+
+do_flush():
+ writer_->flush() // M_data 落盘,M_meta 增加 D×b
+ accumulated_rows_ = 0
+ stats_.current_file_flush_count++
+
+ D_eff = dynamic_mode_ ? observed_max_devices_ : constraints_.n_devices
+ meta_est = stats_.current_file_flush_count × D_eff × b_per_device_
+ stats_.estimated_current_mem_bytes = M_INIT_BYTES + meta_est
+
+ if meta_est >= rotate_thresh_:
+ rotate_file()
+```
+
+### 6.5 rotate_file() 流程
+
+```
+rotate_file():
+ 1. writer_->close() // 写盘,释放 M_meta
+ 2. created_files_.push_back(current_path)
+ 3. stats_.files_completed++
+ 4. delete writer_; writer_ = nullptr
+ 5. file_index_++
+ 6. g_config_value_.chunk_group_size_threshold_ = s_ × strategy_.rows_per_flush
+ 7. writer_ = new TsFileWriter()
+ 8. writer_->open(make_file_path(file_index_))
+ 9. writer_->register_table(table_schema_)
+ 10. stats_.current_file_flush_count = 0
+ 11. if dynamic_mode_:
+ observed_max_devices_ = 0
+ recompute_rotate_thresh()
+```
+
+### 6.6 动态设备追踪
+
+```
+recompute_rotate_thresh():
+ D = max(1LL, observed_max_devices_)
+ // rotate_thresh 以"flush 次数"表示
+ rotate_thresh_ = (int64_t)(meta_budget_ / (D × b_per_device_) × SAFETY_FACTOR)
+ rotate_thresh_ = max(1LL, rotate_thresh_)
+```
+
+`observed_max_devices_` 在单个文件内单调递增,rotate 后重置为 0。设备数越多,阈值越小,天然保守。
+
+---
+
+## 7. 行为示例
+
+配置:50 设备,50 FIELD 列 int32,memory_limit=12MB
+
+```
+s_tablet = 8 + 100×4 = 408 bytes/row (假设 50 TAG + 50 FIELD 各 4 字节)
+s_data = 50×(8+4) = 600 bytes/row
+s = 1008 bytes/row
+b = 50×104 + 96 = 5296 bytes/设备/flush (不可压缩元数据)
+M_avail ≈ 11.5 MB
+
+对半分策略:
+ F = 11.5MB / (2×1008) ≈ 5,694 行
+ K = 11.5MB / (2×50×5296) ≈ 21 次
+
+data_budget = 5694 × 1008 ≈ 5.74 MB
+meta_budget ≈ 5.75 MB
+rotate_thresh = 5.75MB × 0.85 / (50×5296) ≈ 18.4 → 第 18 次 flush 后触发
+```
+
+| 事件 | M_meta 估算 | 操作 |
+|------|------------|------|
+| flush 1 | 0.26 MB | 继续 |
+| flush 10 | 2.65 MB | 继续 |
+| flush 18 | 4.77 MB ≥ thresh | **rotate:close 释放 4.77 MB 元数据,重开新文件** |
+| flush 1(新文件) | 0.26 MB | 继续 |
+
+文件轮转的本质:每 18 次 flush 必须 close 一次,强制释放积压的不可压缩元数据。
+
+---
+
+## 8. 约束与局限
+
+1. **超大 Tablet 的短暂 M_data 超限**:若单个 Tablet 的行数超过 F,写入时 M_data 会短暂超出 data_budget,随后的 `do_flush()` 会立即释放。`chunk_group_size_threshold_ = data_budget_` 作为双重保险在此场景下触发保护(见 §3.3)。若需严格保证不超限,可在 write_table 前检查 Tablet 行数并返回错误。
+
+2. **STRING 列内存估算误差**:使用固定 32 字节估算变长字符串,通过 SAFETY_FACTOR=0.85 和保守 M_INIT_BYTES 缓解。
+
+3. **Schema 同构假设**:假设所有设备有相同的 FIELD 列结构。异构 schema 取最大 FIELD 列数作为保守估计。
+
+4. **`chunk_group_size_threshold_` 是进程全局配置**:多个实例并发运行时会互相覆盖。当前设计仅支持单写入器场景。
+
+5. **单线程写入**:不考虑并发。
+
+---
+
+## 9. 待确认事项
+
+- [ ] `M_INIT_BYTES=900KB`:是否随注册的时间序列数量显著变化。
+- [ ] `b = n_field_cols × 104 + 96`:104 bytes/chunk_meta 和 96 bytes/chunk_group_meta 为近似值,需根据实际序列化大小校准。
+- [ ] `find_all_device_boundaries().size() - 1`:确认等于本次 Tablet 中唯一设备数。
+
+---
+
+## 10. 设计演进记录
+
+### 10.1 从公式估算到直接监控
+
+**原始方向**:用公式 `M_data = s × F`、`M_meta = K × D × b` 预测内存,提前计算 F/K/s/b 等参数,通过行数计数驱动 flush 和 rotate。
+
+**转变原因**:TsFile 已有两个精确的内存测量接口,无需估算:
+
+**M_data 的直接测量**:`TsFileWriter::calculate_mem_size_for_all_group()` 已存在,遍历所有 ChunkWriter 求和:
+
+```cpp
+// ChunkWriter / ValueChunkWriter / TimeChunkWriter 均实现:
+int64_t estimate_max_series_mem_size() {
+ return chunk_data_.total_size() // 已完成页(编码后字节)
+ + page_writer_.estimate_max_mem_size() // 当前页已编码 + encoder pending 上界
+ + page_header_overhead
+ + statistic_sizeof;
+}
+```
+
+测量的是**编码后的实际大小**,不是原始数据大小。高效编码(delta、RLE)时返回值远小于 `s × F`,天然适应实际编码效率。
+
+**M_meta 的直接测量**:所有元数据(ChunkMeta、ChunkGroupMeta、Statistic)均从 `TsFileIOWriter::meta_allocator_`(类型 `PageArena`)分配。只需给 `PageArena` 新增一个方法:
+
+```cpp
+// page_arena.h 新增
+int64_t get_total_used_bytes() const {
+ int64_t total = 0;
+ Page* p = dummy_head_.next_;
+ while (p) {
+ total += p->cur_alloc_ - (char*)(p + 1);
+ p = p->next_;
+ }
+ return total;
+}
+
+// tsfile_io_writer.h 新增
+int64_t get_meta_size() const {
+ return meta_allocator_.get_total_used_bytes();
+}
+```
+
+M_meta 是**不经过数据编码的结构体**,PageArena 的已用字节数即为精确的 M_meta 大小。
+
+### 10.2 监控接口对称性
+
+| | M_data | M_meta |
+|---|---|---|
+| 存储位置 | ChunkWriter 内部 ByteStream | `meta_allocator_`(PageArena) |
+| 测量方法 | `calculate_mem_size_for_all_group()` | `meta_allocator_.get_total_used_bytes()` |
+| 精度 | 编码后实际大小 + encoder pending 上界(保守) | 精确 |
+| 编码影响 | **已体现**,反映实际编码效率 | **无关**,元数据不经数据编码 |
+| 需新增代码 | 已有,直接暴露为 `get_data_size()` | PageArena 新增一个方法 + IOWriter getter |
+
+### 10.3 策略的简化
+
+有了直接监控,原来需要计算的 F、K、s、b、D 等参数大部分消失。"最优策略"退化为**唯一一个决策:如何在 M_avail 中分配 data_budget 和 meta_budget**。
+
+```
+M_avail = memory_limit - M_init
+
+// 流式(总量未知):50/50 是数学最优(最大化单文件容纳数据量)
+data_budget = M_avail / 2
+meta_budget = M_avail / 2
+
+// 总量已知:EOQ 给出最优分配比(同样只是两个数值)
+```
+
+运行时控制变为纯粹的阈值比较:
+
+```
+write_table(tablet):
+ writer->write_table(tablet)
+ if writer->get_data_size() >= data_budget:
+ writer->flush()
+ if writer->get_meta_size() >= meta_budget:
+ rotate_file()
+```
+
+去掉的东西:accumulated_rows 计数、observed_max_devices 追踪、find_all_device_boundaries 调用、b_per_device 计算、SAFETY_FACTOR(仅覆盖 M_init 估算误差,可提高到 0.95)。
+
+### 10.4 两层接口定位
+
+监控接口和自动管理层相互独立,服务不同用户:
+
+```
+TsFileWriter(已有 + 小幅扩展)
+ ├── get_data_size() ← calculate_mem_size_for_all_group() 直接暴露
+ └── get_meta_size() ← meta_allocator_.get_total_used_bytes()
+
+ConstrainedTsFileWriter(建立在上面两个接口之上)
+ └── write_table() ← 内部用两个阈值自动判断 flush / rotate
+```
+
+- **直接使用监控接口**:适合已有自己写入节奏的用户,只需资源可见性,自行决策
+- **ConstrainedTsFileWriter**:适合完全不想管写入细节的用户,约束自动执行
diff --git a/cpp/experiment/read_perf_optimization.md b/cpp/experiment/read_perf_optimization.md
new file mode 100644
index 000000000..00c3e8f7d
--- /dev/null
+++ b/cpp/experiment/read_perf_optimization.md
@@ -0,0 +1,342 @@
+# TsFile 读取路径性能优化
+
+## 目标
+
+- **批量读取(Full Scan)**:吞吐量 > Parquet + Arrow
+- **过滤查询(Tag Filter)**:延迟与吞吐量 > Parquet + Arrow
+
+对比对象:Apache Parquet C++ 库 + Apache Arrow C++ 库(`parquet::arrow::FileReader` + Arrow Compute)
+
+---
+
+## TsFile 读取路径详解
+
+```
+用户调用
+ TsFileReader::query(table, columns, time_range, tag_filter)
+ │
+ ▼
+ TableQueryExecutor::query()
+ ├── IMetadataQuerier::getDevices(table, id_filter) ← Tag Filter 在此剪枝设备
+ │ TsFileMeta(内存索引)中查找匹配 id_filter 的设备列表
+ │
+ └── 对每个匹配设备 → DeviceQueryTask
+ │
+ ▼
+ TsFileIOReader::alloc_ssi() ← 为每个 measurement 分配 SeriesScanIterator
+ └── 从 TsFileMeta 中定位 ChunkMeta(offset, size, 统计信息)
+ │
+ ▼
+ ChunkReader::load_by_meta() ← 按需从磁盘加载 Chunk(含 PageHeader 列表)
+ │
+ └── 对每个 Page:
+ cur_page_statisify_filter() ← 使用 Page 统计信息(min/max)剪枝
+ skip_cur_page() ← 不满足则跳过,无 IO
+ decode_cur_page_data() ← 满足则:
+ ├── Compressor::decompress() (解压缩)
+ └── Decoder::decode() (解码 → TsBlock)
+ │
+ ▼
+ TsBlock(列式内存块,含 time 列 + value 列)
+ │
+ ▼
+ TsBlockReader::next()(返回一个 TsBlock)
+ │
+ ▼
+ TableResultSet(行迭代器包装)
+ │
+ ▼
+ 用户:result->next() → get_value<T>(col_index)
+```
+
+### 关键类说明
+
+| 类 | 职责 |
+|---|---|
+| `TsFileReader` | 对外 API 入口,持有 ReadFile + TsFileExecutor + TableQueryExecutor |
+| `TableQueryExecutor` | 查询编排;协调元数据查询 → 设备迭代 → TsBlock 读取 |
+| `IMetadataQuerier` / `MetadataQuerier` | 从 `TsFileMeta` 中查设备列表;在 Tag Filter 阶段完成设备级剪枝 |
+| `TsFileIOReader` | 文件底层 IO;加载文件 Footer、设备/测量点索引;分配 SSI |
+| `ChunkReader` | 核心读取逻辑;load_by_meta → 逐 Page 解压 → 解码 → TsBlock |
+| `Compressor` | 解压缩(GZIP / LZ4 / Snappy / Uncompressed) |
+| `Decoder` | 解码(Gorilla / Sprintz / RLE / Delta / Plain 等) |
+| `TsBlock` | 列式内存格式(`VectorDesc` 数组),ChunkReader 输出单元 |
+| `TableResultSet` | 包装 `TsBlockReader`,提供行迭代器 API(`next()` / `get_value<T>()`) |
+| `Filter` | 抽象过滤基类;子类:`EqualToFilter`, `BetweenFilter`, `AndFilter`, `TagFilter`, `TimeFilter` |
+
+### TsFile 存储结构对读取的影响
+
+```
+TsFile 物理布局:
+┌────────────────────────────────┐
+│ Magic + Version │
+├────────────────────────────────┤
+│ Chunk (Device_0, s1) │ ← 一个设备的一列所有数据连续存储
+│ PageHeader | PageData │
+│ PageHeader | PageData │
+│ ... │
+├────────────────────────────────┤
+│ Chunk (Device_1, s1) │
+│ ... │
+├────────────────────────────────┤
+│ ... │
+├────────────────────────────────┤
+│ File Footer(元数据索引) │ ← TsFileMeta,加载到内存的 PageArena
+│ DeviceMetadataIndex │
+│ MeasurementMetadataIndex │
+│ ChunkMeta(offset/size)│
+└────────────────────────────────┘
+```
+
+- **Tag Filter 剪枝粒度:设备(Chunk)级** — 不满足条件的设备的所有 Chunk 都不会被读取
+- **Time Filter 剪枝粒度:Page 级** — 利用 PageHeader 中的 min/max 时间戳统计信息跳过整页
+
+---
+
+## 当前性能分析
+
+### TsFile 相对 Parquet+Arrow 的优势
+
+| 场景 | TsFile 优势 |
+|---|---|
+| **Tag Filter 查询** | 设备粒度剪枝,仅加载匹配设备的 Chunk;Parquet 无设备概念,需全列扫描后再 filter |
+| **时间范围查询** | Page 级 min/max 统计,可跳过整页 IO;Parquet Row Group 统计粒度更粗 |
+| **时序数据编码** | Gorilla(浮点)/ Sprintz / Delta(时间戳)专为时序优化;Parquet 通用编码在时序上压缩率低 |
+| **写入语义一致性** | Tag 是一等公民,查询接口天然支持设备过滤 |
+
+### 当前瓶颈(相对 Parquet+Arrow 的劣势)
+
+| 编号 | 瓶颈 | 说明 |
+|---|---|---|
+| **P1** | 行式结果集 API | `TableResultSet::next()` 每次返回一行;Arrow 直接操作列内存,无行迭代开销 |
+| **P2** | 字符串列名查找 | `get_value("s1")` 每次查 map;Arrow 通过列索引直接访问 |
+| **P3** | TsBlock 未直接暴露给用户 | 内部已有列式 TsBlock,但最终被包装为行迭代器,额外开销 |
+| **P4** | 非向量化解码路径 | decode 函数逐值处理;Arrow 解码路径为 SIMD 优化的列式批处理 |
+| **P5** | 无 Arrow 格式输出接口 | Arrow compute/analytics 生态无法直接对接 TsFile 输出 |
+| **P6** | 单线程 Chunk 读取 | 同一个查询内各设备的 Chunk 串行读取;可并行 |
+
+---
+
+## Parquet + Arrow 特点分析
+
+```
+Parquet 读取路径:
+ parquet::arrow::OpenFile()
+ │
+ ▼
+ FileReader::ReadTable(column_indices) ← 列投影(仅加载所需列)
+ ├── Row Group 统计信息 (min/max) → 粗粒度剪枝
+ └── Column Chunk 解码 → Arrow ChunkedArray
+ │
+ ▼
+ Arrow Table(所有列在内存中为 Arrow 格式 ChunkedArray)
+ │
+ ▼
+ Arrow Compute:
+ CallFunction("equal", {id1_col, scalar}) → 布尔 mask
+ CallFunction("filter", {s1_col, mask}) → 过滤后 ChunkedArray
+ CallFunction("sum", {filtered}) → Scalar 结果
+ │
+ ▼
+ 全程向量化,SIMD 加速
+```
+
+**Parquet+Arrow 劣势(TsFile 可以利用的点):**
+- 无设备/Tag 语义,Tag Filter 必须全列加载后再 compute filter(内存峰值高)
+- 无时序专用编码,相同时序数据压缩率低于 TsFile(更多 IO)
+- Row Group 是唯一的跳过粒度,粒度比 TsFile 的 Chunk+Page 两级更粗
+
+---
+
+## 优化方向
+
+### 短期(当前 Baseline 完成后确认优先级)
+
+| 优先级 | 优化点 | 预期收益 |
+|---|---|---|
+| 高 | **暴露批量读取 API(TsBlock 级)** | 消除 P1/P2,Full Scan 大幅提速 |
+| 高 | **列索引替代列名字符串查找** | 消除 P2,每行节省 map 查找 |
+| 中 | **Tag Filter + 列投影组合** | 同时剪枝设备 + 只解码所需列 |
+
+### 中期
+
+| 优化点 | 说明 |
+|---|---|
+| **SIMD 友好解码路径** | 对 Plain/RLE 编码使用 SIMD 批量解码,对齐 Arrow 的向量化优势 |
+| **Arrow IPC 格式输出接口** | 将 TsBlock 直接转换为 Arrow RecordBatch,打通 Arrow compute 生态 |
+| **Predicate pushdown to Page level** | 将 field filter(如 s1 > 100)推入 Page 解码阶段,减少解码量 |
+
+### 长期
+
+| 优化点 | 说明 |
+|---|---|
+| **多线程并行 Chunk 读取** | 不同设备 Chunk 并行加载 + 解码,充分利用多核 |
+| **IO prefetch** | 异步预取下一个 Chunk,隐藏 IO 延迟 |
+| **列式存储布局优化** | 对高频访问 field 列,考虑调整 Chunk 边界以提高 IO 局部性 |
+
+---
+
+## Baseline 实验设计
+
+### 数据模型
+
+```
+Table: bench_table
+Columns:
+ time (TIMESTAMP/INT64) — 单调递增,隐式列(TsFile)/ 显式列(Parquet)
+ id1 (TAG, STRING) — 设备标识,值: "device_0" ~ "device_9"
+ id2 (TAG, STRING) — 固定值 "tag_b"
+ s1 (FIELD, INT64) — 值 = 时间戳,用于 checksum
+ s2 (FIELD, DOUBLE) — 值 = ts * 1.1
+ s3 (FIELD, FLOAT) — 值 = ts % 10000
+ s4 (FIELD, INT32) — 值 = ts % 100000
+
+压缩:SNAPPY(两侧对齐;TsFile TAG 列为 UNCOMPRESSED,FIELD 列为 SNAPPY)
+
+规模:
+ 设备数 (kNumDevices): 10
+ 每设备行数: row_count / 10 (默认 100,000)
+ 总行数 (row_count): 1,000,000
+ 时间戳分布: device_d 拥有 [d*100K, (d+1)*100K)
+```
+
+### 测试场景
+
+| 场景 | 描述 | 结果行数 | 理论优势方 |
+|---|---|---|---|
+| **Tag Filter** | `id1="device_0"`,计算 `sum(s1)` | 100K(1/10) | TsFile(设备级剪枝) |
+| **Time Filter** | `time ∈ [0, 500K)`,计算 `sum(s1)` | 500K(1/2) | TsFile(Page 级时间剪枝) |
+
+两侧产生相同 checksum(`sum_s1`),以此验证结果正确性。计算方式不要求对等(TsFile 行迭代 vs Parquet 向量化,这正是要优化的差距)。
+
+### 运行方式
+
+```bash
+# 构建 TsFile 主库
+cmake -B build -S . -DCMAKE_BUILD_TYPE=Release
+cmake --build build --target tsfile -j
+
+# 构建 read_perf_compare(需要系统安装 apache-arrow)
+cmake -B examples/build -S examples \
+ -DENABLE_READ_PERF_COMPARE=ON \
+ -DCMAKE_PREFIX_PATH="$(brew --prefix apache-arrow)/lib/cmake" \
+ -DCMAKE_BUILD_TYPE=Release
+cmake --build examples/build --target read_perf_compare -j
+
+# 运行(默认 1,000,000 行)
+./examples/build/read_perf_compare 1000000
+```
+
+### Baseline 记录表(待填入实测数据)
+
+### 测试环境
+
+- 硬件:Apple M 系列(arm64)、macOS
+- 存储:本地 SSD
+- Arrow 版本:22.0.0(Homebrew)
+- 编译:`-O3`,`-DCMAKE_BUILD_TYPE=Release`
+
+### Baseline 结果(2026-03-26)
+
+| 场景 | 结果行数 | TsFile 耗时(s) | TsFile 吞吐(rows/s) | Parquet+Arrow 耗时(s) | Parquet+Arrow 吞吐(rows/s) | 差距(倍) |
+|---|---|---|---|---|---|---|
+| TAG_FILTER (`id1="device_0"`) | 100K | 0.147 | 681,356 | 0.057 | 1,755,389 | **2.6×** |
+| TIME_FILTER (`time ∈ [0, 500K)`) | 500K | 0.731 | 683,886 | 0.050 | 9,933,783 | **14.5×** |
+
+> checksum 两侧一致(TAG: 4,999,950,000 / TIME: 124,999,750,000)
+>
+> 运行方式:`cmake --build examples/build -j && ./examples/build/examples`
+
+### 观察
+
+- **TAG_FILTER**:TsFile 设备级剪枝只读 1/10 数据,仍慢 2.6×,瓶颈在行式 `ResultSet` 迭代层
+- **TIME_FILTER**:差距达 14.5×,Parquet 全量加载后列式扫描极快;TsFile 的 Page 级时间剪枝(理论上应跳过 devices 5-9)受限于当前行迭代开销,优势未体现
+- 核心问题:当前 TsFile 读取 API 是 `next()` 行迭代,无法利用已有的列式 TsBlock 内存格式;**暴露批量/列式读取接口是首要优化方向**
+
+---
+
+## 优化记录
+
+### Round 1: Bug 修复 + 批量解码 + 块级过滤(2026-03-27)
+
+#### 修复的 Bug
+
+**TS2DIFFDecoder::has_remaining() 缺少 header_peeked_ 检查**
+
+- 文件:`src/encoding/ts2diff_decoder.h:226`
+- 原因:`peek_next_block_range_int64()` 读取最后一个 TS2DIFF 块的 header(24 字节)后,ByteStream 已空。随后 `has_remaining()` 返回 false(未检查 `header_peeked_` 标志),导致 `read_batch_int64()` 返回 time_count=0,丢失最后一个 partial block(67 个值)
+- 级联效应:时间页已耗尽但值页仍有数据 → `prev_time_page_not_finish()=false` / `prev_value_page_not_finish()=true` 不匹配 → `get_next_page()` 跳过后续所有页 → 每设备只读 1 页(9933 行),而非 10 页(100000 行)
+- 修复:在 `has_remaining()` 中增加 `header_peeked_` 条件:
+ ```cpp
+ return header_peeked_ || bits_left_ != 0 ||
+ (current_index_ <= write_index_ && write_index_ != -1 && current_index_ != 0);
+ ```
+
+#### 新增的优化
+
+1. **TS2DIFF 批量解码**(`read_batch_int64`):一次解码 129 个时间戳(对应一个 TS2DIFF 块),避免逐值虚函数调用
+2. **块级时间过滤**(`peek_next_block_range_int64`):读取 TS2DIFF 块 header 获取 `[first_value, first_value + write_index * delta_min]` 时间范围,整块不满足时直接跳过解码
+3. **批量值解码**(`read_batch_int32/int64/float/double`):与时间批量解码配对,一次读取一批值
+4. **批量时间过滤**(`satisfy_batch_time`):对一批时间戳做批量 filter 判断,返回 pass 掩码
+
+#### 优化后结果(2026-03-27)
+
+测试数据:10 设备 × 100K 行/设备 = 1M 总行,4 列 FIELD(s1=INT64, s2=DOUBLE, s3=FLOAT, s4=INT32)
+
+| 场景 | 结果行数 | TsFile (row) | TsFile (batch) | Parquet+Arrow | TsFile batch vs Parquet |
+|---|---|---|---|---|---|
+| TAG_FILTER (`id1="device_0"`) | 100K | 0.0222s / 4.5M rows/s | 0.0105s / 9.5M rows/s | 0.0745s / 1.3M rows/s | **7.1× 快** |
+| TIME_FILTER (`time ∈ [0, 333333)`) | 333K | 0.0741s / 4.5M rows/s | 0.0364s / 9.2M rows/s | 0.0513s / 6.5M rows/s | **1.4× 快** |
+
+> checksum 两侧一致(TAG: 4,999,950,000 / TIME: 55,555,277,778)
+
+#### 与 Baseline 对比
+
+| 场景 | Baseline TsFile (row) | 优化后 TsFile (row) | 优化后 TsFile (batch) | 行模式提速 | 批量模式提速 |
+|---|---|---|---|---|---|
+| TAG_FILTER | 0.147s / 681K rows/s | 0.022s / 4.5M rows/s | 0.0105s / 9.5M rows/s | **6.6×** | **14×** |
+| TIME_FILTER | 0.731s / 684K rows/s | 0.074s / 4.5M rows/s | 0.036s / 9.2M rows/s | **9.9×** | **20×** |
+
+> 注:Baseline 受 has_remaining bug 影响,每设备只读约 1/10 的数据。修复 bug 后行模式吞吐约 4.5M rows/s,批量模式约 9.5M rows/s。
+
+#### 火焰图分析(2026-03-27,time_filter 场景,570 采样)
+
+**自身时间(self time)Top 10:**
+
+| 采样数 | 占比 | 函数 | 说明 |
+|---|---|---|---|
+| 49 | 8.6% | `ByteStream::read_buf` | Gorilla/TS2DIFF 底层字节读取 |
+| 29 | 5.1% | `_platform_memmove` | 解压缩/向量拷贝中的内存搬运 |
+| 22 | 3.9% | `TableResultSet::next` | 行迭代器开销(仅 row 模式) |
+| 21 | 3.7% | `GorillaDecoder::read_long` | Gorilla 解码核心(逐值读 bit) |
+| 19 | 3.3% | `GorillaDecoder::flush_byte_if_empty` | Gorilla 字节刷新 |
+| 17 | 3.0% | `Field::set_value` | 行模式每行填充 Field 对象 |
+| 11 | 1.9% | `FixedLengthVector::append` | TsBlock 列追加 |
+| 10 | 1.8% | `double_DECODE_TV_BATCH` | 批量解码自身 |
+| 9 | 1.6% | `TS2DIFFDecoder::read_batch_int64` | 时间戳批量解码 |
+| 9 | 1.6% | `FixedLengthVector::read` | TsBlock 列读取 |
+
+**关键瓶颈分析:**
+
+1. **Gorilla 解码器(~25% 总时间)**:`GorillaDecoder::read_long` + `flush_byte_if_empty` + `ByteStream::read_buf` 合计约 140 采样。Gorilla 仍为逐值解码(bit-by-bit),是当前最大解码瓶颈。TS2DIFF 已有批量路径(仅 9 采样),Gorilla 尚无。
+2. **行模式 ResultSet 开销(~20% 总时间)**:`TableResultSet::next`(22)+ `Field::set_value`(17)+ `CaseInsensitiveHash/Equal`(~80 采样来自字符串列名 map 查找)。batch 模式完全跳过此层。
+3. **Snappy 解压**(~22 采样,3.9%):相对较小。
+4. **IO**(pread 9 采样,1.6%):数据已在 OS 缓存中,IO 不是瓶颈。
+
+**下一步优化方向(按收益排序):**
+
+| 优先级 | 优化 | 预期收益 |
+|---|---|---|
+| 高 | Gorilla 批量解码(`read_batch_float/double`) | 减少 Gorilla 25% 开销,预期 batch 路径再提速 30-50% |
+| 中 | 行模式列名查找缓存(column index) | row 模式提速 ~20%,但 batch 模式不受影响 |
+| 中 | TS2DIFF SIMD 解包(128-delta block) | 进一步优化时间戳解码,但当前仅 1.6%,收益有限 |
+| 低 | Arrow IPC 输出接口 | 打通 Arrow compute 生态 |
+
+---
+
+## 后续计划
+
+1. 运行 Baseline → 记录数据 → 确认各场景 TsFile 与 Parquet+Arrow 的差距
+2. 针对差距最大的场景,按上述优化方向逐步实施
+3. 每次优化后重新运行 Baseline 对比,量化收益
+4. 目标:Full Scan 与 Parquet+Arrow 持平或更快;Tag Filter 显著快于 Parquet+Arrow
diff --git "a/cpp/experiment/\350\265\204\346\272\220\345\217\227\351\231\220\346\235\241\344\273\266\344\270\213\347\232\204 TsFile \345\206\231\345\205\245\346\234\272\345\210\266-202603250503.md" "b/cpp/experiment/\350\265\204\346\272\220\345\217\227\351\231\220\346\235\241\344\273\266\344\270\213\347\232\204 TsFile \345\206\231\345\205\245\346\234\272\345\210\266-202603250503.md"
new file mode 100644
index 000000000..4cdf15d5f
--- /dev/null
+++ "b/cpp/experiment/\350\265\204\346\272\220\345\217\227\351\231\220\346\235\241\344\273\266\344\270\213\347\232\204 TsFile \345\206\231\345\205\245\346\234\272\345\210\266-202603250503.md"
@@ -0,0 +1,343 @@
+# 版本变更记录
+
+|**操作日期**|**文档版本**|**操作人**|**内容**|
+|:---|:---|:---|:---|
+|2026.3.25|V0.1(初始版本)|李烁麟|创建初始文档|
+
+
+|模型|开源/企业|是否需要更新用户手册|使用人员|
+|:---|:---|---|---|
+|通用|开源/企业版|是|外部|
+
+
+# 1. 需求背景
+
+TsFile C++ 被广泛部署于资源受限环境,包括嵌入式设备、工业边缘网关、容器化部署以及配置了内存限额的云实例。在这些场景中,进程可用内存通常在数十 MB 以内,而数据采集任务需要持续写入大量时序数据。
+
+当前 `TsFileWriter` / `TsFileTableWriter` 要求用户自行决定三个关键参数:
+
+- **每次写入多少行**(Tablet 的 `max_rows`):行数过多则单次内存峰值过高,行数过少则元数据增长过快。
+- **何时调用 `flush()`**:flush 决定数据何时从内存写入磁盘,直接影响内存水位。
+- **何时 `close()` 当前文件并打开新文件**:TsFile 的元数据在文件关闭前持续累积在内存中,若不切换文件,最终必然超出内存上限。
+
+这三个参数之间存在耦合关系,需要用户深入理解 TsFile 内存模型才能正确配置。对于不了解内部实现的用户,极易出现内存溢出或文件切换过于频繁导致性能劣化的问题。
+
+**典型场景示例:**
+
+某工厂部署了一台边缘网关,采集 50 台设备共 2500 条时序,每条序列持续采集 10 万个数据点,设备总内存 32MB,分配给写入进程的内存上限为 12MB。用户希望写完所有数据,但不知道应如何配置写入参数以避免 OOM。
+
+
+# 2. 产品调研
+
+## 2.1 客户使用场景调研
+
+根据资源受限环境下的典型部署,用户需求可归纳为以下三类:
+
+| 场景 | 描述 | 对写入参数的了解程度 |
+|:---|:---|:---|
+| 嵌入式/边缘设备 | 内存严格受限(< 32MB),长时间持续采集 | 几乎不了解,只知道总内存上限 |
+| 容器化部署 | 设置了 cgroup 内存限额,避免影响同节点其他服务 | 知道内存上限,不了解 TsFile 内部参数 |
+| 批量历史数据导入 | 将大批量历史数据写入 TsFile,内存有限 | 知道总数据量,但不了解最优批次大小 |
+
+三类场景的共同点:用户知道内存上限,但不知道应设置多大的批次和何时切换文件。
+
+## 2.2 常见竞品调研
+
+| 产品 | 内存控制方式 | 对用户的要求 |
+|:---|:---|:---|
+| Apache Parquet(C++)| 通过 `row_group_size` 控制每组数据大小 | 用户需自行估算每行字节数再换算 |
+| InfluxDB IOx | 内部自动 compaction,用户设置全局内存上限 | 全自动,无需用户干预 |
+| HDF5 | 通过 chunk size 控制分块 | 用户需手动设置,文档复杂 |
+| LevelDB / RocksDB | write buffer size 全局配置 | 用户设置 buffer 大小,无文件切换概念 |
+
+**差距分析:** 相比 InfluxDB IOx 等新一代系统,TsFile C++ 当前将内存管理的复杂性完全暴露给用户。本需求目标是提供类似"全自动"的写入体验,同时保留用户对参数的可见性和可调性。
+
+
+# 3. 功能定义
+
+## 3.1 功能描述
+
+提供 `MemConstrainedWriter` 类,用户只需指定**内存上限**和**表结构(TableSchema)**,写入器自动完成以下工作:
+
+1. 根据内存上限和 schema 计算推荐的每次写入行数(`rows_per_tablet`)。
+2. 每次 `write_table()` 调用后自动执行 `flush()`,将数据落盘释放内存。
+3. 追踪元数据累积量,在接近内存上限时自动 `close()` 当前文件并打开下一个文件。
+4. 管理输出文件的命名和编号,用户完成后可查询所有生成的文件列表。
+
+用户无需手动调用 `flush()` 和 `close()`,无需了解 TsFile 的内存模型细节。
+
+## 3.2 功能定义
+
+### 3.2.1 写入策略计算(`compute_strategy`)
+
+提供静态方法,根据内存约束和 schema 计算写入策略,可独立于写入器使用:
+
+**输入:**
+- `mem_limit_bytes`:内存上限(字节)
+- `table_schema`:表结构,包含列名、数据类型、列类别(TAG/FIELD)
+- `n_devices`:预期设备数量(唯一 TAG 组合数),填 0 表示未知
+- `total_rows_per_device`:每设备总行数,填 -1 表示未知
+
+**输出(`WritingStrategy`):**
+
+| 字段 | 含义 |
+|:---|:---|
+| `rows_per_tablet` | 建议的每次 Tablet 行数(F) |
+| `flushes_per_file` | 每个文件最多 flush 次数(K),-1 表示无需切换文件 |
+| `estimated_peak_mem` | 按此策略的预估峰值内存(字节) |
+
+**计算策略:**
+
+- 若 `total_rows_per_device > 0` 且 `n_devices > 0`:采用 EOQ 最优解,最小化内存占用。
+ ```
+ F = √(total_rows × n_devices × b / s)
+ K = total_rows / F
+ ```
+- 否则(流式写入或设备数未知):采用对半分预算策略,最大化单文件容量。
+ ```
+ F = M_avail / (2 × s)
+ K = M_avail / (2 × n_devices × b)
+ ```
+
+其中 `s` 为每行综合字节数,`b` 为每设备每次 flush 的元数据增量,均由 schema 自动推导。
+
+### 3.2.2 受限写入器(`MemConstrainedWriter`)
+
+**构造参数:**
+
+| 参数 | 类型 | 说明 |
+|:---|:---|:---|
+| `mem_limit_bytes` | `int64_t` | 内存上限(字节) |
+| `output_dir` | `string` | 输出目录 |
+| `file_prefix` | `string` | 文件名前缀,生成 `prefix_0000.tsfile`、`prefix_0001.tsfile` ... |
+| `table_schema` | `shared_ptr<TableSchema>` | 表结构 |
+| `n_devices` | `int` | 设备数,默认为 0(未知,自动追踪) |
+| `total_rows_per_device` | `int64_t` | 每设备总行数,默认为 -1(未知) |
+
+**主要方法:**
+
+| 方法 | 说明 |
+|:---|:---|
+| `write_table(tablet)` | 写入数据,自动 flush,必要时自动切换文件 |
+| `close()` | 完成所有写入,关闭最后一个文件 |
+| `rows_per_tablet()` | 返回建议的 Tablet 行数,用于创建 Tablet |
+| `strategy()` | 返回当前生效的写入策略(可用于日志和诊断) |
+| `created_files()` | 返回本次写入创建的所有文件路径列表 |
+
+### 3.2.3 设备数未知时的自适应追踪
+
+当 `n_devices = 0` 时,写入器进入动态追踪模式:
+
+- 每次 `write_table()` 后调用 `tablet.find_all_device_boundaries()` 获取本次 Tablet 中的设备数。
+- 若发现新设备(超过历史最大值),实时更新元数据预算估算,收紧切换阈值。
+- 该方法基于列的逐元比较实现,性能优于逐行扫描。
+- rotate 后重置设备计数,从下一个文件重新开始追踪。
+
+## 3.3 非功能定义
+
+### 3.3.1 性能要求
+
+| 指标 | 要求 |
+|:---|:---|
+| rotate 判断开销 | 每次 `flush()` 后执行一次整数乘法,延迟 < 1 μs |
+| 内存监控方式 | 不依赖 `ModStat`(`ATOMIC_FAA` 热路径),仅使用公式估算 |
+| 内存控制精度 | 实际峰值内存不超过用户指定上限的 115%(公式误差 + 安全裕度) |
+| 写入吞吐影响 | 相比直接使用 `TsFileTableWriter`,额外开销 < 1% |
+
+安全裕度(`SAFETY_FACTOR = 0.85`):在元数据预算耗尽前 15% 触发 rotate,用于吸收公式估算误差。
+
+### 3.3.2 兼容性要求
+
+**功能兼容说明表:**
+
+| 兼容项 | 说明 |
+|:---|:---|
+| 与 `TsFileTableWriter` 的接口兼容 | `write_table()` 签名与 `TsFileTableWriter::write_table()` 完全一致,迁移成本低 |
+| 与现有 TableSchema 兼容 | 直接接受 `shared_ptr<TableSchema>`,无需额外转换 |
+| 生成文件的读取兼容 | 生成的 `.tsfile` 文件与标准 TsFile 完全兼容,可用现有读取接口读取 |
+| 平台兼容 | Linux / macOS / Windows(与 TsFile C++ 现有平台支持一致) |
+
+### 3.3.3 安全性要求
+
+|自查内容|说明|自查结果|
+|:---|:---|:---|
+|身份验证和授权|该功能是否涉及用户身份验证逻辑?|不涉及|
+||是否确保仅通过验证的合法用户可使用功能?|不涉及|
+||是否确保仅被授予权限的合法用户可使用功能?|不涉及|
+|完整性|该功能是否能保证数据在传输、计算和存储过程中不被泄露、篡改或越权访问?|不涉及|
+|审计和日志记录|该功能的关键操作是否需要被记录到审计日志中?|不涉及|
+||该功能的审计日志是否包含"操作人、操作时间、操作内容、客户端 IP、操作结果"等关键信息?|不涉及|
+|错误处理和异常管理|该功能是否合理处理运行时错误,避免报错信息泄露敏感内容?|不涉及|
+||是否监控异常行为,并触发告警或限制操作?|不涉及|
+||异常日志是否仅记录必要信息,不包含敏感数据?|不涉及|
+|安全配置|该功能相关的默认配置是否安全?|不涉及|
+||是否涉及密码明文输入?|不涉及|
+|输入验证|该功能是否对所有用户输入进行验证,如数据类型、格式、范围校验?|`mem_limit_bytes` 需校验 > M_INIT_BYTES(约 900KB),否则返回错误|
+
+#### 3.3.3.1 访问控制
+
+无
+
+#### 3.3.3.2 审计和日志记录
+
+无
+
+### 3.3.4 手册要求
+
+需在用户手册中补充以下内容:
+
+1. `MemConstrainedWriter` 的使用说明,包含构造参数含义与推荐配置。
+2. `compute_strategy()` 的独立使用示例(仅计算策略,不执行写入)。
+3. 输出文件的命名规则说明(`prefix_NNNN.tsfile`)。
+4. 常见问题:内存上限过小导致单个文件无法容纳任何数据时的报错说明。
+
+### 3.3.5 创新点
+
+1. **公式驱动的零开销内存控制**:基于 TsFile 内存模型(M_init + M_data + M_meta)推导出解析解,不引入任何运行时内存采样开销。
+
+2. **EOQ 模型应用**:将经济订货量(Economic Order Quantity)模型引入时序写入场景,在数据批次大小(决定内存峰值)与 flush 次数(决定元数据增量)之间求得最优平衡点。
+
+3. **自适应设备追踪**:利用 `find_all_device_boundaries()` 的列式比较实现,以极低开销实现设备数量的动态感知,在设备数未知场景下仍能保证内存不超限。
+
+
+## 3.5 场景示例
+
+### 场景一:已知设备数和总数据量(最优配置)
+
+```cpp
+// 50 个设备,每设备 10 万行,内存上限 10MB
+auto schema = std::make_shared<TableSchema>("sensor", columns);
+
+MemConstrainedWriter writer(
+ 10 * 1024 * 1024, // 10MB 内存上限
+ "/data/output",
+ "sensor",
+ schema,
+ 50, // 50 个设备(已知)
+ 100000 // 每设备 10 万行(已知)
+);
+
+// 查询推荐的 Tablet 行数
+int F = writer.rows_per_tablet(); // 系统自动计算最优批次大小
+
+while (has_more_data) {
+ Tablet tablet("sensor", schema_vec, F);
+ fill_tablet(tablet, cur_row, F);
+ writer.write_table(tablet); // flush / rotate 全自动
+}
+writer.close();
+
+// 查看生成的文件列表
+for (const auto& path : writer.created_files()) {
+ process(path); // /data/output/sensor_0000.tsfile, sensor_0001.tsfile ...
+}
+```
+
+### 场景二:设备数未知(动态追踪模式)
+
+```cpp
+// 不知道有多少设备,只知道内存上限
+MemConstrainedWriter writer(
+ 8 * 1024 * 1024, // 8MB 内存上限
+ "/data/output",
+ "data",
+ schema // n_devices 省略,默认 0(动态追踪)
+);
+
+int F = writer.rows_per_tablet();
+
+while (stream.has_next()) {
+ auto batch = stream.next_batch(F); // 每批 F 行,设备 TAG 值不固定
+ writer.write_table(batch);
+ // 内部自动统计本次 Tablet 包含多少个设备,
+ // 动态调整切换阈值,保证内存不超限
+}
+writer.close();
+```
+
+### 场景三:仅查询策略,不创建写入器
+
+```cpp
+// 在正式写入前预估策略
+auto strategy = MemConstrainedWriter::compute_strategy(
+ 12 * 1024 * 1024, // 12MB
+ schema,
+ 50, // 50 设备
+ 100000 // 10 万行
+);
+
+printf("推荐批次大小: %ld 行\n", strategy.rows_per_tablet);
+printf("每文件最多 flush: %ld 次\n", strategy.flushes_per_file);
+printf("预估峰值内存: %.1f MB\n", strategy.estimated_peak_mem / 1e6);
+```
+
+## 3.6 完成计划
+
+|功能内容|预计排期|
+|:---|:---|
+|3.2.1 `compute_strategy()` 策略计算函数|本版本完成|
+|3.2.2 `MemConstrainedWriter` 核心类(已知设备数模式)|本版本完成|
+|3.2.3 `MemConstrainedWriter` 动态设备追踪模式|本版本完成|
+|3.3.1 性能要求(零 ModStat 依赖、< 1% 吞吐影响)|本版本完成|
+|3.3.2 兼容性要求|本版本完成|
+|3.3.4 用户手册补充|本版本完成|
+|多表(多个 TableSchema)写入支持|暂未涉及,进入需求池|
+|Tree 模式(非 Table 模式)的受限写入支持|暂未涉及,进入需求池|
+|并发写入场景|暂未涉及,进入需求池|
+
+
+# 4. 自测及测试场景建议
+
+## 4.1 策略计算正确性
+
+| 测试用例 | 输入 | 期望结果 |
+|:---|:---|:---|
+| 总量已知,内存充足 | M=12MB, D=50, R=100000, int32×50列 | F≈7500, K≈22, peak≈11MB |
+| 总量已知,内存紧张(EOQ 超限) | M=5MB, D=50, R=100000, int32×50列 | 退化为对半分策略 |
+| 总量未知,设备数已知 | M=10MB, D=50, total=-1 | F=M_avail/(2s),K=M_avail/(2B) |
+| 设备数未知 | M=10MB, D=0, total=-1 | D 初始按 1 估算,不报错 |
+| 内存上限过小 | M=500KB | 返回错误或 F=1(最小值) |
+
+## 4.2 自动 flush 与 rotate 行为
+
+| 测试用例 | 验证点 |
+|:---|:---|
+| 写入 K+1 次 Tablet 后,应生成第 2 个文件 | `created_files().size() == 2` |
+| rotate 后 flush_count 重置为 0 | 下一文件能容纳完整 K 次 flush |
+| 最后一个文件 `close()` 后可正常读取 | 用 `TsFileReader` 验证数据完整性 |
+| 总写入数据量与各文件数据量之和一致 | 无数据丢失或重复 |
+
+## 4.3 动态追踪模式
+
+| 测试用例 | 验证点 |
+|:---|:---|
+| 前几批 Tablet 设备少,后续增多 | K 随设备数增多而收紧,最终 rotate 时机不晚于已知模式 |
+| 每批 Tablet 只含 1 个设备 | 逐步发现设备,不提前 rotate |
+| rotate 后首批 Tablet 包含所有设备 | 立即收紧 K,行为与已知模式一致 |
+
+## 4.4 内存上限保证
+
+| 测试用例 | 验证点 |
+|:---|:---|
+| 全程写入,用 Valgrind/Massif 测量峰值 | 实际峰值 ≤ mem_limit_bytes × 1.15 |
+| STRING 列场景 | 内存不超限(32字节估算 + 安全裕度) |
+
+## 4.5 生成文件可读性
+
+| 测试用例 | 验证点 |
+|:---|:---|
+| 多文件场景,用标准接口逐一读取 | 数据类型、值、时间戳与写入一致 |
+| 最后一个文件(非满文件)可正常读取 | 边界条件不损坏文件结构 |
+
+
+# 附录:历史内容及讨论记录
+
+## 2026年3月25日 内部讨论
+
+- 讨论关键记录及结论:
+ - 内存模型确认:M_total = M_init + M_data + M_meta,三部分特征不同(固定/周期/单调递增)。
+ - 确认 M_init ≈ 900KB,M_data = s×F(flush 后释放),M_meta = K×D×b(close 才写盘)。
+ - 公式误差实验验证:5–15%,通过 SAFETY_FACTOR=0.85 吸收。
+ - 排除 ModStat 方案:`ATOMIC_FAA` 热路径开销在嵌入式场景不可接受,且精度超出所需。
+ - 确认使用 `find_all_device_boundaries()` 进行动态设备追踪:列式比较,性能优于逐行扫描。
+ - 设计决策:直接包装 `TsFileWriter`(而非 `TsFileTableWriter`),以便 rotate 时精确控制 `open()` 和 `register_table()`。
diff --git a/cpp/src/CMakeLists.txt b/cpp/src/CMakeLists.txt
index e922836b7..5e5f6d899 100644
--- a/cpp/src/CMakeLists.txt
+++ b/cpp/src/CMakeLists.txt
@@ -37,6 +37,9 @@ message("cmake using: ENABLE_LZOKAY=${ENABLE_LZOKAY}")
option(ENABLE_ZLIB "Enable Zlib compression" ON)
message("cmake using: ENABLE_ZLIB=${ENABLE_ZLIB}")
+option(ENABLE_SIMD "Enable SIMD acceleration for TS2DIFF codec via SIMDe" ON)
+message("cmake using: ENABLE_SIMD=${ENABLE_SIMD}")
+
message("Running in src directory")
if (${COV_ENABLED})
add_compile_options(-fprofile-arcs -ftest-coverage)
@@ -86,6 +89,13 @@ if (ENABLE_ANTLR4)
message("Adding ANTLR4 include directory")
endif()
+if (ENABLE_SIMD)
+ add_definitions(-DENABLE_SIMD)
+ list(APPEND PROJECT_INCLUDE_DIR
+ ${CMAKE_SOURCE_DIR}/third_party/simde-0.8.4-rc3
+ )
+endif()
+
include_directories(${PROJECT_INCLUDE_DIR})
if (ENABLE_ANTLR4)
diff --git a/cpp/src/common/thread_pool.h b/cpp/src/common/thread_pool.h
new file mode 100644
index 000000000..cff60c355
--- /dev/null
+++ b/cpp/src/common/thread_pool.h
@@ -0,0 +1,102 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * License); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+#ifndef COMMON_THREAD_POOL_H
+#define COMMON_THREAD_POOL_H
+
+#include <condition_variable>
+#include <functional>
+#include <mutex>
+#include <queue>
+#include <thread>
+#include <vector>
+
+namespace common {
+
+// A simple fixed-size thread pool for parallel decode tasks.
+// Usage:
+// DecodeThreadPool pool(4);
+// pool.submit([&] { decode_column_0(); });
+// pool.submit([&] { decode_column_1(); });
+// pool.wait_all();
+class DecodeThreadPool {
+ public:
+ explicit DecodeThreadPool(int num_threads) : stop_(false), active_(0) {
+ for (int i = 0; i < num_threads; i++) {
+ workers_.emplace_back([this] { worker_loop(); });
+ }
+ }
+
+ ~DecodeThreadPool() {
+ {
+ std::lock_guard<std::mutex> lk(mu_);
+ stop_ = true;
+ }
+ cv_work_.notify_all();
+ for (auto& w : workers_) {
+ if (w.joinable()) w.join();
+ }
+ }
+
+ void submit(std::function<void()> task) {
+ {
+ std::lock_guard<std::mutex> lk(mu_);
+ tasks_.push(std::move(task));
+ active_++;
+ }
+ cv_work_.notify_one();
+ }
+
+ // Block until all submitted tasks have completed.
+ void wait_all() {
+ std::unique_lock<std::mutex> lk(mu_);
+ cv_done_.wait(lk, [this] { return active_ == 0 && tasks_.empty(); });
+ }
+
+ private:
+ void worker_loop() {
+ while (true) {
+ std::function<void()> task;
+ {
+ std::unique_lock<std::mutex> lk(mu_);
+ cv_work_.wait(lk, [this] { return stop_ || !tasks_.empty(); });
+ if (stop_ && tasks_.empty()) return;
+ task = std::move(tasks_.front());
+ tasks_.pop();
+ }
+ task();
+ {
+ std::lock_guard<std::mutex> lk(mu_);
+ active_--;
+ }
+ cv_done_.notify_one();
+ }
+ }
+
+ std::vector<std::thread> workers_;
+ std::queue<std::function<void()>> tasks_;
+ std::mutex mu_;
+ std::condition_variable cv_work_;
+ std::condition_variable cv_done_;
+ bool stop_;
+ int active_;
+};
+
+} // namespace common
+
+#endif // COMMON_THREAD_POOL_H
diff --git a/cpp/src/common/tsblock/tsblock.h b/cpp/src/common/tsblock/tsblock.h
index 859ad393d..df092ebb2 100644
--- a/cpp/src/common/tsblock/tsblock.h
+++ b/cpp/src/common/tsblock/tsblock.h
@@ -144,6 +144,12 @@ class RowAppender {
ASSERT(tsblock_->row_count_ > 0);
tsblock_->row_count_--;
}
+ FORCE_INLINE uint32_t remaining() const {
+ return tsblock_->max_row_count_ - tsblock_->row_count_;
+ }
+ FORCE_INLINE void add_rows(uint32_t count) {
+ tsblock_->row_count_ += count;
+ }
FORCE_INLINE void append(uint32_t slot_index, const char* value,
uint32_t len) {
@@ -222,6 +228,13 @@ class ColAppender {
}
FORCE_INLINE void reset() { column_row_count_ = 0; }
+ FORCE_INLINE void bulk_append_fixed(const char* data, uint32_t count,
+ uint32_t elem_size) {
+ vec_->get_value_data().append_fixed_value(data, count * elem_size);
+ vec_->add_row_nums(count);
+ column_row_count_ += count;
+ }
+
private:
uint32_t column_index_;
uint32_t column_row_count_;
@@ -311,6 +324,17 @@ class ColIterator {
FORCE_INLINE uint32_t get_column_index() { return column_index_; }
+ FORCE_INLINE uint32_t remaining() const {
+ return tsblock_->row_count_ - row_id_;
+ }
+ FORCE_INLINE char* data_ptr() {
+ return vec_->get_value_data().get_data() + vec_->get_offset();
+ }
+ FORCE_INLINE void advance(uint32_t n, uint32_t elem_size) {
+ row_id_ += n;
+ vec_->advance_offset(n * elem_size);
+ }
+
private:
uint32_t column_index_;
uint32_t row_id_;
diff --git a/cpp/src/common/tsblock/vector/vector.h b/cpp/src/common/tsblock/vector/vector.h
index 37a96c543..dde3e76cc 100644
--- a/cpp/src/common/tsblock/vector/vector.h
+++ b/cpp/src/common/tsblock/vector/vector.h
@@ -73,6 +73,9 @@ class Vector {
FORCE_INLINE uint32_t get_row_num() { return row_num_; }
FORCE_INLINE void add_row_num() { row_num_++; }
+ FORCE_INLINE void add_row_nums(uint32_t n) { row_num_ += n; }
+ FORCE_INLINE uint32_t get_offset() const { return offset_; }
+ FORCE_INLINE void advance_offset(uint32_t bytes) { offset_ += bytes; }
FORCE_INLINE common::TsBlock* get_tsblock() { return tsblock_; }
diff --git a/cpp/src/common/tsfile_common.h b/cpp/src/common/tsfile_common.h
index c866e4995..bb180abee 100644
--- a/cpp/src/common/tsfile_common.h
+++ b/cpp/src/common/tsfile_common.h
@@ -314,6 +314,11 @@ class ITimeseriesIndex {
virtual common::SimpleList<ChunkMeta*>* get_value_chunk_meta_list() const {
return nullptr;
}
+ virtual uint32_t get_value_column_count() const { return 1; }
+ virtual common::SimpleList<ChunkMeta*>* get_value_chunk_meta_list(
+ uint32_t col_index) const {
+ return col_index == 0 ? get_value_chunk_meta_list() : nullptr;
+ }
virtual common::String get_measurement_name() const {
return common::String();
@@ -605,6 +610,46 @@ class AlignedTimeseriesIndex : public ITimeseriesIndex {
#endif
};
+class MultiAlignedTimeseriesIndex : public ITimeseriesIndex {
+ public:
+ TimeseriesIndex* time_ts_idx_ = nullptr;
+ std::vector<TimeseriesIndex*> value_ts_idxs_;
+
+ MultiAlignedTimeseriesIndex() {}
+ ~MultiAlignedTimeseriesIndex() {}
+
+ common::SimpleList<ChunkMeta*>* get_time_chunk_meta_list() const override {
+ return time_ts_idx_ ? time_ts_idx_->get_chunk_meta_list() : nullptr;
+ }
+ common::SimpleList<ChunkMeta*>* get_value_chunk_meta_list() const override {
+ return value_ts_idxs_.empty()
+ ? nullptr
+ : value_ts_idxs_[0]->get_chunk_meta_list();
+ }
+ uint32_t get_value_column_count() const override {
+ return value_ts_idxs_.size();
+ }
+ common::SimpleList<ChunkMeta*>* get_value_chunk_meta_list(
+ uint32_t col_index) const override {
+ return col_index < value_ts_idxs_.size()
+ ? value_ts_idxs_[col_index]->get_chunk_meta_list()
+ : nullptr;
+ }
+ common::String get_measurement_name() const override {
+ return value_ts_idxs_.empty() ? common::String()
+ : value_ts_idxs_[0]->get_measurement_name();
+ }
+ common::TSDataType get_data_type() const override {
+ return time_ts_idx_ ? time_ts_idx_->get_data_type()
+ : common::INVALID_DATATYPE;
+ }
+ Statistic* get_statistic() const override { return nullptr; }
+
+ const std::vector<TimeseriesIndex*>& get_value_indices() const {
+ return value_ts_idxs_;
+ }
+};
+
class TSMIterator {
public:
explicit TSMIterator(
diff --git a/cpp/src/encoding/decoder.h b/cpp/src/encoding/decoder.h
index c290b5791..1a3cba596 100644
--- a/cpp/src/encoding/decoder.h
+++ b/cpp/src/encoding/decoder.h
@@ -21,6 +21,7 @@
#define ENCODING_DECODER_H
#include "common/allocator/byte_stream.h"
+#include "common/db_common.h"
namespace storage {
@@ -37,6 +38,124 @@ class Decoder {
virtual int read_double(double& ret_value, common::ByteStream& in) = 0;
virtual int read_String(common::String& ret_value, common::PageArena& pa,
common::ByteStream& in) = 0;
+
+ virtual int read_batch_int32(int32_t* out, int capacity, int& actual,
+ common::ByteStream& in) {
+ actual = 0;
+ int ret = common::E_OK;
+ int32_t val;
+ while (actual < capacity && has_remaining(in)) {
+ ret = read_int32(val, in);
+ if (ret != common::E_OK) { return ret; }
+ out[actual++] = val;
+ }
+ return common::E_OK;
+ }
+
+ virtual int read_batch_int64(int64_t* out, int capacity, int& actual,
+ common::ByteStream& in) {
+ actual = 0;
+ int ret = common::E_OK;
+ int64_t val;
+ while (actual < capacity && has_remaining(in)) {
+ ret = read_int64(val, in);
+ if (ret != common::E_OK) { return ret; }
+ out[actual++] = val;
+ }
+ return common::E_OK;
+ }
+
+ virtual int read_batch_float(float* out, int capacity, int& actual,
+ common::ByteStream& in) {
+ actual = 0;
+ int ret = common::E_OK;
+ float val;
+ while (actual < capacity && has_remaining(in)) {
+ ret = read_float(val, in);
+ if (ret != common::E_OK) { return ret; }
+ out[actual++] = val;
+ }
+ return common::E_OK;
+ }
+
+ virtual int read_batch_double(double* out, int capacity, int& actual,
+ common::ByteStream& in) {
+ actual = 0;
+ int ret = common::E_OK;
+ double val;
+ while (actual < capacity && has_remaining(in)) {
+ ret = read_double(val, in);
+ if (ret != common::E_OK) { return ret; }
+ out[actual++] = val;
+ }
+ return common::E_OK;
+ }
+
+ virtual int skip_int32(int count, int& skipped, common::ByteStream& in) {
+ skipped = 0;
+ int ret = common::E_OK;
+ int32_t dummy;
+ while (skipped < count && has_remaining(in)) {
+ ret = read_int32(dummy, in);
+ if (ret != common::E_OK) { return ret; }
+ ++skipped;
+ }
+ return common::E_OK;
+ }
+
+ virtual int skip_int64(int count, int& skipped, common::ByteStream& in) {
+ skipped = 0;
+ int ret = common::E_OK;
+ int64_t dummy;
+ while (skipped < count && has_remaining(in)) {
+ ret = read_int64(dummy, in);
+ if (ret != common::E_OK) { return ret; }
+ ++skipped;
+ }
+ return common::E_OK;
+ }
+
+ virtual int skip_float(int count, int& skipped, common::ByteStream& in) {
+ skipped = 0;
+ int ret = common::E_OK;
+ float dummy;
+ while (skipped < count && has_remaining(in)) {
+ ret = read_float(dummy, in);
+ if (ret != common::E_OK) { return ret; }
+ ++skipped;
+ }
+ return common::E_OK;
+ }
+
+ virtual int skip_double(int count, int& skipped, common::ByteStream& in) {
+ skipped = 0;
+ int ret = common::E_OK;
+ double dummy;
+ while (skipped < count && has_remaining(in)) {
+ ret = read_double(dummy, in);
+ if (ret != common::E_OK) { return ret; }
+ ++skipped;
+ }
+ return common::E_OK;
+ }
+
+ // Block-level filter check: peek the next block header and compute
+ // the value range [block_min, block_max] without decoding.
+ // Returns true if a block was peeked; false if not supported or no data.
+ // After peeking, caller must either:
+ // - Call skip_peeked_block_int64() to skip the block
+ // - Call read_batch_int64() which will use the peeked header
+ virtual bool peek_next_block_range_int64(common::ByteStream& in,
+ int64_t& block_min,
+ int64_t& block_max,
+ int& block_count) {
+ return false;
+ }
+
+ // Skip the block whose header was already consumed by peek.
+ virtual int skip_peeked_block_int64(common::ByteStream& in, int& skipped) {
+ return common::E_NOT_SUPPORT;
+ }
};
} // end namespace storage
diff --git a/cpp/src/encoding/gorilla_decoder.h b/cpp/src/encoding/gorilla_decoder.h
index 5684561aa..bd609ca14 100644
--- a/cpp/src/encoding/gorilla_decoder.h
+++ b/cpp/src/encoding/gorilla_decoder.h
@@ -30,6 +30,145 @@
namespace storage {
+// ── Raw-pointer bit reader ────────────────────────────────────────────────
+// Operates directly on a contiguous byte array, bypassing ByteStream's
+// per-byte read_buf() overhead (atomic loads, page boundary checks, memcpy).
+
+struct GorillaBitReader {
+ const uint8_t* data;
+ uint32_t pos; // next byte index to load
+ uint32_t data_len; // total bytes
+ int bits; // remaining bits in cur_byte (0..8)
+ uint8_t cur_byte;
+
+ FORCE_INLINE void load_byte_if_empty() {
+ if (bits == 0 && pos < data_len) {
+ cur_byte = data[pos++];
+ bits = 8;
+ }
+ }
+
+ FORCE_INLINE bool read_bit() {
+ bool bit = ((cur_byte >> (bits - 1)) & 1) == 1;
+ bits--;
+ load_byte_if_empty();
+ return bit;
+ }
+
+ FORCE_INLINE int64_t read_long(int n) {
+ int64_t value = 0;
+ while (n > 0) {
+ if (n > bits || n == 8) {
+ value = (value << bits) + (cur_byte & ((1 << bits) - 1));
+ n -= bits;
+ bits = 0;
+ } else {
+ value = (value << n) +
+ ((cur_byte >> (bits - n)) & ((1 << n) - 1));
+ bits -= n;
+ n = 0;
+ }
+ load_byte_if_empty();
+ }
+ return value;
+ }
+
+ FORCE_INLINE uint8_t read_control_bits(int max_bits) {
+ uint8_t value = 0x00;
+ for (int i = 0; i < max_bits; i++) {
+ value <<= 1;
+ if (read_bit()) {
+ value |= 0x01;
+ } else {
+ break;
+ }
+ }
+ return value;
+ }
+};
+
+// ── Templated raw-pointer decode helpers ──────────────────────────────────
+
+template <typename T>
+struct GorillaRawOps {
+ static FORCE_INLINE T read_next(GorillaBitReader& r,
+ T& stored_value,
+ int& stored_leading_zeros,
+ int& stored_trailing_zeros);
+};
+
+template <>
+struct GorillaRawOps<int32_t> {
+ static constexpr int VALUE_BITS = VALUE_BITS_LENGTH_32BIT;
+
+ static FORCE_INLINE int32_t read_next(GorillaBitReader& r,
+ int32_t& stored_value,
+ int& stored_leading_zeros,
+ int& stored_trailing_zeros) {
+ uint8_t ctrl = r.read_control_bits(2);
+ switch (ctrl) {
+ case 3: {
+ stored_leading_zeros =
+ (int)r.read_long(LEADING_ZERO_BITS_LENGTH_32BIT);
+ uint8_t sig =
+ (uint8_t)r.read_long(MEANINGFUL_XOR_BITS_LENGTH_32BIT);
+ sig++;
+ stored_trailing_zeros =
+ VALUE_BITS - sig - stored_leading_zeros;
+ }
+ // fallthrough
+ case 2: {
+ int32_t xor_value = (int32_t)r.read_long(
+ VALUE_BITS - stored_leading_zeros - stored_trailing_zeros);
+ xor_value = static_cast<uint32_t>(xor_value)
+ << stored_trailing_zeros;
+ stored_value ^= xor_value;
+ }
+ // fallthrough
+ default:
+ return stored_value;
+ }
+ return stored_value;
+ }
+};
+
+template <>
+struct GorillaRawOps<int64_t> {
+ static constexpr int VALUE_BITS = VALUE_BITS_LENGTH_64BIT;
+
+ static FORCE_INLINE int64_t read_next(GorillaBitReader& r,
+ int64_t& stored_value,
+ int& stored_leading_zeros,
+ int& stored_trailing_zeros) {
+ uint8_t ctrl = r.read_control_bits(2);
+ switch (ctrl) {
+ case 3: {
+ stored_leading_zeros =
+ (int)r.read_long(LEADING_ZERO_BITS_LENGTH_64BIT);
+ uint8_t sig =
+ (uint8_t)r.read_long(MEANINGFUL_XOR_BITS_LENGTH_64BIT);
+ sig++;
+ stored_trailing_zeros =
+ VALUE_BITS - sig - stored_leading_zeros;
+ }
+ // fallthrough
+ case 2: {
+ int64_t xor_value = r.read_long(
+ VALUE_BITS - stored_leading_zeros - stored_trailing_zeros);
+ xor_value = static_cast<uint64_t>(xor_value)
+ << stored_trailing_zeros;
+ stored_value ^= xor_value;
+ }
+ // fallthrough
+ default:
+ return stored_value;
+ }
+ return stored_value;
+ }
+};
+
+// ──────────────────────────────────────────────────────────────────────────
+
template <typename T>
class GorillaDecoder : public Decoder {
public:
@@ -127,6 +266,160 @@ class GorillaDecoder : public Decoder {
int read_String(common::String& ret_value, common::PageArena& pa,
common::ByteStream& in) override;
+ // Batch overrides — declared here, defined after template specializations
+ int read_batch_int32(int32_t* out, int capacity, int& actual,
+ common::ByteStream& in) override;
+ int read_batch_int64(int64_t* out, int capacity, int& actual,
+ common::ByteStream& in) override;
+ int skip_int32(int count, int& skipped,
+ common::ByteStream& in) override;
+ int skip_int64(int count, int& skipped,
+ common::ByteStream& in) override;
+
+ protected:
+ // ── Batch decode using raw pointer (bypasses ByteStream) ─────────────
+ // The decode() contract:
+ // stored_value_ holds the "next" value to be returned.
+ // decode() returns stored_value_, then advances via cache_next().
+ // has_next_==false means the ending sentinel was hit.
+ //
+ // batch_decode_raw replicates this logic using GorillaBitReader on the
+ // wrapped contiguous buffer, then syncs state back to ByteStream.
+ int batch_decode_raw(T* out, int capacity, int& actual, T ending,
+ common::ByteStream& in) {
+ if (!in.is_wrapped()) {
+ return batch_decode_fallback(out, capacity, actual, ending, in);
+ }
+
+ const uint8_t* base =
+ (const uint8_t*)in.get_wrapped_buf() + in.read_pos();
+ uint32_t remain = in.remaining_size();
+
+ GorillaBitReader r;
+ r.data = base;
+ r.pos = 0;
+ r.data_len = remain;
+ r.bits = bits_left_;
+ r.cur_byte = buffer_;
+
+ actual = 0;
+
+ // Bootstrap first value if needed (mirrors decode()'s first-call path)
+ if (UNLIKELY(!first_value_was_read_)) {
+ if (r.bits == 0 && r.pos >= r.data_len) goto done;
+ r.load_byte_if_empty();
+ stored_value_ =
+ (T)r.read_long(GorillaRawOps<T>::VALUE_BITS);
+ first_value_was_read_ = true;
+ // Save the first value before cache_next mutates stored_value_
+ T first_value = stored_value_;
+ // cache_next: read_next then check ending
+ GorillaRawOps<T>::read_next(r, stored_value_,
+ stored_leading_zeros_,
+ stored_trailing_zeros_);
+ if (stored_value_ == ending) {
+ has_next_ = false;
+ } else {
+ has_next_ = true;
+ }
+ // Output the first value
+ out[actual++] = first_value;
+ if (!has_next_ || actual >= capacity) goto done;
+ }
+
+ // Main batch loop
+ while (actual < capacity && has_next_) {
+ out[actual++] = stored_value_;
+ GorillaRawOps<T>::read_next(r, stored_value_,
+ stored_leading_zeros_,
+ stored_trailing_zeros_);
+ if (stored_value_ == ending) {
+ has_next_ = false;
+ }
+ }
+
+ done:
+ // Sync bit-reader state back
+ buffer_ = r.cur_byte;
+ bits_left_ = r.bits;
+ in.wrapped_buf_advance_read_pos(r.pos);
+ return common::E_OK;
+ }
+
+ int batch_skip_raw(int count, int& skipped, T ending,
+ common::ByteStream& in) {
+ if (!in.is_wrapped()) {
+ return batch_skip_fallback(count, skipped, ending, in);
+ }
+
+ const uint8_t* base =
+ (const uint8_t*)in.get_wrapped_buf() + in.read_pos();
+ uint32_t remain = in.remaining_size();
+
+ GorillaBitReader r;
+ r.data = base;
+ r.pos = 0;
+ r.data_len = remain;
+ r.bits = bits_left_;
+ r.cur_byte = buffer_;
+
+ skipped = 0;
+
+ if (UNLIKELY(!first_value_was_read_)) {
+ if (r.bits == 0 && r.pos >= r.data_len) goto done;
+ r.load_byte_if_empty();
+ stored_value_ =
+ (T)r.read_long(GorillaRawOps<T>::VALUE_BITS);
+ first_value_was_read_ = true;
+ GorillaRawOps<T>::read_next(r, stored_value_,
+ stored_leading_zeros_,
+ stored_trailing_zeros_);
+ if (stored_value_ == ending) {
+ has_next_ = false;
+ } else {
+ has_next_ = true;
+ }
+ // The first value counts as one skip
+ skipped++;
+ if (!has_next_ || skipped >= count) goto done;
+ }
+
+ while (skipped < count && has_next_) {
+ skipped++;
+ GorillaRawOps<T>::read_next(r, stored_value_,
+ stored_leading_zeros_,
+ stored_trailing_zeros_);
+ if (stored_value_ == ending) {
+ has_next_ = false;
+ }
+ }
+
+ done:
+ buffer_ = r.cur_byte;
+ bits_left_ = r.bits;
+ in.wrapped_buf_advance_read_pos(r.pos);
+ return common::E_OK;
+ }
+
+ int batch_decode_fallback(T* out, int capacity, int& actual, T ending,
+ common::ByteStream& in) {
+ actual = 0;
+ while (actual < capacity && has_remaining(in)) {
+ out[actual++] = decode(in);
+ }
+ return common::E_OK;
+ }
+
+ int batch_skip_fallback(int count, int& skipped, T ending,
+ common::ByteStream& in) {
+ skipped = 0;
+ while (skipped < count && has_remaining(in)) {
+ decode(in);
+ skipped++;
+ }
+ return common::E_OK;
+ }
+
public:
common::TSEncoding type_;
T stored_value_;
@@ -254,18 +547,18 @@ FORCE_INLINE int64_t GorillaDecoder<int64_t>::decode(common::ByteStream& in) {
class FloatGorillaDecoder : public GorillaDecoder<int32_t> {
public:
- int read_boolean(bool& ret_value, common::ByteStream& in);
- int read_int32(int32_t& ret_value, common::ByteStream& in);
- int read_int64(int64_t& ret_value, common::ByteStream& in);
- int read_float(float& ret_value, common::ByteStream& in);
- int read_double(double& ret_value, common::ByteStream& in);
+ int read_boolean(bool& ret_value, common::ByteStream& in) override;
+ int read_int32(int32_t& ret_value, common::ByteStream& in) override;
+ int read_int64(int64_t& ret_value, common::ByteStream& in) override;
+ int read_float(float& ret_value, common::ByteStream& in) override;
+ int read_double(double& ret_value, common::ByteStream& in) override;
float decode(common::ByteStream& in) {
int32_t value_int = GorillaDecoder<int32_t>::decode(in);
return common::int_to_float(value_int);
}
- int32_t cache_next(common::ByteStream& in) {
+ int32_t cache_next(common::ByteStream& in) override {
read_next(in);
if (stored_value_ ==
common::float_to_int(GORILLA_ENCODING_ENDING_FLOAT)) {
@@ -273,22 +566,47 @@ class FloatGorillaDecoder : public GorillaDecoder<int32_t> {
}
return stored_value_;
}
+
+ int read_batch_float(float* out, int capacity, int& actual,
+ common::ByteStream& in) override {
+ int32_t ending = common::float_to_int(GORILLA_ENCODING_ENDING_FLOAT);
+ actual = 0;
+ while (actual < capacity && has_remaining(in)) {
+ int32_t buf[129];
+ int batch = std::min(129, capacity - actual);
+ int buf_actual = 0;
+ int ret = batch_decode_raw(buf, batch, buf_actual, ending, in);
+ if (ret != common::E_OK) return ret;
+ if (buf_actual == 0) break;
+ for (int i = 0; i < buf_actual; i++) {
+ out[actual + i] = common::int_to_float(buf[i]);
+ }
+ actual += buf_actual;
+ }
+ return common::E_OK;
+ }
+
+ int skip_float(int count, int& skipped,
+ common::ByteStream& in) override {
+ int32_t ending = common::float_to_int(GORILLA_ENCODING_ENDING_FLOAT);
+ return batch_skip_raw(count, skipped, ending, in);
+ }
};
class DoubleGorillaDecoder : public GorillaDecoder<int64_t> {
public:
- int read_boolean(bool& ret_value, common::ByteStream& in);
- int read_int32(int32_t& ret_value, common::ByteStream& in);
- int read_int64(int64_t& ret_value, common::ByteStream& in);
- int read_float(float& ret_value, common::ByteStream& in);
- int read_double(double& ret_value, common::ByteStream& in);
+ int read_boolean(bool& ret_value, common::ByteStream& in) override;
+ int read_int32(int32_t& ret_value, common::ByteStream& in) override;
+ int read_int64(int64_t& ret_value, common::ByteStream& in) override;
+ int read_float(float& ret_value, common::ByteStream& in) override;
+ int read_double(double& ret_value, common::ByteStream& in) override;
double decode(common::ByteStream& in) {
int64_t value_long = GorillaDecoder<int64_t>::decode(in);
return common::long_to_double(value_long);
}
- int64_t cache_next(common::ByteStream& in) {
+ int64_t cache_next(common::ByteStream& in) override {
read_next(in);
if (stored_value_ ==
common::double_to_long(GORILLA_ENCODING_ENDING_DOUBLE)) {
@@ -296,12 +614,89 @@ class DoubleGorillaDecoder : public GorillaDecoder<int64_t> {
}
return stored_value_;
}
+
+ int read_batch_double(double* out, int capacity, int& actual,
+ common::ByteStream& in) override {
+ int64_t ending = common::double_to_long(GORILLA_ENCODING_ENDING_DOUBLE);
+ actual = 0;
+ while (actual < capacity && has_remaining(in)) {
+ int64_t buf[129];
+ int batch = std::min(129, capacity - actual);
+ int buf_actual = 0;
+ int ret = batch_decode_raw(buf, batch, buf_actual, ending, in);
+ if (ret != common::E_OK) return ret;
+ if (buf_actual == 0) break;
+ for (int i = 0; i < buf_actual; i++) {
+ out[actual + i] = common::long_to_double(buf[i]);
+ }
+ actual += buf_actual;
+ }
+ return common::E_OK;
+ }
+
+ int skip_double(int count, int& skipped,
+ common::ByteStream& in) override {
+ int64_t ending = common::double_to_long(GORILLA_ENCODING_ENDING_DOUBLE);
+ return batch_skip_raw(count, skipped, ending, in);
+ }
};
typedef GorillaDecoder<int32_t> IntGorillaDecoder;
typedef GorillaDecoder<int64_t> LongGorillaDecoder;
-// wrap as Decoder interface
+// ── IntGorillaDecoder batch/skip overrides ─────────────────────────────────
+template <>
+inline int GorillaDecoder<int32_t>::read_batch_int32(
+ int32_t* out, int capacity, int& actual, common::ByteStream& in) {
+ return batch_decode_raw(out, capacity, actual,
+ GORILLA_ENCODING_ENDING_INTEGER, in);
+}
+template <>
+inline int GorillaDecoder<int32_t>::read_batch_int64(
+ int64_t*, int, int& actual, common::ByteStream&) {
+ actual = 0;
+ return common::E_NOT_SUPPORT;
+}
+template <>
+inline int GorillaDecoder<int32_t>::skip_int32(int count, int& skipped,
+ common::ByteStream& in) {
+ return batch_skip_raw(count, skipped,
+ GORILLA_ENCODING_ENDING_INTEGER, in);
+}
+template <>
+inline int GorillaDecoder<int32_t>::skip_int64(int, int& skipped,
+ common::ByteStream&) {
+ skipped = 0;
+ return common::E_NOT_SUPPORT;
+}
+
+// ── LongGorillaDecoder batch/skip overrides ───────────────────────────────
+template <>
+inline int GorillaDecoder<int64_t>::read_batch_int32(
+ int32_t*, int, int& actual, common::ByteStream&) {
+ actual = 0;
+ return common::E_NOT_SUPPORT;
+}
+template <>
+inline int GorillaDecoder<int64_t>::read_batch_int64(
+ int64_t* out, int capacity, int& actual, common::ByteStream& in) {
+ return batch_decode_raw(out, capacity, actual,
+ GORILLA_ENCODING_ENDING_LONG, in);
+}
+template <>
+inline int GorillaDecoder<int64_t>::skip_int32(int, int& skipped,
+ common::ByteStream&) {
+ skipped = 0;
+ return common::E_NOT_SUPPORT;
+}
+template <>
+inline int GorillaDecoder<int64_t>::skip_int64(int count, int& skipped,
+ common::ByteStream& in) {
+ return batch_skip_raw(count, skipped,
+ GORILLA_ENCODING_ENDING_LONG, in);
+}
+
+// ── Scalar Decoder interface wrappers (unchanged) ─────────────────────────
template <>
FORCE_INLINE int IntGorillaDecoder::read_boolean(bool& ret_value,
common::ByteStream& in) {
diff --git a/cpp/src/encoding/plain_decoder.h b/cpp/src/encoding/plain_decoder.h
index c2627f71d..4c002160e 100644
--- a/cpp/src/encoding/plain_decoder.h
+++ b/cpp/src/encoding/plain_decoder.h
@@ -20,6 +20,9 @@
#ifndef ENCODING_PLAIN_DECODER_H
#define ENCODING_PLAIN_DECODER_H
+#include <algorithm>
+#include <cstring>
+
#include "encoding/decoder.h"
namespace storage {
@@ -62,6 +65,125 @@ class PlainDecoder : public Decoder {
common::ByteStream& in) override {
return common::SerializationUtil::read_mystring(ret_String, &pa, in);
}
+
+ // ── Batch overrides ──────────────────────────────────────────────────────
+ //
+ // INT32: PLAIN encoding uses varint (variable stride). Override to avoid
+ // virtual dispatch per element; actual decode is still per-value.
+ int read_batch_int32(int32_t* out, int capacity, int& actual,
+ common::ByteStream& in) override {
+ actual = 0;
+ while (actual < capacity && in.has_remaining()) {
+ int ret = common::SerializationUtil::read_var_int(out[actual], in);
+ if (ret != common::E_OK) return ret;
+ ++actual;
+ }
+ return common::E_OK;
+ }
+
+ int skip_int32(int count, int& skipped, common::ByteStream& in) override {
+ skipped = 0;
+ int32_t dummy;
+ while (skipped < count && in.has_remaining()) {
+ int ret = common::SerializationUtil::read_var_int(dummy, in);
+ if (ret != common::E_OK) return ret;
+ ++skipped;
+ }
+ return common::E_OK;
+ }
+
+ // INT64: fixed 8-byte big-endian. Direct pointer access for wrapped
+ // ByteStream, __builtin_bswap64 for byte-swap (single REV on ARM64).
+ int read_batch_int64(int64_t* out, int capacity, int& actual,
+ common::ByteStream& in) override {
+ actual = 0;
+ int n = static_cast<int>(
+ std::min<uint32_t>(in.remaining_size() / 8,
+ static_cast<uint32_t>(capacity)));
+ if (n <= 0) return common::E_OK;
+
+ const uint8_t* src =
+ (const uint8_t*)in.get_wrapped_buf() + in.read_pos();
+ in.wrapped_buf_advance_read_pos(static_cast<uint32_t>(n) * 8);
+ actual = n;
+ for (int i = 0; i < n; ++i) {
+ uint64_t v;
+ memcpy(&v, src + i * 8, 8);
+ out[i] = static_cast<int64_t>(__builtin_bswap64(v));
+ }
+ return common::E_OK;
+ }
+
+ int skip_int64(int count, int& skipped, common::ByteStream& in) override {
+ skipped = static_cast<int>(
+ std::min<uint32_t>(in.remaining_size() / 8,
+ static_cast<uint32_t>(count)));
+ if (skipped <= 0) { skipped = 0; return common::E_OK; }
+ in.wrapped_buf_advance_read_pos(static_cast<uint32_t>(skipped) * 8);
+ return common::E_OK;
+ }
+
+ int skip_float(int count, int& skipped, common::ByteStream& in) override {
+ skipped = static_cast<int>(
+ std::min<uint32_t>(in.remaining_size() / 4,
+ static_cast<uint32_t>(count)));
+ if (skipped <= 0) { skipped = 0; return common::E_OK; }
+ in.wrapped_buf_advance_read_pos(static_cast<uint32_t>(skipped) * 4);
+ return common::E_OK;
+ }
+
+ int skip_double(int count, int& skipped, common::ByteStream& in) override {
+ skipped = static_cast<int>(
+ std::min<uint32_t>(in.remaining_size() / 8,
+ static_cast<uint32_t>(count)));
+ if (skipped <= 0) { skipped = 0; return common::E_OK; }
+ in.wrapped_buf_advance_read_pos(static_cast<uint32_t>(skipped) * 8);
+ return common::E_OK;
+ }
+
+ // FLOAT: fixed 4-byte big-endian IEEE 754.
+ int read_batch_float(float* out, int capacity, int& actual,
+ common::ByteStream& in) override {
+ actual = 0;
+ int n = static_cast<int>(
+ std::min<uint32_t>(in.remaining_size() / 4,
+ static_cast<uint32_t>(capacity)));
+ if (n <= 0) return common::E_OK;
+
+ const uint8_t* src =
+ (const uint8_t*)in.get_wrapped_buf() + in.read_pos();
+ in.wrapped_buf_advance_read_pos(static_cast<uint32_t>(n) * 4);
+ actual = n;
+ for (int i = 0; i < n; ++i) {
+ uint32_t v;
+ memcpy(&v, src + i * 4, 4);
+ v = __builtin_bswap32(v);
+ memcpy(&out[i], &v, 4);
+ }
+ return common::E_OK;
+ }
+
+ // DOUBLE: fixed 8-byte big-endian IEEE 754.
+ int read_batch_double(double* out, int capacity, int& actual,
+ common::ByteStream& in) override {
+ actual = 0;
+ int n = static_cast<int>(
+ std::min<uint32_t>(in.remaining_size() / 8,
+ static_cast<uint32_t>(capacity)));
+ if (n <= 0) return common::E_OK;
+
+ const uint8_t* src =
+ (const uint8_t*)in.get_wrapped_buf() + in.read_pos();
+ in.wrapped_buf_advance_read_pos(static_cast<uint32_t>(n) * 8);
+ actual = n;
+ for (int i = 0; i < n; ++i) {
+ uint64_t v;
+ memcpy(&v, src + i * 8, 8);
+ v = __builtin_bswap64(v);
+ memcpy(&out[i], &v, 8);
+ }
+ return common::E_OK;
+ }
};
} // end namespace storage
diff --git a/cpp/src/encoding/ts2diff_decoder.h b/cpp/src/encoding/ts2diff_decoder.h
index 32584546d..dfe074f08 100644
--- a/cpp/src/encoding/ts2diff_decoder.h
+++ b/cpp/src/encoding/ts2diff_decoder.h
@@ -23,13 +23,187 @@
#include <sys/types.h>
#include <cstddef>
+#include <cstring>
#include "common/allocator/alloc_base.h"
#include "common/allocator/byte_stream.h"
#include "decoder.h"
#include "utils/util_define.h"
+#ifdef ENABLE_SIMD
+#include "simde/x86/avx2.h"
+#endif
+
namespace storage {
+
+// ============================================================================
+// SIMD batch decode helpers (INT32)
+// ============================================================================
+#ifdef ENABLE_SIMD
+
+// Decode 4 INT32 values from bit-packed data using SIMD gather + shift.
+// @in: pointer to the start of packed bit data for the block
+// @bit_width: bits per delta value
+// @delta_min: minimum delta offset for this block
+// @index: current position within the block (0-based, among write_index_
+// deltas)
+// @base: the previous reconstructed value (for prefix-sum)
+// @out: output array (4 values written)
+// Returns: the last reconstructed value (new base for next group)
+static inline int32_t simd_decode_4_i32(const uint8_t* in, int32_t bit_width,
+ int32_t delta_min, int32_t index,
+ int32_t base, int32_t out[4]) {
+ static const simde__m128i SHUF_REV4 = simde_mm_setr_epi8(
+ 3, 2, 1, 0, 7, 6, 5, 4, 11, 10, 9, 8, 15, 14, 13, 12);
+
+ const simde__m128i VMIN4 = simde_mm_set1_epi32(delta_min);
+
+ int32_t pos0 = index * bit_width;
+ int32_t pos[4] = {pos0, pos0 + bit_width, pos0 + 2 * bit_width,
+ pos0 + 3 * bit_width};
+ int32_t bidx[4] = {pos[0] >> 3, pos[1] >> 3, pos[2] >> 3, pos[3] >> 3};
+ int32_t off[4] = {pos[0] & 7, pos[1] & 7, pos[2] & 7, pos[3] & 7};
+
+ simde__m128i IDX = simde_mm_setr_epi32(bidx[0], bidx[1], bidx[2], bidx[3]);
+ simde__m128i OFF = simde_mm_setr_epi32(off[0], off[1], off[2], off[3]);
+
+ simde__m128i V4;
+
+ if (bit_width <= 16) {
+ int rshift = 32 - bit_width;
+ simde__m128i w32_le =
+ simde_mm_i32gather_epi32((const int*)in, IDX, 1);
+ simde__m128i w32_be = simde_mm_shuffle_epi8(w32_le, SHUF_REV4);
+ simde__m128i U32 = simde_mm_sllv_epi32(w32_be, OFF);
+ simde__m128i RS32 = simde_mm_set1_epi32(rshift);
+ V4 = simde_mm_srlv_epi32(U32, RS32);
+ } else {
+ static const simde__m256i SHUF_REV8 = simde_mm256_setr_epi8(
+ 7, 6, 5, 4, 3, 2, 1, 0, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4,
+ 3, 2, 1, 0, 15, 14, 13, 12, 11, 10, 9, 8);
+ int rshift = 64 - bit_width;
+ simde__m256i w64_le =
+ simde_mm256_i32gather_epi64((const long long*)in, IDX, 1);
+ simde__m256i w64_be = simde_mm256_shuffle_epi8(w64_le, SHUF_REV8);
+ simde__m256i OFF64 = simde_mm256_cvtepu32_epi64(OFF);
+ simde__m256i U64 = simde_mm256_sllv_epi64(w64_be, OFF64);
+ simde__m256i V64 =
+ simde_mm256_srl_epi64(U64, simde_mm_cvtsi32_si128(rshift));
+ simde__m256i perm =
+ simde_mm256_setr_epi32(0, 2, 4, 6, 0, 0, 0, 0);
+ simde__m256i comp = simde_mm256_permutevar8x32_epi32(V64, perm);
+ V4 = simde_mm256_castsi256_si128(comp);
+ }
+
+ // Add delta_min
+ V4 = simde_mm_add_epi32(V4, VMIN4);
+
+ // Prefix sum to reconstruct absolute values
+ simde__m128i t;
+ t = simde_mm_slli_si128(V4, 4);
+ V4 = simde_mm_add_epi32(V4, t);
+ t = simde_mm_slli_si128(V4, 8);
+ V4 = simde_mm_add_epi32(V4, t);
+
+ // Add base
+ simde__m128i C4 = simde_mm_set1_epi32(base);
+ V4 = simde_mm_add_epi32(V4, C4);
+
+ simde_mm_storeu_si128((simde__m128i*)out, V4);
+ return out[3];
+}
+
+// Decode 4 INT64 values from bit-packed data using SIMD.
+static inline int64_t simd_decode_4_i64(const uint8_t* in, int32_t bit_width,
+ int64_t delta_min, int32_t index,
+ int64_t base, int64_t out[4]) {
+ static const simde__m256i SHUF_REV8 = simde_mm256_setr_epi8(
+ 7, 6, 5, 4, 3, 2, 1, 0, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3,
+ 2, 1, 0, 15, 14, 13, 12, 11, 10, 9, 8);
+
+ const simde__m256i VMIN4 = simde_mm256_set1_epi64x(delta_min);
+
+ int32_t pos0 = index * bit_width;
+ int32_t pos[4] = {pos0, pos0 + bit_width, pos0 + 2 * bit_width,
+ pos0 + 3 * bit_width};
+ int32_t bidx[4] = {pos[0] >> 3, pos[1] >> 3, pos[2] >> 3, pos[3] >> 3};
+ int32_t off[4] = {pos[0] & 7, pos[1] & 7, pos[2] & 7, pos[3] & 7};
+
+ simde__m128i IDX = simde_mm_setr_epi32(bidx[0], bidx[1], bidx[2], bidx[3]);
+
+ int rshift = 64 - bit_width;
+ simde__m256i w64_le =
+ simde_mm256_i32gather_epi64((const long long*)in, IDX, 1);
+ simde__m256i w64_be = simde_mm256_shuffle_epi8(w64_le, SHUF_REV8);
+ simde__m256i OFF64 = simde_mm256_cvtepu32_epi64(
+ simde_mm_setr_epi32(off[0], off[1], off[2], off[3]));
+ simde__m256i U64 = simde_mm256_sllv_epi64(w64_be, OFF64);
+ simde__m256i V64 =
+ simde_mm256_srl_epi64(U64, simde_mm_cvtsi32_si128(rshift));
+
+ // Add delta_min
+ V64 = simde_mm256_add_epi64(V64, VMIN4);
+
+ // Prefix sum (64-bit, 4 lanes)
+ simde__m256i t;
+ // shift by 8 bytes = 1 lane
+ t = simde_mm256_slli_si256(V64, 8);
+ V64 = simde_mm256_add_epi64(V64, t);
+ // cross-lane: add lane[1] to lane[2] and lane[3]
+ // Extract high 128 bits, add broadcast of element[1] to both elements
+ int64_t tmp_buf[4];
+ simde_mm256_storeu_si256((simde__m256i*)tmp_buf, V64);
+ tmp_buf[2] += tmp_buf[1];
+ tmp_buf[3] += tmp_buf[1];
+ V64 = simde_mm256_loadu_si256((const simde__m256i*)tmp_buf);
+
+ // Add base
+ simde__m256i C4 = simde_mm256_set1_epi64x(base);
+ V64 = simde_mm256_add_epi64(V64, C4);
+
+ simde_mm256_storeu_si256((simde__m256i*)out, V64);
+ return out[3];
+}
+
+#endif // ENABLE_SIMD
+
+// ============================================================================
+// Scalar batch decode helpers
+// ============================================================================
+
+// Scalar: extract one value from bit-packed data.
+// @data: pointer to packed bits (NOT advanced; caller handles position)
+// @bit_pos: bit offset from start of data
+// @bit_width: bits per value
+static inline int64_t scalar_read_bits(const uint8_t* data, int32_t bit_pos,
+ int32_t bit_width) {
+ int64_t value = 0;
+ int bits = bit_width;
+ int byte_idx = bit_pos >> 3;
+ int bit_offset = bit_pos & 7;
+ int bits_avail = 8 - bit_offset;
+
+ while (bits > 0) {
+ if (bits >= bits_avail) {
+ uint8_t d = data[byte_idx] & ((1 << bits_avail) - 1);
+ value = (value << bits_avail) | d;
+ bits -= bits_avail;
+ byte_idx++;
+ bits_avail = 8;
+ } else {
+ uint8_t d = (data[byte_idx] >> (bits_avail - bits)) &
+ ((1 << bits) - 1);
+ value = (value << bits) | d;
+ bits = 0;
+ }
+ }
+ return value;
+}
+
+// ============================================================================
+// TS2DIFFDecoder template
+// ============================================================================
+
template <typename T>
class TS2DIFFDecoder : public Decoder {
public:
@@ -46,12 +220,14 @@ class TS2DIFFDecoder : public Decoder {
previous_value_ = 0;
bit_width_ = 0;
current_index_ = 0;
+ header_peeked_ = false;
}
FORCE_INLINE bool has_remaining(const common::ByteStream& buffer) override {
if (buffer.has_remaining()) return true;
- return bits_left_ != 0 || (current_index_ <= write_index_ &&
- write_index_ != -1 && current_index_ != 0);
+ return header_peeked_ || bits_left_ != 0 ||
+ (current_index_ <= write_index_ &&
+ write_index_ != -1 && current_index_ != 0);
}
void read_header(common::ByteStream& in) {
@@ -106,6 +282,20 @@ class TS2DIFFDecoder : public Decoder {
int read_String(common::String& ret_value, common::PageArena& pa,
common::ByteStream& in) override;
+ int read_batch_int32(int32_t* out, int capacity, int& actual,
+ common::ByteStream& in) override;
+ int read_batch_int64(int64_t* out, int capacity, int& actual,
+ common::ByteStream& in) override;
+ int skip_int32(int count, int& skipped, common::ByteStream& in) override;
+ int skip_int64(int count, int& skipped, common::ByteStream& in) override;
+
+ bool peek_next_block_range_int64(common::ByteStream& in,
+ int64_t& block_min,
+ int64_t& block_max,
+ int& block_count) override;
+ int skip_peeked_block_int64(common::ByteStream& in,
+ int& skipped) override;
+
public:
T first_value_;
T previous_value_;
@@ -116,8 +306,13 @@ class TS2DIFFDecoder : public Decoder {
int bit_width_;
int write_index_;
int current_index_;
+ bool header_peeked_;
};
+// ============================================================================
+// Per-value decode (unchanged)
+// ============================================================================
+
template <>
inline int32_t TS2DIFFDecoder<int32_t>::decode(common::ByteStream& in) {
int32_t ret_value = stored_value_;
@@ -172,6 +367,371 @@ inline int64_t TS2DIFFDecoder<int64_t>::decode(common::ByteStream& in) {
return ret_value;
}
+// ============================================================================
+// Batch decode: INT32
+// Decodes one full block (up to 129 values) per call using SIMD when enabled.
+// ============================================================================
+
+template <>
+inline int TS2DIFFDecoder<int32_t>::read_batch_int32(
+ int32_t* out, int capacity, int& actual, common::ByteStream& in) {
+ actual = 0;
+
+ while (actual < capacity && has_remaining(in)) {
+ // If we are mid-block (current_index_ != 0), finish it per-value.
+ if (current_index_ != 0) {
+ while (actual < capacity && current_index_ != 0 &&
+ has_remaining(in)) {
+ out[actual++] = decode(in);
+ }
+ continue;
+ }
+
+ // Start of a new block — read header
+ read_header(in);
+ common::SerializationUtil::read_i32(delta_min_, in);
+ common::SerializationUtil::read_i32(first_value_, in);
+ bits_left_ = 0;
+ buffer_ = 0;
+
+ // Output first_value
+ if (actual >= capacity) {
+ // Must consume first_value next time; set state for per-value path
+ current_index_ = 0;
+ // We already consumed the header; push first_value as stored
+ // and let the next call to decode() handle it.
+ // Actually, we need to handle this: rewind is not possible.
+ // So we output first_value and accept going 1 over capacity.
+ }
+ out[actual++] = first_value_;
+
+ if (write_index_ == 0) {
+ // Block has only first_value, no deltas
+ current_index_ = 0;
+ continue;
+ }
+
+ // Direct pointer into the wrapped ByteStream buffer.
+ int32_t block_bytes = (write_index_ * bit_width_ + 7) / 8;
+ const uint8_t* blk_ptr =
+ (const uint8_t*)in.get_wrapped_buf() + in.read_pos();
+ in.wrapped_buf_advance_read_pos(static_cast<uint32_t>(block_bytes));
+
+ int32_t remaining = write_index_;
+ if (actual + remaining > capacity) {
+ int32_t prev = first_value_;
+ int32_t bit_pos = 0;
+ for (int32_t i = 0; i < remaining && actual < capacity; ++i) {
+ int64_t delta = scalar_read_bits(blk_ptr, bit_pos, bit_width_);
+ bit_pos += bit_width_;
+ int32_t val =
+ (int32_t)delta + prev + delta_min_;
+ prev = val;
+ out[actual++] = val;
+ }
+ current_index_ = 0;
+ continue;
+ }
+
+ // Full block decode
+ int32_t prev = first_value_;
+ int32_t i = 0;
+
+#ifdef ENABLE_SIMD
+ // SIMD path: decode 8 values at a time (2 groups of 4)
+ for (; i + 7 < remaining; i += 8) {
+ int32_t need_bytes =
+ ((i + 7) * bit_width_ + bit_width_ + 7) / 8 +
+ (bit_width_ > 16 ? 8 : 4);
+ if (need_bytes > block_bytes) break;
+
+ int32_t grp_out[8];
+ prev = simd_decode_4_i32(blk_ptr, bit_width_, delta_min_, i, prev,
+ grp_out);
+ prev = simd_decode_4_i32(blk_ptr, bit_width_, delta_min_, i + 4,
+ prev, grp_out + 4);
+
+ memcpy(out + actual, grp_out, 8 * sizeof(int32_t));
+ actual += 8;
+ }
+#endif
+
+ // Scalar tail
+ int32_t bit_pos = i * bit_width_;
+ for (; i < remaining; ++i) {
+ int64_t delta = scalar_read_bits(blk_ptr, bit_pos, bit_width_);
+ bit_pos += bit_width_;
+ int32_t val = (int32_t)delta + prev + delta_min_;
+ prev = val;
+ out[actual++] = val;
+ }
+
+ // Block done, reset state
+ first_value_ = prev;
+ current_index_ = 0;
+ }
+
+ return common::E_OK;
+}
+
+// ============================================================================
+// Batch decode: INT64
+// ============================================================================
+
+template <>
+inline int TS2DIFFDecoder<int64_t>::read_batch_int64(
+ int64_t* out, int capacity, int& actual, common::ByteStream& in) {
+ actual = 0;
+
+ while (actual < capacity && has_remaining(in)) {
+ // If mid-block, finish per-value
+ if (current_index_ != 0) {
+ while (actual < capacity && current_index_ != 0 &&
+ has_remaining(in)) {
+ out[actual++] = decode(in);
+ }
+ continue;
+ }
+
+ // Start of a new block
+ if (!header_peeked_) {
+ read_header(in);
+ common::SerializationUtil::read_i64(delta_min_, in);
+ common::SerializationUtil::read_i64(first_value_, in);
+ bits_left_ = 0;
+ buffer_ = 0;
+ }
+ header_peeked_ = false;
+
+ out[actual++] = first_value_;
+
+ if (write_index_ == 0) {
+ current_index_ = 0;
+ continue;
+ }
+
+ int32_t block_bytes = (write_index_ * bit_width_ + 7) / 8;
+ // Direct pointer into the wrapped ByteStream buffer.
+ const uint8_t* blk_ptr =
+ (const uint8_t*)in.get_wrapped_buf() + in.read_pos();
+ in.wrapped_buf_advance_read_pos(static_cast<uint32_t>(block_bytes));
+
+ int32_t remaining = write_index_;
+ if (actual + remaining > capacity) {
+ int64_t prev = first_value_;
+ int32_t bit_pos = 0;
+ for (int32_t j = 0; j < remaining && actual < capacity; ++j) {
+ int64_t delta = scalar_read_bits(blk_ptr, bit_pos, bit_width_);
+ bit_pos += bit_width_;
+ int64_t val = delta + prev + delta_min_;
+ prev = val;
+ out[actual++] = val;
+ }
+ current_index_ = 0;
+ continue;
+ }
+
+ int64_t prev = first_value_;
+ int32_t i = 0;
+
+#ifdef ENABLE_SIMD
+ // SIMD path: decode 4 INT64 values at a time
+ for (; i + 3 < remaining; i += 4) {
+ int32_t need_bytes =
+ ((i + 3) * bit_width_ + bit_width_ + 7) / 8 + 8;
+ if (need_bytes > block_bytes) break;
+
+ int64_t grp_out[4];
+ prev = simd_decode_4_i64(blk_ptr, bit_width_, delta_min_, i, prev,
+ grp_out);
+ memcpy(out + actual, grp_out, 4 * sizeof(int64_t));
+ actual += 4;
+ }
+#endif
+
+ // Scalar tail
+ int32_t bit_pos = i * bit_width_;
+ for (; i < remaining; ++i) {
+ int64_t delta = scalar_read_bits(blk_ptr, bit_pos, bit_width_);
+ bit_pos += bit_width_;
+ int64_t val = delta + prev + delta_min_;
+ prev = val;
+ out[actual++] = val;
+ }
+
+ first_value_ = prev;
+ current_index_ = 0;
+ }
+
+ return common::E_OK;
+}
+
+// ============================================================================
+// Skip: INT32 — read header only, jump over packed data
+// ============================================================================
+
+template <>
+inline int TS2DIFFDecoder<int32_t>::skip_int32(int count, int& skipped,
+ common::ByteStream& in) {
+ skipped = 0;
+
+ // If mid-block, finish current block per-value
+ while (skipped < count && current_index_ != 0 && has_remaining(in)) {
+ decode(in);
+ ++skipped;
+ }
+
+ // Skip whole blocks
+ while (skipped < count && has_remaining(in)) {
+ int32_t wi, bw, dm, fv;
+ common::SerializationUtil::read_i32(wi, in);
+ common::SerializationUtil::read_i32(bw, in);
+ common::SerializationUtil::read_i32(dm, in);
+ common::SerializationUtil::read_i32(fv, in);
+
+ int32_t block_vals = wi + 1;
+ int32_t skip_bytes = (wi * bw + 7) / 8;
+ in.wrapped_buf_advance_read_pos(skip_bytes);
+
+ skipped += block_vals;
+ // Reset decoder state
+ bits_left_ = 0;
+ buffer_ = 0;
+ current_index_ = 0;
+ write_index_ = -1;
+ }
+
+ return common::E_OK;
+}
+
+// ============================================================================
+// Skip: INT64
+// ============================================================================
+
+template <>
+inline int TS2DIFFDecoder<int64_t>::skip_int64(int count, int& skipped,
+ common::ByteStream& in) {
+ skipped = 0;
+
+ while (skipped < count && current_index_ != 0 && has_remaining(in)) {
+ decode(in);
+ ++skipped;
+ }
+
+ while (skipped < count && has_remaining(in)) {
+ int32_t wi, bw;
+ int64_t dm, fv;
+ common::SerializationUtil::read_i32(wi, in);
+ common::SerializationUtil::read_i32(bw, in);
+ common::SerializationUtil::read_i64(dm, in);
+ common::SerializationUtil::read_i64(fv, in);
+
+ int32_t block_vals = wi + 1;
+ int32_t skip_bytes = (wi * bw + 7) / 8;
+ in.wrapped_buf_advance_read_pos(skip_bytes);
+
+ skipped += block_vals;
+ bits_left_ = 0;
+ buffer_ = 0;
+ current_index_ = 0;
+ write_index_ = -1;
+ }
+
+ return common::E_OK;
+}
+
+// ============================================================================
+// Block-level filter check: peek header and compute value range
+// ============================================================================
+
+template <>
+inline bool TS2DIFFDecoder<int64_t>::peek_next_block_range_int64(
+ common::ByteStream& in, int64_t& block_min, int64_t& block_max,
+ int& block_count) {
+ if (current_index_ != 0 || !has_remaining(in)) return false;
+
+ read_header(in);
+ common::SerializationUtil::read_i64(delta_min_, in);
+ common::SerializationUtil::read_i64(first_value_, in);
+ bits_left_ = 0;
+ buffer_ = 0;
+
+ block_min = first_value_;
+ block_count = write_index_ + 1;
+
+ if (write_index_ == 0 || bit_width_ == 0) {
+ block_max = first_value_ + (int64_t)write_index_ * delta_min_;
+ } else if (bit_width_ >= 63) {
+ block_max = INT64_MAX;
+ } else {
+ int64_t max_delta = delta_min_ + ((1LL << bit_width_) - 1);
+ block_max = first_value_ + (int64_t)write_index_ * max_delta;
+ }
+
+ header_peeked_ = true;
+ return true;
+}
+
+template <>
+inline int TS2DIFFDecoder<int64_t>::skip_peeked_block_int64(
+ common::ByteStream& in, int& skipped) {
+ skipped = write_index_ + 1;
+ int32_t skip_bytes = (write_index_ * bit_width_ + 7) / 8;
+ in.wrapped_buf_advance_read_pos(skip_bytes);
+ header_peeked_ = false;
+ bits_left_ = 0;
+ buffer_ = 0;
+ current_index_ = 0;
+ write_index_ = -1;
+ return common::E_OK;
+}
+
+// INT32 specialization: not applicable (timestamps are always INT64)
+template <>
+inline bool TS2DIFFDecoder<int32_t>::peek_next_block_range_int64(
+ common::ByteStream& in, int64_t& block_min, int64_t& block_max,
+ int& block_count) {
+ return false;
+}
+
+template <>
+inline int TS2DIFFDecoder<int32_t>::skip_peeked_block_int64(
+ common::ByteStream& in, int& skipped) {
+ return common::E_NOT_SUPPORT;
+}
+
+// ============================================================================
+// Default (unsupported type) batch/skip — fall back to base class
+// ============================================================================
+
+template <>
+inline int TS2DIFFDecoder<int32_t>::read_batch_int64(
+ int64_t* out, int capacity, int& actual, common::ByteStream& in) {
+ return Decoder::read_batch_int64(out, capacity, actual, in);
+}
+
+template <>
+inline int TS2DIFFDecoder<int32_t>::skip_int64(
+ int count, int& skipped, common::ByteStream& in) {
+ return Decoder::skip_int64(count, skipped, in);
+}
+
+template <>
+inline int TS2DIFFDecoder<int64_t>::read_batch_int32(
+ int32_t* out, int capacity, int& actual, common::ByteStream& in) {
+ return Decoder::read_batch_int32(out, capacity, actual, in);
+}
+
+template <>
+inline int TS2DIFFDecoder<int64_t>::skip_int32(
+ int count, int& skipped, common::ByteStream& in) {
+ return Decoder::skip_int32(count, skipped, in);
+}
+
+// ============================================================================
+// Float / Double wrapper decoders (unchanged)
+// ============================================================================
+
class FloatTS2DIFFDecoder : public TS2DIFFDecoder<int32_t> {
public:
float decode(common::ByteStream& in) {
@@ -184,6 +744,19 @@ class FloatTS2DIFFDecoder : public TS2DIFFDecoder<int32_t> {
int read_int64(int64_t& ret_value, common::ByteStream& in);
int read_float(float& ret_value, common::ByteStream& in);
int read_double(double& ret_value, common::ByteStream& in);
+
+ int read_batch_float(float* out, int capacity, int& actual,
+ common::ByteStream& in) override {
+ // Reuse SIMD batch decode for int32, then bit-cast to float
+ int32_t* buf = reinterpret_cast<int32_t*>(out);
+ int ret = TS2DIFFDecoder<int32_t>::read_batch_int32(
+ buf, capacity, actual, in);
+ if (ret != common::E_OK) return ret;
+ for (int i = 0; i < actual; ++i) {
+ out[i] = common::int_to_float(buf[i]);
+ }
+ return common::E_OK;
+ }
};
class DoubleTS2DIFFDecoder : public TS2DIFFDecoder<int64_t> {
@@ -198,6 +771,19 @@ class DoubleTS2DIFFDecoder : public TS2DIFFDecoder<int64_t> {
int read_int64(int64_t& ret_value, common::ByteStream& in);
int read_float(float& ret_value, common::ByteStream& in);
int read_double(double& ret_value, common::ByteStream& in);
+
+ int read_batch_double(double* out, int capacity, int& actual,
+ common::ByteStream& in) override {
+ // Reuse SIMD batch decode for int64, then bit-cast to double
+ int64_t* buf = reinterpret_cast<int64_t*>(out);
+ int ret = TS2DIFFDecoder<int64_t>::read_batch_int64(
+ buf, capacity, actual, in);
+ if (ret != common::E_OK) return ret;
+ for (int i = 0; i < actual; ++i) {
+ out[i] = common::long_to_double(buf[i]);
+ }
+ return common::E_OK;
+ }
};
typedef TS2DIFFDecoder<int32_t> IntTS2DIFFDecoder;
diff --git a/cpp/src/file/tsfile_io_reader.cc b/cpp/src/file/tsfile_io_reader.cc
index 405c55534..33e75360f 100644
--- a/cpp/src/file/tsfile_io_reader.cc
+++ b/cpp/src/file/tsfile_io_reader.cc
@@ -80,6 +80,130 @@ int TsFileIOReader::alloc_ssi(std::shared_ptr<IDeviceID> device_id,
return ret;
}
+int TsFileIOReader::alloc_multi_ssi(
+ std::shared_ptr<IDeviceID> device_id,
+ const std::vector<std::string>& measurement_names,
+ TsFileSeriesScanIterator*& ssi, common::PageArena& pa,
+ Filter* time_filter) {
+ int ret = E_OK;
+ if (RET_FAIL(load_tsfile_meta_if_necessary())) return ret;
+
+ ssi = new TsFileSeriesScanIterator;
+ ssi->init(device_id,
+ measurement_names.empty() ? "" : measurement_names[0],
+ read_file_, time_filter, pa);
+
+ auto& ssi_pa = ssi->timeseries_index_pa_;
+
+ // Load device index entry
+ std::shared_ptr<IMetaIndexEntry> device_index_entry;
+ int64_t device_ie_end_offset = 0;
+ if (RET_FAIL(load_device_index_entry(
+ std::make_shared<DeviceIDComparable>(device_id),
+ device_index_entry, device_ie_end_offset))) {
+ delete ssi;
+ ssi = nullptr;
+ return ret;
+ }
+
+ // Read and deserialize top measurement index node
+ int64_t start_offset = device_index_entry->get_offset();
+ int64_t end_offset = device_ie_end_offset;
+ ASSERT(start_offset < end_offset);
+ const int32_t read_size = end_offset - start_offset;
+ int32_t ret_read_len = 0;
+ char* data_buf = (char*)ssi_pa.alloc(read_size);
+ void* m_idx_node_buf = ssi_pa.alloc(sizeof(MetaIndexNode));
+ if (IS_NULL(data_buf) || IS_NULL(m_idx_node_buf)) {
+ delete ssi;
+ ssi = nullptr;
+ return E_OOM;
+ }
+ auto* top_node_ptr = new (m_idx_node_buf) MetaIndexNode(&ssi_pa);
+ auto top_node = std::shared_ptr<MetaIndexNode>(
+ top_node_ptr, MetaIndexNode::self_deleter);
+
+ if (RET_FAIL(read_file_->read(start_offset, data_buf, read_size,
+ ret_read_len))) {
+ delete ssi;
+ ssi = nullptr;
+ return ret;
+ }
+ if (RET_FAIL(top_node->deserialize_from(data_buf, read_size))) {
+ delete ssi;
+ ssi = nullptr;
+ return ret;
+ }
+
+ bool is_aligned = is_aligned_device(top_node);
+ if (!is_aligned) {
+ delete ssi;
+ ssi = nullptr;
+ return E_NOT_SUPPORT;
+ }
+
+ // Get time column metadata
+ TimeseriesIndex* time_ts_idx = nullptr;
+ if (RET_FAIL(get_time_column_metadata(top_node, time_ts_idx, ssi_pa))) {
+ delete ssi;
+ ssi = nullptr;
+ return ret;
+ }
+
+ // Create MultiAlignedTimeseriesIndex
+ void* multi_buf = ssi_pa.alloc(sizeof(MultiAlignedTimeseriesIndex));
+ if (IS_NULL(multi_buf)) {
+ delete ssi;
+ ssi = nullptr;
+ return E_OOM;
+ }
+ auto* multi_idx = new (multi_buf) MultiAlignedTimeseriesIndex;
+ multi_idx->time_ts_idx_ = time_ts_idx;
+
+ // Load each measurement's TimeseriesIndex
+ for (const auto& meas_name : measurement_names) {
+ std::shared_ptr<IMetaIndexEntry> meas_entry;
+ int64_t meas_end_offset = 0;
+ if (RET_FAIL(load_measurement_index_entry(
+ meas_name, top_node, meas_entry, meas_end_offset))) {
+ // Measurement not found — abort multi path
+ delete ssi;
+ ssi = nullptr;
+ return ret;
+ }
+
+ ITimeseriesIndex* ts_idx = nullptr;
+ if (RET_FAIL(do_load_timeseries_index(meas_name,
+ meas_entry->get_offset(),
+ meas_end_offset, ssi_pa,
+ ts_idx, /*is_aligned=*/true))) {
+ delete ssi;
+ ssi = nullptr;
+ return ret;
+ }
+
+ auto* aligned_idx = dynamic_cast<AlignedTimeseriesIndex*>(ts_idx);
+ if (aligned_idx && aligned_idx->value_ts_idx_) {
+ multi_idx->value_ts_idxs_.push_back(aligned_idx->value_ts_idx_);
+ } else {
+ delete ssi;
+ ssi = nullptr;
+ return E_NOT_EXIST;
+ }
+ }
+
+ ssi->itimeseries_index_ = multi_idx;
+
+ // Skip global statistic filter for multi — per-chunk filtering still works.
+
+ if (RET_FAIL(ssi->init_chunk_reader())) {
+ ssi->destroy();
+ delete ssi;
+ ssi = nullptr;
+ }
+ return ret;
+}
+
void TsFileIOReader::revert_ssi(TsFileSeriesScanIterator* ssi) {
if (ssi != nullptr) {
ssi->destroy();
diff --git a/cpp/src/file/tsfile_io_reader.h b/cpp/src/file/tsfile_io_reader.h
index 2f4135e0e..e6e0a04a0 100644
--- a/cpp/src/file/tsfile_io_reader.h
+++ b/cpp/src/file/tsfile_io_reader.h
@@ -59,6 +59,11 @@ class TsFileIOReader {
TsFileSeriesScanIterator*& ssi, common::PageArena& pa,
Filter* time_filter = nullptr);
+ int alloc_multi_ssi(std::shared_ptr<IDeviceID> device_id,
+ const std::vector<std::string>& measurement_names,
+ TsFileSeriesScanIterator*& ssi, common::PageArena& pa,
+ Filter* time_filter = nullptr);
+
void revert_ssi(TsFileSeriesScanIterator* ssi);
std::string get_file_path() const { return read_file_->file_path(); }
diff --git a/cpp/src/reader/aligned_chunk_reader.cc b/cpp/src/reader/aligned_chunk_reader.cc
index 14250e7f8..526095523 100644
--- a/cpp/src/reader/aligned_chunk_reader.cc
+++ b/cpp/src/reader/aligned_chunk_reader.cc
@@ -19,6 +19,8 @@
#include "aligned_chunk_reader.h"
+#include <algorithm>
+
#include "compress/compressor_factory.h"
#include "encoding/decoder_factory.h"
@@ -64,6 +66,34 @@ void AlignedChunkReader::reset() {
file_data_value_buf_size_ = 0;
time_chunk_visit_offset_ = 0;
value_chunk_visit_offset_ = 0;
+
+ // Free leftover uncompressed buffers from the previous chunk.
+ if (time_uncompressed_buf_ != nullptr && time_compressor_ != nullptr) {
+ time_compressor_->after_uncompress(time_uncompressed_buf_);
+ time_uncompressed_buf_ = nullptr;
+ }
+
+ // Multi-value reset
+ for (auto* col : value_columns_) {
+ // Free uncompressed buffer before resetting.
+ if (col->uncompressed_buf != nullptr && col->compressor != nullptr) {
+ col->compressor->after_uncompress(col->uncompressed_buf);
+ col->uncompressed_buf = nullptr;
+ }
+ char* buf = col->in_stream.get_wrapped_buf();
+ if (buf != nullptr) mem_free(buf);
+ col->in_stream.reset();
+ col->in.reset();
+ col->chunk_header.reset();
+ col->cur_page_header.reset();
+ col->file_data_buf_size = 0;
+ col->chunk_visit_offset = 0;
+ col->notnull_bitmap.clear();
+ col->cur_value_index = -1;
+ col->chunk_meta = nullptr;
+ // Note: decoder/compressor are NOT freed here — they are reused by
+ // alloc_compressor_and_decoder() in load_by_aligned_meta_multi().
+ }
}
void AlignedChunkReader::destroy() {
@@ -100,6 +130,32 @@ void AlignedChunkReader::destroy() {
}
cur_value_page_header_.reset();
chunk_header_.~ChunkHeader();
+
+ // Multi-value destroy
+ for (auto* col : value_columns_) {
+ if (col->decoder != nullptr) {
+ col->decoder->~Decoder();
+ DecoderFactory::free(col->decoder);
+ col->decoder = nullptr;
+ }
+ if (col->compressor != nullptr) {
+ col->compressor->~Compressor();
+ CompressorFactory::free(col->compressor);
+ col->compressor = nullptr;
+ }
+ buf = col->in_stream.get_wrapped_buf();
+ if (buf != nullptr) {
+ mem_free(buf);
+ col->in_stream.clear_wrapped_buf();
+ }
+ col->cur_page_header.reset();
+ delete col;
+ }
+ value_columns_.clear();
+ if (decode_pool_ != nullptr) {
+ delete decode_pool_;
+ decode_pool_ = nullptr;
+ }
}
int AlignedChunkReader::load_by_aligned_meta(ChunkMeta* time_chunk_meta,
@@ -206,15 +262,20 @@ int AlignedChunkReader::alloc_compressor_and_decoder(
int AlignedChunkReader::get_next_page(TsBlock* ret_tsblock,
Filter* oneshoot_filter, PageArena& pa) {
+ if (multi_value_mode_) {
+ return get_next_page_multi(ret_tsblock, oneshoot_filter, pa);
+ }
int ret = E_OK;
Filter* filter =
(oneshoot_filter != nullptr ? oneshoot_filter : time_filter_);
- if (prev_time_page_not_finish() && prev_value_page_not_finish()) {
+ bool pt = prev_time_page_not_finish();
+ bool pv = prev_value_page_not_finish();
+ if (pt && pv) {
ret = decode_time_value_buf_into_tsblock(ret_tsblock, oneshoot_filter,
&pa);
return ret;
}
- if (!prev_time_page_not_finish() && !prev_value_page_not_finish()) {
+ if (!pt && !pv) {
while (IS_SUCC(ret)) {
if (RET_FAIL(get_cur_page_header(
time_chunk_meta_, time_in_stream_, cur_time_page_header_,
@@ -247,7 +308,8 @@ int AlignedChunkReader::get_cur_page_header(ChunkMeta*& chunk_meta,
common::ByteStream& in_stream,
PageHeader& cur_page_header,
uint32_t& chunk_visit_offset,
- ChunkHeader& chunk_header) {
+ ChunkHeader& chunk_header,
+ int32_t* override_buf_size) {
int ret = E_OK;
bool retry = true;
int cur_page_header_serialized_size = 0;
@@ -270,7 +332,8 @@ int AlignedChunkReader::get_cur_page_header(ChunkMeta*& chunk_meta,
retry = false;
retry_read_want_size += 1024;
int32_t& file_data_buf_size =
- chunk_header.data_type_ == common::VECTOR
+ override_buf_size != nullptr ? *override_buf_size
+ : chunk_header.data_type_ == common::VECTOR
? file_data_time_buf_size_
: file_data_value_buf_size_;
// do not shrink buffer for page header, otherwise, the buffer is
@@ -620,6 +683,502 @@ int AlignedChunkReader::i32_DECODE_TYPED_TV_INTO_TSBLOCK(
return ret;
}
+int AlignedChunkReader::i32_DECODE_TV_BATCH(ByteStream& time_in,
+ ByteStream& value_in,
+ RowAppender& row_appender,
+ Filter* filter) {
+ int ret = E_OK;
+ const int BATCH = 129;
+ int64_t times[BATCH];
+ int32_t values[BATCH];
+ const uint32_t null_mask_base = 1 << 7;
+
+ while (time_decoder_->has_remaining(time_in)) {
+ if (row_appender.remaining() < (uint32_t)BATCH) {
+ ret = E_OVERFLOW;
+ break;
+ }
+
+ // Block-level time filter check
+ bool block_all_pass = false;
+ if (filter != nullptr) {
+ int64_t block_min, block_max;
+ int block_count;
+ if (time_decoder_->peek_next_block_range_int64(
+ time_in, block_min, block_max, block_count)) {
+ if (!filter->satisfy_start_end_time(block_min, block_max)) {
+ int skipped = 0;
+ time_decoder_->skip_peeked_block_int64(time_in, skipped);
+ int nonnull = 0;
+ for (int i = 0; i < block_count; ++i) {
+ int vi = cur_value_index + 1 + i;
+ if (!value_page_col_notnull_bitmap_.empty() &&
+ ((value_page_col_notnull_bitmap_[vi / 8] & 0xFF) &
+ (null_mask_base >> (vi % 8))) != 0) {
+ ++nonnull;
+ }
+ }
+ cur_value_index += block_count;
+ if (nonnull > 0) {
+ int sk = 0;
+ value_decoder_->skip_int32(nonnull, sk, value_in);
+ }
+ continue;
+ }
+ if (filter->contain_start_end_time(block_min, block_max)) {
+ block_all_pass = true;
+ }
+ }
+ }
+
+ int time_count = 0;
+ if (RET_FAIL(time_decoder_->read_batch_int64(
+ times, BATCH, time_count, time_in))) {
+ break;
+ }
+ if (time_count == 0) break;
+
+ bool is_null[BATCH];
+ int nonnull_count = 0;
+ for (int i = 0; i < time_count; ++i) {
+ int vi = cur_value_index + 1 + i;
+ if (value_page_col_notnull_bitmap_.empty() ||
+ ((value_page_col_notnull_bitmap_[vi / 8] & 0xFF) &
+ (null_mask_base >> (vi % 8))) == 0) {
+ is_null[i] = true;
+ } else {
+ is_null[i] = false;
+ ++nonnull_count;
+ }
+ }
+
+ bool time_mask[BATCH];
+ int pass_count = time_count;
+ if (filter != nullptr && !block_all_pass) {
+ pass_count = filter->satisfy_batch_time(times, time_count,
+ time_mask);
+ }
+
+ if (pass_count == 0) {
+ if (nonnull_count > 0) {
+ int skipped = 0;
+ value_decoder_->skip_int32(nonnull_count, skipped, value_in);
+ }
+ cur_value_index += time_count;
+ continue;
+ }
+
+ int value_count = 0;
+ if (nonnull_count > 0) {
+ if (RET_FAIL(value_decoder_->read_batch_int32(
+ values, nonnull_count, value_count, value_in))) {
+ break;
+ }
+ }
+
+ int val_idx = 0;
+ for (int i = 0; i < time_count; ++i) {
+ cur_value_index++;
+ if (filter != nullptr && !block_all_pass && !time_mask[i]) {
+ if (!is_null[i]) ++val_idx;
+ continue;
+ }
+ if (is_null[i]) {
+ if (UNLIKELY(!row_appender.add_row())) {
+ ret = E_OVERFLOW;
+ break;
+ }
+ row_appender.append(0, (char*)×[i], sizeof(int64_t));
+ row_appender.append_null(1);
+ } else {
+ int32_t val = values[val_idx++];
+ if (filter != nullptr && !block_all_pass &&
+ !filter->satisfy(times[i], (int64_t)val)) {
+ continue;
+ }
+ if (UNLIKELY(!row_appender.add_row())) {
+ ret = E_OVERFLOW;
+ break;
+ }
+ row_appender.append(0, (char*)×[i], sizeof(int64_t));
+ row_appender.append(1, (char*)&val, sizeof(int32_t));
+ }
+ }
+ if (ret != E_OK) break;
+ }
+ return ret;
+}
+
+int AlignedChunkReader::i64_DECODE_TV_BATCH(ByteStream& time_in,
+ ByteStream& value_in,
+ RowAppender& row_appender,
+ Filter* filter) {
+ int ret = E_OK;
+ const int BATCH = 129;
+ int64_t times[BATCH];
+ int64_t values[BATCH];
+ const uint32_t null_mask_base = 1 << 7;
+
+ while (time_decoder_->has_remaining(time_in)) {
+ if (row_appender.remaining() < (uint32_t)BATCH) {
+ ret = E_OVERFLOW;
+ break;
+ }
+
+ // Block-level time filter check: skip entire block if out of range
+ bool block_all_pass = false;
+ if (filter != nullptr) {
+ int64_t block_min, block_max;
+ int block_count;
+ if (time_decoder_->peek_next_block_range_int64(
+ time_in, block_min, block_max, block_count)) {
+ if (!filter->satisfy_start_end_time(block_min, block_max)) {
+ int skipped = 0;
+ time_decoder_->skip_peeked_block_int64(time_in, skipped);
+ int nonnull = 0;
+ for (int i = 0; i < block_count; ++i) {
+ int vi = cur_value_index + 1 + i;
+ if (!value_page_col_notnull_bitmap_.empty() &&
+ ((value_page_col_notnull_bitmap_[vi / 8] & 0xFF) &
+ (null_mask_base >> (vi % 8))) != 0) {
+ ++nonnull;
+ }
+ }
+ cur_value_index += block_count;
+ if (nonnull > 0) {
+ int sk = 0;
+ value_decoder_->skip_int64(nonnull, sk, value_in);
+ }
+ continue;
+ }
+ if (filter->contain_start_end_time(block_min, block_max)) {
+ block_all_pass = true;
+ }
+ }
+ }
+
+ int time_count = 0;
+ if (RET_FAIL(time_decoder_->read_batch_int64(
+ times, BATCH, time_count, time_in))) {
+ break;
+ }
+ if (time_count == 0) break;
+
+ bool is_null[BATCH];
+ int nonnull_count = 0;
+ for (int i = 0; i < time_count; ++i) {
+ int vi = cur_value_index + 1 + i;
+ if (value_page_col_notnull_bitmap_.empty() ||
+ ((value_page_col_notnull_bitmap_[vi / 8] & 0xFF) &
+ (null_mask_base >> (vi % 8))) == 0) {
+ is_null[i] = true;
+ } else {
+ is_null[i] = false;
+ ++nonnull_count;
+ }
+ }
+
+ bool time_mask[BATCH];
+ int pass_count = time_count;
+ if (filter != nullptr && !block_all_pass) {
+ pass_count = filter->satisfy_batch_time(times, time_count,
+ time_mask);
+ }
+
+ if (pass_count == 0) {
+ if (nonnull_count > 0) {
+ int skipped = 0;
+ value_decoder_->skip_int64(nonnull_count, skipped, value_in);
+ }
+ cur_value_index += time_count;
+ continue;
+ }
+
+ int value_count = 0;
+ if (nonnull_count > 0) {
+ if (RET_FAIL(value_decoder_->read_batch_int64(
+ values, nonnull_count, value_count, value_in))) {
+ break;
+ }
+ }
+
+ int val_idx = 0;
+ for (int i = 0; i < time_count; ++i) {
+ cur_value_index++;
+ if (filter != nullptr && !block_all_pass && !time_mask[i]) {
+ if (!is_null[i]) ++val_idx;
+ continue;
+ }
+ if (is_null[i]) {
+ if (UNLIKELY(!row_appender.add_row())) {
+ ret = E_OVERFLOW;
+ break;
+ }
+ row_appender.append(0, (char*)×[i], sizeof(int64_t));
+ row_appender.append_null(1);
+ } else {
+ int64_t val = values[val_idx++];
+ if (filter != nullptr && !block_all_pass &&
+ !filter->satisfy(times[i], val)) {
+ continue;
+ }
+ if (UNLIKELY(!row_appender.add_row())) {
+ ret = E_OVERFLOW;
+ break;
+ }
+ row_appender.append(0, (char*)×[i], sizeof(int64_t));
+ row_appender.append(1, (char*)&val, sizeof(int64_t));
+ }
+ }
+ if (ret != E_OK) break;
+ }
+ return ret;
+}
+
+int AlignedChunkReader::float_DECODE_TV_BATCH(ByteStream& time_in,
+ ByteStream& value_in,
+ RowAppender& row_appender,
+ Filter* filter) {
+ int ret = E_OK;
+ const int BATCH = 129;
+ int64_t times[BATCH];
+ float values[BATCH];
+ const uint32_t null_mask_base = 1 << 7;
+
+ while (time_decoder_->has_remaining(time_in)) {
+ if (row_appender.remaining() < (uint32_t)BATCH) {
+ ret = E_OVERFLOW;
+ break;
+ }
+
+ // Block-level time filter check
+ bool block_all_pass = false;
+ if (filter != nullptr) {
+ int64_t block_min, block_max;
+ int block_count;
+ if (time_decoder_->peek_next_block_range_int64(
+ time_in, block_min, block_max, block_count)) {
+ if (!filter->satisfy_start_end_time(block_min, block_max)) {
+ int skipped = 0;
+ time_decoder_->skip_peeked_block_int64(time_in, skipped);
+ int nonnull = 0;
+ for (int i = 0; i < block_count; ++i) {
+ int vi = cur_value_index + 1 + i;
+ if (!value_page_col_notnull_bitmap_.empty() &&
+ ((value_page_col_notnull_bitmap_[vi / 8] & 0xFF) &
+ (null_mask_base >> (vi % 8))) != 0) {
+ ++nonnull;
+ }
+ }
+ cur_value_index += block_count;
+ if (nonnull > 0) {
+ int sk = 0;
+ value_decoder_->skip_float(nonnull, sk, value_in);
+ }
+ continue;
+ }
+ if (filter->contain_start_end_time(block_min, block_max)) {
+ block_all_pass = true;
+ }
+ }
+ }
+
+ int time_count = 0;
+ if (RET_FAIL(time_decoder_->read_batch_int64(
+ times, BATCH, time_count, time_in))) {
+ break;
+ }
+ if (time_count == 0) break;
+
+ bool is_null[BATCH];
+ int nonnull_count = 0;
+ for (int i = 0; i < time_count; ++i) {
+ int vi = cur_value_index + 1 + i;
+ if (value_page_col_notnull_bitmap_.empty() ||
+ ((value_page_col_notnull_bitmap_[vi / 8] & 0xFF) &
+ (null_mask_base >> (vi % 8))) == 0) {
+ is_null[i] = true;
+ } else {
+ is_null[i] = false;
+ ++nonnull_count;
+ }
+ }
+
+ bool time_mask[BATCH];
+ int pass_count = time_count;
+ if (filter != nullptr && !block_all_pass) {
+ pass_count = filter->satisfy_batch_time(times, time_count,
+ time_mask);
+ }
+
+ if (pass_count == 0) {
+ if (nonnull_count > 0) {
+ int skipped = 0;
+ value_decoder_->skip_float(nonnull_count, skipped, value_in);
+ }
+ cur_value_index += time_count;
+ continue;
+ }
+
+ int value_count = 0;
+ if (nonnull_count > 0) {
+ if (RET_FAIL(value_decoder_->read_batch_float(
+ values, nonnull_count, value_count, value_in))) {
+ break;
+ }
+ }
+
+ int val_idx = 0;
+ for (int i = 0; i < time_count; ++i) {
+ cur_value_index++;
+ if (filter != nullptr && !block_all_pass && !time_mask[i]) {
+ if (!is_null[i]) ++val_idx;
+ continue;
+ }
+ if (is_null[i]) {
+ if (UNLIKELY(!row_appender.add_row())) {
+ ret = E_OVERFLOW;
+ break;
+ }
+ row_appender.append(0, (char*)×[i], sizeof(int64_t));
+ row_appender.append_null(1);
+ } else {
+ float val = values[val_idx++];
+ if (UNLIKELY(!row_appender.add_row())) {
+ ret = E_OVERFLOW;
+ break;
+ }
+ row_appender.append(0, (char*)×[i], sizeof(int64_t));
+ row_appender.append(1, (char*)&val, sizeof(float));
+ }
+ }
+ if (ret != E_OK) break;
+ }
+ return ret;
+}
+
+int AlignedChunkReader::double_DECODE_TV_BATCH(ByteStream& time_in,
+ ByteStream& value_in,
+ RowAppender& row_appender,
+ Filter* filter) {
+ int ret = E_OK;
+ const int BATCH = 129;
+ int64_t times[BATCH];
+ double values[BATCH];
+ const uint32_t null_mask_base = 1 << 7;
+
+ while (time_decoder_->has_remaining(time_in)) {
+ if (row_appender.remaining() < (uint32_t)BATCH) {
+ ret = E_OVERFLOW;
+ break;
+ }
+
+ // Block-level time filter check
+ bool block_all_pass = false;
+ if (filter != nullptr) {
+ int64_t block_min, block_max;
+ int block_count;
+ if (time_decoder_->peek_next_block_range_int64(
+ time_in, block_min, block_max, block_count)) {
+ if (!filter->satisfy_start_end_time(block_min, block_max)) {
+ int skipped = 0;
+ time_decoder_->skip_peeked_block_int64(time_in, skipped);
+ int nonnull = 0;
+ for (int i = 0; i < block_count; ++i) {
+ int vi = cur_value_index + 1 + i;
+ if (!value_page_col_notnull_bitmap_.empty() &&
+ ((value_page_col_notnull_bitmap_[vi / 8] & 0xFF) &
+ (null_mask_base >> (vi % 8))) != 0) {
+ ++nonnull;
+ }
+ }
+ cur_value_index += block_count;
+ if (nonnull > 0) {
+ int sk = 0;
+ value_decoder_->skip_double(nonnull, sk, value_in);
+ }
+ continue;
+ }
+ if (filter->contain_start_end_time(block_min, block_max)) {
+ block_all_pass = true;
+ }
+ }
+ }
+
+ int time_count = 0;
+ if (RET_FAIL(time_decoder_->read_batch_int64(
+ times, BATCH, time_count, time_in))) {
+ break;
+ }
+ if (time_count == 0) break;
+
+ bool is_null[BATCH];
+ int nonnull_count = 0;
+ for (int i = 0; i < time_count; ++i) {
+ int vi = cur_value_index + 1 + i;
+ if (value_page_col_notnull_bitmap_.empty() ||
+ ((value_page_col_notnull_bitmap_[vi / 8] & 0xFF) &
+ (null_mask_base >> (vi % 8))) == 0) {
+ is_null[i] = true;
+ } else {
+ is_null[i] = false;
+ ++nonnull_count;
+ }
+ }
+
+ bool time_mask[BATCH];
+ int pass_count = time_count;
+ if (filter != nullptr && !block_all_pass) {
+ pass_count = filter->satisfy_batch_time(times, time_count,
+ time_mask);
+ }
+
+ if (pass_count == 0) {
+ if (nonnull_count > 0) {
+ int skipped = 0;
+ value_decoder_->skip_double(nonnull_count, skipped, value_in);
+ }
+ cur_value_index += time_count;
+ continue;
+ }
+
+ int value_count = 0;
+ if (nonnull_count > 0) {
+ if (RET_FAIL(value_decoder_->read_batch_double(
+ values, nonnull_count, value_count, value_in))) {
+ break;
+ }
+ }
+
+ int val_idx = 0;
+ for (int i = 0; i < time_count; ++i) {
+ cur_value_index++;
+ if (filter != nullptr && !block_all_pass && !time_mask[i]) {
+ if (!is_null[i]) ++val_idx;
+ continue;
+ }
+ if (is_null[i]) {
+ if (UNLIKELY(!row_appender.add_row())) {
+ ret = E_OVERFLOW;
+ break;
+ }
+ row_appender.append(0, (char*)×[i], sizeof(int64_t));
+ row_appender.append_null(1);
+ } else {
+ double val = values[val_idx++];
+ if (UNLIKELY(!row_appender.add_row())) {
+ ret = E_OVERFLOW;
+ break;
+ }
+ row_appender.append(0, (char*)×[i], sizeof(int64_t));
+ row_appender.append(1, (char*)&val, sizeof(double));
+ }
+ }
+ if (ret != E_OK) break;
+ }
+ return ret;
+}
+
int AlignedChunkReader::decode_tv_buf_into_tsblock_by_datatype(
ByteStream& time_in, ByteStream& value_in, TsBlock* ret_tsblock,
Filter* filter, common::PageArena* pa) {
@@ -632,23 +1191,21 @@ int AlignedChunkReader::decode_tv_buf_into_tsblock_by_datatype(
break;
case common::DATE:
case common::INT32:
- // DECODE_TYPED_TV_INTO_TSBLOCK(int32_t, int32, time_in_, value_in_,
- // row_appender);
- ret = i32_DECODE_TYPED_TV_INTO_TSBLOCK(time_in_, value_in_,
- row_appender, filter);
+ ret = i32_DECODE_TV_BATCH(time_in_, value_in_,
+ row_appender, filter);
break;
case common::TIMESTAMP:
case common::INT64:
- DECODE_TYPED_TV_INTO_TSBLOCK(int64_t, int64, time_in_, value_in_,
- row_appender);
+ ret = i64_DECODE_TV_BATCH(time_in_, value_in_,
+ row_appender, filter);
break;
case common::FLOAT:
- DECODE_TYPED_TV_INTO_TSBLOCK(float, float, time_in_, value_in_,
- row_appender);
+ ret = float_DECODE_TV_BATCH(time_in_, value_in_,
+ row_appender, filter);
break;
case common::DOUBLE:
- DECODE_TYPED_TV_INTO_TSBLOCK(double, double, time_in_, value_in_,
- row_appender);
+ ret = double_DECODE_TV_BATCH(time_in_, value_in_,
+ row_appender, filter);
break;
case common::STRING:
case common::BLOB:
@@ -711,4 +1268,608 @@ int AlignedChunkReader::STRING_DECODE_TYPED_TV_INTO_TSBLOCK(
return ret;
}
+// ══════════════════════════════════════════════════════════════════════════
+// Multi-value AlignedChunkReader implementation
+// ══════════════════════════════════════════════════════════════════════════
+
+int AlignedChunkReader::load_by_aligned_meta_multi(
+ ChunkMeta* time_chunk_meta,
+ const std::vector<ChunkMeta*>& value_metas) {
+ int ret = E_OK;
+ multi_value_mode_ = true;
+ time_chunk_meta_ = time_chunk_meta;
+
+ // ── Load time chunk header ──
+ file_data_time_buf_size_ = 1024;
+ int32_t ret_read_len = 0;
+ char* time_file_data_buf =
+ (char*)mem_alloc(file_data_time_buf_size_, MOD_CHUNK_READER);
+ if (IS_NULL(time_file_data_buf)) return E_OOM;
+
+ ret = read_file_->read(time_chunk_meta_->offset_of_chunk_header_,
+ time_file_data_buf, file_data_time_buf_size_,
+ ret_read_len);
+ if (IS_SUCC(ret) && ret_read_len < ChunkHeader::MIN_SERIALIZED_SIZE) {
+ ret = E_TSFILE_CORRUPTED;
+ mem_free(time_file_data_buf);
+ return ret;
+ }
+ if (IS_SUCC(ret)) {
+ time_in_stream_.wrap_from(time_file_data_buf, ret_read_len);
+ if (RET_FAIL(time_chunk_header_.deserialize_from(time_in_stream_))) {
+ return ret;
+ }
+ time_chunk_visit_offset_ = time_in_stream_.read_pos();
+ }
+
+ // Alloc time decoder/compressor
+ if (IS_SUCC(ret)) {
+ if (RET_FAIL(alloc_compressor_and_decoder(
+ time_decoder_, time_compressor_,
+ time_chunk_header_.encoding_type_,
+ time_chunk_header_.data_type_,
+ time_chunk_header_.compression_type_))) {
+ return ret;
+ }
+ }
+
+ // ── Load each value column ──
+ // Reuse existing ValueColumnState objects if count matches (reset() already
+ // cleared their internal state). Otherwise, recreate.
+ if (value_columns_.size() != value_metas.size()) {
+ for (auto* p : value_columns_) delete p;
+ value_columns_.clear();
+ value_columns_.reserve(value_metas.size());
+ for (size_t c = 0; c < value_metas.size(); c++) {
+ value_columns_.push_back(new ValueColumnState);
+ }
+ }
+ for (size_t c = 0; c < value_metas.size() && IS_SUCC(ret); c++) {
+ auto* col = value_columns_[c];
+ col->chunk_meta = value_metas[c];
+ col->file_data_buf_size = 1024;
+ ret_read_len = 0;
+ char* vbuf =
+ (char*)mem_alloc(col->file_data_buf_size, MOD_CHUNK_READER);
+ if (IS_NULL(vbuf)) return E_OOM;
+
+ ret = read_file_->read(col->chunk_meta->offset_of_chunk_header_, vbuf,
+ col->file_data_buf_size, ret_read_len);
+ if (IS_SUCC(ret) && ret_read_len < ChunkHeader::MIN_SERIALIZED_SIZE) {
+ ret = E_TSFILE_CORRUPTED;
+ mem_free(vbuf);
+ break;
+ }
+ if (IS_SUCC(ret)) {
+ col->in_stream.wrap_from(vbuf, ret_read_len);
+ if (RET_FAIL(
+ col->chunk_header.deserialize_from(col->in_stream))) {
+ break;
+ }
+ col->chunk_visit_offset = col->in_stream.read_pos();
+ if (RET_FAIL(alloc_compressor_and_decoder(
+ col->decoder, col->compressor,
+ col->chunk_header.encoding_type_,
+ col->chunk_header.data_type_,
+ col->chunk_header.compression_type_))) {
+ break;
+ }
+ }
+ }
+
+ // Create thread pool for parallel decode when we have multiple columns.
+ if (IS_SUCC(ret) && value_columns_.size() > 1 &&
+ decode_pool_ == nullptr) {
+ int nthreads = std::min((int)value_columns_.size(), 4);
+ decode_pool_ = new common::DecodeThreadPool(nthreads);
+ }
+
+ return ret;
+}
+
+bool AlignedChunkReader::has_more_data_multi() const {
+ if (prev_time_page_not_finish() || prev_any_value_page_not_finish_multi()) {
+ return true;
+ }
+ if (time_chunk_visit_offset_ - time_chunk_header_.serialized_size_ <
+ time_chunk_header_.data_size_) {
+ return true;
+ }
+ for (const auto* col : value_columns_) {
+ if (col->chunk_visit_offset - col->chunk_header.serialized_size_ <
+ col->chunk_header.data_size_) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool AlignedChunkReader::prev_any_value_page_not_finish_multi() const {
+ for (const auto* col : value_columns_) {
+ if ((col->decoder && col->decoder->has_remaining(col->in)) ||
+ col->in.has_remaining()) {
+ return true;
+ }
+ }
+ return false;
+}
+
+int AlignedChunkReader::get_next_page_multi(TsBlock* ret_tsblock,
+ Filter* oneshoot_filter,
+ PageArena& pa) {
+ int ret = E_OK;
+ Filter* filter =
+ (oneshoot_filter != nullptr ? oneshoot_filter : time_filter_);
+
+ bool pt = prev_time_page_not_finish();
+ bool pv = prev_any_value_page_not_finish_multi();
+ if (pt && pv) {
+ ret = decode_time_value_buf_into_tsblock_multi(ret_tsblock, filter,
+ &pa);
+ return ret;
+ }
+ if (!pt && !pv) {
+ while (IS_SUCC(ret)) {
+ // Get time page header
+ if (RET_FAIL(get_cur_page_header(
+ time_chunk_meta_, time_in_stream_, cur_time_page_header_,
+ time_chunk_visit_offset_, time_chunk_header_))) {
+ break;
+ }
+ // Get each value column's page header
+ for (size_t c = 0; c < value_columns_.size() && IS_SUCC(ret);
+ c++) {
+ auto* col = value_columns_[c];
+ if (RET_FAIL(get_cur_page_header(
+ col->chunk_meta, col->in_stream, col->cur_page_header,
+ col->chunk_visit_offset, col->chunk_header,
+ &col->file_data_buf_size))) {
+ }
+ }
+ if (IS_FAIL(ret)) break;
+
+ if (cur_page_statisify_filter_multi(filter)) {
+ break;
+ }
+ if (RET_FAIL(skip_cur_page_multi())) break;
+ if (!has_more_data()) {
+ ret = E_NO_MORE_DATA;
+ break;
+ }
+ }
+ if (IS_SUCC(ret)) {
+ ret = decode_cur_time_page_data();
+ if (IS_SUCC(ret)) {
+ ret = decode_cur_value_pages_multi();
+ }
+ }
+ }
+ if (IS_SUCC(ret)) {
+ ret = decode_time_value_buf_into_tsblock_multi(ret_tsblock, filter,
+ &pa);
+ }
+ return ret;
+}
+
+bool AlignedChunkReader::cur_page_statisify_filter_multi(Filter* filter) {
+ bool time_satisfy = filter == nullptr ||
+ cur_time_page_header_.statistic_ == nullptr ||
+ filter->satisfy(cur_time_page_header_.statistic_);
+ return time_satisfy;
+}
+
+int AlignedChunkReader::skip_cur_page_multi() {
+ time_chunk_visit_offset_ += cur_time_page_header_.compressed_size_;
+ time_in_stream_.wrapped_buf_advance_read_pos(
+ cur_time_page_header_.compressed_size_);
+ for (auto* col : value_columns_) {
+ col->chunk_visit_offset += col->cur_page_header.compressed_size_;
+ col->in_stream.wrapped_buf_advance_read_pos(
+ col->cur_page_header.compressed_size_);
+ }
+ return E_OK;
+}
+
+int AlignedChunkReader::decode_cur_value_pages_multi() {
+ int ret = E_OK;
+ // Phase 1: Serial IO — ensure each column's page data is in memory.
+ for (size_t c = 0; c < value_columns_.size() && IS_SUCC(ret); c++) {
+ ret = ensure_value_page_loaded(*value_columns_[c]);
+ }
+ if (IS_FAIL(ret)) return ret;
+
+ // Phase 2: Parallel CPU — decompress + parse bitmap + reset decoder.
+ if (value_columns_.size() > 1 && decode_pool_ != nullptr) {
+ std::vector<int> col_rets(value_columns_.size(), E_OK);
+ for (size_t c = 0; c < value_columns_.size(); c++) {
+ auto* col = value_columns_[c];
+ int* col_ret = &col_rets[c];
+ decode_pool_->submit([col, col_ret] {
+ *col_ret = decompress_and_parse_value_page(*col);
+ });
+ }
+ decode_pool_->wait_all();
+ for (size_t c = 0; c < col_rets.size(); c++) {
+ if (IS_FAIL(col_rets[c])) return col_rets[c];
+ }
+ } else {
+ for (size_t c = 0; c < value_columns_.size() && IS_SUCC(ret); c++) {
+ ret = decompress_and_parse_value_page(*value_columns_[c]);
+ }
+ }
+ return ret;
+}
+
+int AlignedChunkReader::decode_cur_value_page_data_for(ValueColumnState& col) {
+ int ret = E_OK;
+
+ // Step 1: ensure full page data is loaded
+ if (col.in_stream.remaining_size() <
+ col.cur_page_header.compressed_size_) {
+ if (RET_FAIL(read_from_file_and_rewrap(
+ col.in_stream, col.chunk_meta, col.chunk_visit_offset,
+ col.file_data_buf_size,
+ col.cur_page_header.compressed_size_))) {
+ return ret;
+ }
+ }
+
+ if (col.cur_page_header.compressed_size_ == 0) {
+ col.in.wrap_from(nullptr, 0);
+ return E_OK;
+ }
+
+ // Step 2: uncompress
+ char* compressed_buf =
+ col.in_stream.get_wrapped_buf() + col.in_stream.read_pos();
+ uint32_t compressed_size = col.cur_page_header.compressed_size_;
+ col.in_stream.wrapped_buf_advance_read_pos(compressed_size);
+ col.chunk_visit_offset += compressed_size;
+
+ char* uncompressed_buf = nullptr;
+ uint32_t uncompressed_size = 0;
+ if (RET_FAIL(col.compressor->reset(false))) {
+ return ret;
+ }
+ if (RET_FAIL(col.compressor->uncompress(compressed_buf, compressed_size,
+ uncompressed_buf,
+ uncompressed_size))) {
+ return ret;
+ }
+ col.uncompressed_buf = uncompressed_buf;
+
+ if (uncompressed_size != col.cur_page_header.uncompressed_size_) {
+ return E_TSFILE_CORRUPTED;
+ }
+
+ // Step 3: parse bitmap + value data
+ uint32_t offset = 0;
+ uint32_t data_num = SerializationUtil::read_ui32(uncompressed_buf);
+ offset += sizeof(uint32_t);
+ col.notnull_bitmap.resize((data_num + 7) / 8);
+ for (size_t i = 0; i < col.notnull_bitmap.size(); i++) {
+ col.notnull_bitmap[i] = *(uncompressed_buf + offset);
+ offset++;
+ }
+ col.cur_value_index = -1;
+
+ char* value_buf = uncompressed_buf + offset;
+ uint32_t value_buf_size = uncompressed_size - offset;
+ col.decoder->reset();
+ col.in.wrap_from(value_buf, value_buf_size);
+ return ret;
+}
+
+int AlignedChunkReader::ensure_value_page_loaded(ValueColumnState& col) {
+ int ret = E_OK;
+ if (col.in_stream.remaining_size() <
+ col.cur_page_header.compressed_size_) {
+ if (RET_FAIL(read_from_file_and_rewrap(
+ col.in_stream, col.chunk_meta, col.chunk_visit_offset,
+ col.file_data_buf_size,
+ col.cur_page_header.compressed_size_))) {
+ return ret;
+ }
+ }
+ return ret;
+}
+
+int AlignedChunkReader::decompress_and_parse_value_page(
+ ValueColumnState& col) {
+ int ret = E_OK;
+
+ if (col.cur_page_header.compressed_size_ == 0) {
+ col.in.wrap_from(nullptr, 0);
+ return E_OK;
+ }
+
+ // Decompress
+ char* compressed_buf =
+ col.in_stream.get_wrapped_buf() + col.in_stream.read_pos();
+ uint32_t compressed_size = col.cur_page_header.compressed_size_;
+ col.in_stream.wrapped_buf_advance_read_pos(compressed_size);
+ col.chunk_visit_offset += compressed_size;
+
+ char* uncompressed_buf = nullptr;
+ uint32_t uncompressed_size = 0;
+ if (RET_FAIL(col.compressor->reset(false))) {
+ return ret;
+ }
+ if (RET_FAIL(col.compressor->uncompress(compressed_buf, compressed_size,
+ uncompressed_buf,
+ uncompressed_size))) {
+ return ret;
+ }
+ col.uncompressed_buf = uncompressed_buf;
+
+ if (uncompressed_size != col.cur_page_header.uncompressed_size_) {
+ return E_TSFILE_CORRUPTED;
+ }
+
+ // Parse bitmap + value data
+ uint32_t offset = 0;
+ uint32_t data_num = SerializationUtil::read_ui32(uncompressed_buf);
+ offset += sizeof(uint32_t);
+ col.notnull_bitmap.resize((data_num + 7) / 8);
+ for (size_t i = 0; i < col.notnull_bitmap.size(); i++) {
+ col.notnull_bitmap[i] = *(uncompressed_buf + offset);
+ offset++;
+ }
+ col.cur_value_index = -1;
+
+ char* value_buf = uncompressed_buf + offset;
+ uint32_t value_buf_size = uncompressed_size - offset;
+ col.decoder->reset();
+ col.in.wrap_from(value_buf, value_buf_size);
+ return ret;
+}
+
+int AlignedChunkReader::decode_time_value_buf_into_tsblock_multi(
+ TsBlock*& ret_tsblock, Filter* filter, PageArena* pa) {
+ int ret = E_OK;
+ RowAppender row_appender(ret_tsblock);
+ ret = multi_DECODE_TV_BATCH(ret_tsblock, row_appender, filter, pa);
+
+ // Release uncompressed buffers if pages are done
+ if (ret != E_OVERFLOW) {
+ if (time_uncompressed_buf_ != nullptr) {
+ time_compressor_->after_uncompress(time_uncompressed_buf_);
+ time_uncompressed_buf_ = nullptr;
+ }
+ for (auto* col : value_columns_) {
+ if (col->uncompressed_buf != nullptr) {
+ col->compressor->after_uncompress(col->uncompressed_buf);
+ col->uncompressed_buf = nullptr;
+ }
+ if (!(col->decoder && col->decoder->has_remaining(col->in)) &&
+ !col->in.has_remaining()) {
+ col->in.reset();
+ }
+ col->notnull_bitmap.clear();
+ col->notnull_bitmap.shrink_to_fit();
+ }
+ if (!prev_time_page_not_finish()) {
+ time_in_.reset();
+ }
+ } else {
+ ret = E_OK;
+ }
+ return ret;
+}
+
+int AlignedChunkReader::multi_DECODE_TV_BATCH(TsBlock* ret_tsblock,
+ RowAppender& row_appender,
+ Filter* filter,
+ PageArena* pa) {
+ int ret = E_OK;
+ const int BATCH = 129;
+ int64_t times[BATCH];
+ const uint32_t null_mask_base = 1 << 7;
+ const uint32_t num_cols = value_columns_.size();
+
+
+ while (time_decoder_->has_remaining(time_in_)) {
+ if (row_appender.remaining() < (uint32_t)BATCH) {
+ ret = E_OVERFLOW;
+ break;
+ }
+
+ // ── Phase 1: Decode a batch of timestamps ──
+ int time_count = 0;
+ if (RET_FAIL(time_decoder_->read_batch_int64(times, BATCH, time_count,
+ time_in_))) {
+ break;
+ }
+ if (time_count == 0) break;
+
+ // ── Phase 2: Apply time filter ──
+ bool time_mask[BATCH];
+ bool block_all_pass = (filter == nullptr);
+ int pass_count = time_count;
+ if (!block_all_pass) {
+ pass_count =
+ filter->satisfy_batch_time(times, time_count, time_mask);
+ }
+
+ // ── Phase 3: Per-column null check + value decode ──
+ // For each column, compute null flags and decode non-null values.
+ // We store decoded values in column-specific buffers.
+ // Max 8 bytes per value, 129 values per batch.
+ struct ColBatch {
+ bool is_null[BATCH];
+ int nonnull_count;
+ // Value buffer — up to 129 * 8 bytes = 1032 bytes on stack
+ char val_buf[BATCH * 8];
+ int val_count;
+ };
+ // Allocate on heap if many columns, stack for small counts
+ std::vector<ColBatch> col_batches(num_cols);
+
+ for (uint32_t c = 0; c < num_cols; c++) {
+ auto* col = value_columns_[c];
+ auto& cb = col_batches[c];
+ cb.nonnull_count = 0;
+ cb.val_count = 0;
+ for (int i = 0; i < time_count; i++) {
+ int vi = col->cur_value_index + 1 + i;
+ if (col->notnull_bitmap.empty() ||
+ ((col->notnull_bitmap[vi / 8] & 0xFF) &
+ (null_mask_base >> (vi % 8))) == 0) {
+ cb.is_null[i] = true;
+ } else {
+ cb.is_null[i] = false;
+ cb.nonnull_count++;
+ }
+ }
+
+ // Skip values if no rows pass time filter
+ if (pass_count == 0 && cb.nonnull_count > 0) {
+ switch (col->chunk_header.data_type_) {
+ case common::INT32:
+ case common::DATE: {
+ int sk = 0;
+ col->decoder->skip_int32(cb.nonnull_count, sk, col->in);
+ break;
+ }
+ case common::INT64:
+ case common::TIMESTAMP: {
+ int sk = 0;
+ col->decoder->skip_int64(cb.nonnull_count, sk, col->in);
+ break;
+ }
+ case common::FLOAT: {
+ int sk = 0;
+ col->decoder->skip_float(cb.nonnull_count, sk, col->in);
+ break;
+ }
+ case common::DOUBLE: {
+ int sk = 0;
+ col->decoder->skip_double(cb.nonnull_count, sk, col->in);
+ break;
+ }
+ default:
+ // STRING etc - fall through to value decode
+ break;
+ }
+ cb.nonnull_count = 0; // already skipped
+ }
+
+ // Decode non-null values
+ if (cb.nonnull_count > 0) {
+ switch (col->chunk_header.data_type_) {
+ case common::INT32:
+ case common::DATE:
+ col->decoder->read_batch_int32(
+ reinterpret_cast<int32_t*>(cb.val_buf),
+ cb.nonnull_count, cb.val_count, col->in);
+ break;
+ case common::INT64:
+ case common::TIMESTAMP:
+ col->decoder->read_batch_int64(
+ reinterpret_cast<int64_t*>(cb.val_buf),
+ cb.nonnull_count, cb.val_count, col->in);
+ break;
+ case common::FLOAT:
+ col->decoder->read_batch_float(
+ reinterpret_cast<float*>(cb.val_buf),
+ cb.nonnull_count, cb.val_count, col->in);
+ break;
+ case common::DOUBLE:
+ col->decoder->read_batch_double(
+ reinterpret_cast<double*>(cb.val_buf),
+ cb.nonnull_count, cb.val_count, col->in);
+ break;
+ default:
+ // STRING handled below in scatter loop
+ break;
+ }
+ }
+ }
+
+ // ── Phase 4: Skip if no rows pass ──
+ if (pass_count == 0) {
+ for (uint32_t c = 0; c < num_cols; c++) {
+ value_columns_[c]->cur_value_index += time_count;
+ }
+ continue;
+ }
+
+ // ── Phase 5: Scatter into TsBlock ──
+
+ // Fast path: all rows pass filter AND all columns have no nulls
+ // → batch memcpy directly into Vector buffers.
+ if (pass_count == time_count) {
+ bool all_nonnull = true;
+ for (uint32_t c = 0; c < num_cols; c++) {
+ if (col_batches[c].nonnull_count != time_count) {
+ all_nonnull = false;
+ break;
+ }
+ }
+ if (all_nonnull) {
+ // Batch append time column
+ common::Vector* time_vec = ret_tsblock->get_vector(0);
+ time_vec->get_value_data().append_fixed_value(
+ (const char*)times,
+ static_cast<uint32_t>(time_count) * sizeof(int64_t));
+ // Batch append each value column
+ for (uint32_t c = 0; c < num_cols; c++) {
+ auto& cb = col_batches[c];
+ auto* col = value_columns_[c];
+ uint32_t elem_size = common::get_data_type_size(
+ col->chunk_header.data_type_);
+ common::Vector* vec = ret_tsblock->get_vector(c + 1);
+ vec->get_value_data().append_fixed_value(
+ cb.val_buf,
+ static_cast<uint32_t>(cb.val_count) * elem_size);
+ col->cur_value_index += time_count;
+ }
+ row_appender.add_rows(static_cast<uint32_t>(time_count));
+ continue;
+ }
+ }
+
+ // Slow path: per-row scatter (has filter or has nulls)
+ std::vector<int> val_idx(num_cols, 0);
+
+ for (int i = 0; i < time_count; i++) {
+ bool passes = block_all_pass || time_mask[i];
+
+ if (!passes) {
+ for (uint32_t c = 0; c < num_cols; c++) {
+ value_columns_[c]->cur_value_index++;
+ if (!col_batches[c].is_null[i]) val_idx[c]++;
+ }
+ continue;
+ }
+
+ if (UNLIKELY(!row_appender.add_row())) {
+ ret = E_OVERFLOW;
+ break;
+ }
+
+ row_appender.append(0, (char*)×[i], sizeof(int64_t));
+
+ for (uint32_t c = 0; c < num_cols; c++) {
+ value_columns_[c]->cur_value_index++;
+ auto& cb = col_batches[c];
+ auto* col = value_columns_[c];
+
+ if (cb.is_null[i]) {
+ row_appender.append_null(c + 1);
+ } else {
+ uint32_t elem_size =
+ common::get_data_type_size(col->chunk_header.data_type_);
+ row_appender.append(c + 1,
+ cb.val_buf + val_idx[c] * elem_size,
+ elem_size);
+ val_idx[c]++;
+ }
+ }
+ }
+ if (ret != E_OK) break;
+ }
+ return ret;
+}
+
} // end namespace storage
\ No newline at end of file
diff --git a/cpp/src/reader/aligned_chunk_reader.h b/cpp/src/reader/aligned_chunk_reader.h
index 12e0b9289..09528923f 100644
--- a/cpp/src/reader/aligned_chunk_reader.h
+++ b/cpp/src/reader/aligned_chunk_reader.h
@@ -21,6 +21,7 @@
#define READER_CHUNK_ALIGNED_READER_H
#include "common/allocator/my_string.h"
+#include "common/thread_pool.h"
#include "common/tsfile_common.h"
#include "compress/compressor.h"
#include "encoding/decoder.h"
@@ -30,6 +31,23 @@
namespace storage {
+// Per-value-column state for multi-value AlignedChunkReader.
+struct ValueColumnState {
+ ChunkMeta* chunk_meta = nullptr;
+ ChunkHeader chunk_header;
+ Decoder* decoder = nullptr;
+ Compressor* compressor = nullptr;
+ common::ByteStream in_stream; // raw data from file
+ common::ByteStream in; // decompressed data
+ char* uncompressed_buf = nullptr;
+ int32_t file_data_buf_size = 0;
+ uint32_t chunk_visit_offset = 0;
+ PageHeader cur_page_header;
+ std::vector<uint8_t> notnull_bitmap;
+ int32_t cur_value_index = -1;
+
+};
+
class AlignedChunkReader : public IChunkReader {
public:
AlignedChunkReader()
@@ -64,21 +82,45 @@ class AlignedChunkReader : public IChunkReader {
~AlignedChunkReader() override = default;
bool has_more_data() const override {
+ if (multi_value_mode_) {
+ return has_more_data_multi();
+ }
return prev_value_page_not_finish() ||
+ prev_time_page_not_finish() ||
(value_chunk_visit_offset_ -
value_chunk_header_.serialized_size_ <
value_chunk_header_.data_size_) ||
- prev_time_page_not_finish() ||
- (time_chunk_visit_offset_ - time_chunk_header_.serialized_size_ <
+ (time_chunk_visit_offset_ -
+ time_chunk_header_.serialized_size_ <
time_chunk_header_.data_size_);
}
ChunkHeader& get_chunk_header() override { return value_chunk_header_; }
int load_by_aligned_meta(ChunkMeta* time_meta,
ChunkMeta* value_meta) override;
+ // Multi-value: load one time chunk + N value chunks.
+ int load_by_aligned_meta_multi(
+ ChunkMeta* time_meta,
+ const std::vector<ChunkMeta*>& value_metas);
+
int get_next_page(common::TsBlock* tsblock, Filter* oneshoot_filter,
common::PageArena& pa) override;
+ // Multi-value: get the number of value columns.
+ uint32_t get_value_column_count() const {
+ return multi_value_mode_ ? value_columns_.size() : 1;
+ }
+
+ // Multi-value: get chunk header for a specific value column.
+ ChunkHeader& get_value_chunk_header(uint32_t col) {
+ if (multi_value_mode_ && col < value_columns_.size()) {
+ return value_columns_[col]->chunk_header;
+ }
+ return value_chunk_header_;
+ }
+
+ bool is_multi_value_mode() const { return multi_value_mode_; }
+
private:
FORCE_INLINE bool chunk_has_only_one_page(
const ChunkHeader& chunk_header) const {
@@ -94,7 +136,8 @@ class AlignedChunkReader : public IChunkReader {
common::ByteStream& in_stream_,
PageHeader& cur_page_header_,
uint32_t& chunk_visit_offset,
- ChunkHeader& chunk_header);
+ ChunkHeader& chunk_header,
+ int32_t* override_buf_size = nullptr);
int read_from_file_and_rewrap(common::ByteStream& in_stream_,
ChunkMeta*& chunk_meta,
uint32_t& chunk_visit_offset,
@@ -126,58 +169,84 @@ class AlignedChunkReader : public IChunkReader {
common::ByteStream& value_in,
common::RowAppender& row_appender,
Filter* filter);
+ int i32_DECODE_TV_BATCH(common::ByteStream& time_in,
+ common::ByteStream& value_in,
+ common::RowAppender& row_appender,
+ Filter* filter);
+ int i64_DECODE_TV_BATCH(common::ByteStream& time_in,
+ common::ByteStream& value_in,
+ common::RowAppender& row_appender,
+ Filter* filter);
+ int float_DECODE_TV_BATCH(common::ByteStream& time_in,
+ common::ByteStream& value_in,
+ common::RowAppender& row_appender,
+ Filter* filter);
+ int double_DECODE_TV_BATCH(common::ByteStream& time_in,
+ common::ByteStream& value_in,
+ common::RowAppender& row_appender,
+ Filter* filter);
int STRING_DECODE_TYPED_TV_INTO_TSBLOCK(common::ByteStream& time_in,
common::ByteStream& value_in,
common::RowAppender& row_appender,
common::PageArena& pa,
Filter* filter);
+ // ── Multi-value private methods ──────────────────────────────────────
+ bool has_more_data_multi() const;
+ bool prev_any_value_page_not_finish_multi() const;
+ int get_next_page_multi(common::TsBlock* ret_tsblock,
+ Filter* oneshoot_filter, common::PageArena& pa);
+ int skip_cur_page_multi();
+ bool cur_page_statisify_filter_multi(Filter* filter);
+ int decode_cur_value_pages_multi();
+ int decode_cur_value_page_data_for(ValueColumnState& col);
+ // Split of decode_cur_value_page_data_for for parallel decode:
+ int ensure_value_page_loaded(ValueColumnState& col);
+ static int decompress_and_parse_value_page(ValueColumnState& col);
+ int decode_time_value_buf_into_tsblock_multi(common::TsBlock*& ret_tsblock,
+ Filter* filter,
+ common::PageArena* pa);
+ int multi_DECODE_TV_BATCH(common::TsBlock* ret_tsblock,
+ common::RowAppender& row_appender,
+ Filter* filter, common::PageArena* pa);
+
private:
ReadFile* read_file_;
+ // ── Single-value mode fields (kept for backward compat) ──────────────
ChunkMeta* time_chunk_meta_;
ChunkMeta* value_chunk_meta_;
common::String measurement_name_;
ChunkHeader time_chunk_header_;
- // TODO: support reading more than one measurement in AlignedChunkReader.
ChunkHeader value_chunk_header_;
PageHeader cur_time_page_header_;
PageHeader cur_value_page_header_;
- /*
- * Data reader from file is stored in @in_stream_, and the size
- * is stored in @file_data_buf_size_. Note, in_stream_.total_size_
- * is used to limit deserialization, that is why we still have
- * @file_data_buf_size_.
- *
- * Since we may want keep data of current page (and page header
- * of next page) in memory, we need a byte-size cursor to tell
- * us which byte we are processing, so we have @chunk_visit_offset_
- * it refer to position from the start of chunk_header_,
- * also refer to offset within the chunk (including chunk header).
- * It advanced by step of a page header or a page tv data.
- */
- common::ByteStream time_in_stream_{common::MOD_CHUNK_READER};
- common::ByteStream value_in_stream_{common::MOD_CHUNK_READER};
+ common::ByteStream time_in_stream_;
+ common::ByteStream value_in_stream_;
int32_t file_data_time_buf_size_;
int32_t file_data_value_buf_size_;
uint32_t time_chunk_visit_offset_;
uint32_t value_chunk_visit_offset_;
- // Statistic *page_statistic_;
Compressor* time_compressor_;
Compressor* value_compressor_;
Filter* time_filter_;
Decoder* time_decoder_;
Decoder* value_decoder_;
- common::ByteStream time_in_{common::MOD_CHUNK_READER};
- common::ByteStream value_in_{common::MOD_CHUNK_READER};
+ common::ByteStream time_in_;
+ common::ByteStream value_in_;
char* time_uncompressed_buf_;
char* value_uncompressed_buf_;
std::vector<uint8_t> value_page_col_notnull_bitmap_;
uint32_t value_page_data_num_;
int32_t cur_value_index;
+
+ // ── Multi-value mode fields ──────────────────────────────────────────
+ bool multi_value_mode_ = false;
+ std::vector<ValueColumnState*> value_columns_;
+ common::DecodeThreadPool* decode_pool_ = nullptr;
};
} // end namespace storage
-#endif // READER_CHUNK_READER_H
+#endif // READER_CHUNK_ALIGNED_READER_H
diff --git a/cpp/src/reader/block/single_device_tsblock_reader.cc b/cpp/src/reader/block/single_device_tsblock_reader.cc
index 836ab6956..f6b33bb55 100644
--- a/cpp/src/reader/block/single_device_tsblock_reader.cc
+++ b/cpp/src/reader/block/single_device_tsblock_reader.cc
@@ -19,6 +19,10 @@
#include "single_device_tsblock_reader.h"
+#include <set>
+
+#include "common/db_common.h"
+
namespace storage {
SingleDeviceTsBlockReader::SingleDeviceTsBlockReader(
@@ -67,8 +71,69 @@ int SingleDeviceTsBlockReader::init(DeviceQueryTask* device_query_task,
time_series_indexs, pa_))) {
return ret;
}
- for (const auto& time_series_index : time_series_indexs) {
- construct_column_context(time_series_index, time_filter);
+ // Try multi-value aligned path: one SSI reads all value columns at once.
+ bool used_multi = false;
+ if (time_series_indexs.size() > 1) {
+ bool can_multi = true;
+ for (const auto& ts_idx : time_series_indexs) {
+ if (ts_idx == nullptr ||
+ ts_idx->get_data_type() != common::VECTOR) {
+ can_multi = false;
+ break;
+ }
+ }
+ if (can_multi) {
+ auto& meas_cols =
+ device_query_task->get_column_mapping()
+ ->get_measurement_columns();
+ std::vector<std::string> meas_names(meas_cols.begin(),
+ meas_cols.end());
+ std::vector<std::vector<int32_t>> pos_list;
+ pos_list.reserve(meas_names.size());
+ for (const auto& name : meas_names) {
+ const auto& pos =
+ device_query_task->get_column_mapping()->get_column_pos(
+ name);
+ pos_list.push_back(
+ std::vector<int32_t>(pos.begin(), pos.end()));
+ }
+
+ auto* ctx =
+ new VectorMeasurementColumnContext(tsfile_io_reader_);
+ if (common::E_OK ==
+ ctx->init(device_query_task_, meas_names, time_filter,
+ pos_list, pa_)) {
+ for (const auto& name : meas_names) {
+ field_column_contexts_.insert(
+ std::make_pair(name, ctx));
+ }
+ aligned_col_count_ = meas_names.size();
+ used_multi = true;
+ } else {
+ delete ctx;
+ }
+ }
+ }
+
+ if (!used_multi) {
+ for (const auto& time_series_index : time_series_indexs) {
+ construct_column_context(time_series_index, time_filter);
+ }
+ }
+
+ // Detect aligned fast path: every field column comes from an aligned chunk.
+ if (!field_column_contexts_.empty() &&
+ aligned_col_count_ == field_column_contexts_.size()) {
+ all_aligned_ = true;
+ aligned_vec_.reserve(field_column_contexts_.size());
+ if (used_multi) {
+ // Single VectorMeasurementColumnContext handles all columns.
+ aligned_vec_.push_back(field_column_contexts_.begin()->second);
+ } else {
+ for (auto& kv : field_column_contexts_) {
+ aligned_vec_.push_back(kv.second);
+ }
+ }
}
// There is no data in this single device tsblock reader.
@@ -108,6 +173,10 @@ int SingleDeviceTsBlockReader::has_next(bool& has_next) {
current_block_->reset();
+ if (all_aligned_) {
+ return has_next_aligned(has_next);
+ }
+
bool next_time_set = false;
next_time_ = -1;
@@ -153,6 +222,83 @@ int SingleDeviceTsBlockReader::has_next(bool& has_next) {
return ret; // return value is not used
}
+int SingleDeviceTsBlockReader::has_next_aligned(bool& result_has_next) {
+ int ret = common::E_OK;
+ int time_in_query_index = tuple_desc_.get_time_column_index();
+
+ while (current_block_->get_row_count() < block_size_) {
+ if (aligned_vec_.empty()) break;
+
+ // Check if first column has data.
+ uint32_t avail = aligned_vec_[0]->available_rows();
+ if (avail == 0) {
+ for (auto* ctx : aligned_vec_) {
+ ctx->remove_from(field_column_contexts_);
+ }
+ aligned_vec_.clear();
+ break;
+ }
+
+ // Find the batch size: min of output capacity and all SSI availabilities.
+ uint32_t batch = block_size_ - current_block_->get_row_count();
+ for (auto* ctx : aligned_vec_) {
+ uint32_t ctx_avail = ctx->available_rows();
+ if (ctx_avail == 0) {
+ batch = 0;
+ break;
+ }
+ if (ctx_avail < batch) batch = ctx_avail;
+ }
+ if (batch == 0) {
+ for (auto* ctx : aligned_vec_) {
+ ctx->remove_from(field_column_contexts_);
+ }
+ aligned_vec_.clear();
+ break;
+ }
+
+ // First SSI: bulk copy time + values + row_count.
+ if (RET_FAIL(aligned_vec_[0]->bulk_copy_into(
+ col_appenders_, col_appenders_[time_column_index_],
+ row_appender_, batch))) {
+ break;
+ }
+
+ // Also copy time to explicit time column if requested.
+ if (time_in_query_index != -1) {
+ // Time data was just appended to time_column_index_ appender.
+ // Re-copy from the same source. The first SSI's time_iter already
+ // advanced, so we grab from the output time vector directly.
+ common::Vector* time_vec =
+ current_block_->get_vector(time_column_index_);
+ char* time_src =
+ time_vec->get_value_data().get_data() +
+ (current_block_->get_row_count() - batch) * sizeof(int64_t);
+ col_appenders_[time_in_query_index]->bulk_append_fixed(
+ time_src, batch, sizeof(int64_t));
+ }
+
+ // Other SSIs: bulk copy values only (no time, no row_count).
+ for (size_t i = 1; i < aligned_vec_.size(); i++) {
+ if (RET_FAIL(aligned_vec_[i]->bulk_copy_into(
+ col_appenders_, nullptr, nullptr, batch))) {
+ break;
+ }
+ }
+ if (IS_FAIL(ret)) break;
+ }
+
+ if (current_block_->get_row_count() > 0) {
+ if (RET_FAIL(fill_ids())) return ret;
+ current_block_->fill_trailling_nulls();
+ last_block_returned_ = false;
+ result_has_next = true;
+ } else {
+ result_has_next = false;
+ }
+ return ret;
+}
+
int SingleDeviceTsBlockReader::fill_measurements(
std::vector<MeasurementColumnContext*>& column_contexts) {
int ret = common::E_OK;
@@ -260,8 +406,15 @@ int SingleDeviceTsBlockReader::next(common::TsBlock*& ret_block) {
}
void SingleDeviceTsBlockReader::close() {
+ aligned_vec_.clear(); // non-owning; owned by field_column_contexts_
+ // De-duplicate pointers before deleting: VectorMeasurementColumnContext
+ // has multiple map entries pointing to the same object.
+ std::set<MeasurementColumnContext*> unique_contexts;
for (auto& column_context : field_column_contexts_) {
- delete column_context.second;
+ unique_contexts.insert(column_context.second);
+ }
+ for (auto* ctx : unique_contexts) {
+ delete ctx;
}
for (auto& col_appender : col_appenders_) {
if (col_appender) {
@@ -311,6 +464,7 @@ int SingleDeviceTsBlockReader::construct_column_context(
field_column_contexts_.insert(std::make_pair(
time_series_index->get_measurement_name().to_std_string(),
column_context));
+ aligned_col_count_++;
} else {
SingleMeasurementColumnContext* column_context =
new SingleMeasurementColumnContext(tsfile_io_reader_);
@@ -427,4 +581,224 @@ void SingleMeasurementColumnContext::fill_into(
}
}
+uint32_t SingleMeasurementColumnContext::available_rows() const {
+ if (!time_iter_ || time_iter_->end()) return 0;
+ return time_iter_->remaining();
+}
+
+int SingleMeasurementColumnContext::bulk_copy_into(
+ std::vector<common::ColAppender*>& col_appenders,
+ common::ColAppender* time_appender, common::RowAppender* row_appender,
+ uint32_t count) {
+ int ret = common::E_OK;
+ const uint32_t time_elem_size = sizeof(int64_t);
+ const uint32_t val_elem_size =
+ common::get_data_type_size(value_iter_->get_data_type());
+
+ // Bulk copy time column (only first SSI does this).
+ if (time_appender) {
+ time_appender->bulk_append_fixed(time_iter_->data_ptr(), count,
+ time_elem_size);
+ }
+
+ // Advance output row count (only first SSI does this).
+ if (row_appender) {
+ row_appender->add_rows(count);
+ }
+
+ // Bulk copy value column to each output position.
+ char* val_ptr = value_iter_->data_ptr();
+ for (int32_t pos : pos_in_result_) {
+ col_appenders[pos + 1]->bulk_append_fixed(val_ptr, count,
+ val_elem_size);
+ }
+
+ // Advance source iterators.
+ time_iter_->advance(count, time_elem_size);
+ value_iter_->advance(count, val_elem_size);
+
+ // If source TsBlock exhausted, load next.
+ if (time_iter_->end()) {
+ if (RET_FAIL(get_next_tsblock(false))) {
+ return ret;
+ }
+ }
+ return ret;
+}
+
+// ── VectorMeasurementColumnContext implementation ───────────────────────
+
+VectorMeasurementColumnContext::~VectorMeasurementColumnContext() {
+ if (time_iter_) {
+ delete time_iter_;
+ time_iter_ = nullptr;
+ }
+ for (auto* vi : value_iters_) {
+ if (vi) delete vi;
+ }
+ value_iters_.clear();
+ if (ssi_) {
+ ssi_->revert_tsblock();
+ }
+ tsfile_io_reader_->revert_ssi(ssi_);
+ ssi_ = nullptr;
+}
+
+int VectorMeasurementColumnContext::init(
+ DeviceQueryTask* device_query_task,
+ const std::vector<std::string>& measurement_names, Filter* time_filter,
+ std::vector<std::vector<int32_t>>& pos_in_result, common::PageArena& pa) {
+ int ret = common::E_OK;
+ pos_in_result_ = pos_in_result;
+ column_names_ = measurement_names;
+ if (RET_FAIL(tsfile_io_reader_->alloc_multi_ssi(
+ device_query_task->get_device_id(), measurement_names, ssi_, pa,
+ time_filter))) {
+ return ret;
+ }
+ if (RET_FAIL(get_next_tsblock(true))) {
+ return ret;
+ }
+ return ret;
+}
+
+int VectorMeasurementColumnContext::get_next_tsblock(bool alloc_mem) {
+ int ret = common::E_OK;
+ if (tsblock_ != nullptr) {
+ if (time_iter_) {
+ delete time_iter_;
+ time_iter_ = nullptr;
+ }
+ for (auto* vi : value_iters_) {
+ if (vi) delete vi;
+ }
+ value_iters_.clear();
+ tsblock_->reset();
+ }
+ if (RET_FAIL(ssi_->get_next(tsblock_, alloc_mem))) {
+ if (time_iter_) {
+ delete time_iter_;
+ time_iter_ = nullptr;
+ }
+ for (auto* vi : value_iters_) {
+ if (vi) delete vi;
+ }
+ value_iters_.clear();
+ if (tsblock_) {
+ ssi_->destroy();
+ tsblock_ = nullptr;
+ }
+ } else {
+ time_iter_ = new common::ColIterator(0, tsblock_);
+ uint32_t num_value_cols = tsblock_->get_column_count() - 1;
+ value_iters_.reserve(num_value_cols);
+ for (uint32_t c = 0; c < num_value_cols; c++) {
+ value_iters_.push_back(new common::ColIterator(c + 1, tsblock_));
+ }
+ }
+ return ret;
+}
+
+int VectorMeasurementColumnContext::get_current_time(int64_t& time) {
+ if (!time_iter_ || time_iter_->end()) return common::E_NO_MORE_DATA;
+ uint32_t len = 0;
+ time = *(int64_t*)(time_iter_->read(&len));
+ return common::E_OK;
+}
+
+int VectorMeasurementColumnContext::get_current_value(char*& value,
+ uint32_t& len) {
+ if (value_iters_.empty() || value_iters_[0]->end())
+ return common::E_NO_MORE_DATA;
+ bool is_null = false;
+ value = value_iters_[0]->read(&len, &is_null);
+ return common::E_OK;
+}
+
+int VectorMeasurementColumnContext::move_iter() {
+ int ret = common::E_OK;
+ time_iter_->next();
+ for (auto* vi : value_iters_) vi->next();
+ if (time_iter_->end()) {
+ if (RET_FAIL(get_next_tsblock(false))) return ret;
+ }
+ return ret;
+}
+
+void VectorMeasurementColumnContext::fill_into(
+ std::vector<common::ColAppender*>& col_appenders) {
+ for (uint32_t c = 0; c < value_iters_.size() && c < pos_in_result_.size();
+ c++) {
+ uint32_t len = 0;
+ bool is_null = false;
+ char* val = value_iters_[c]->read(&len, &is_null);
+ for (int32_t pos : pos_in_result_[c]) {
+ col_appenders[pos + 1]->add_row();
+ if (is_null) {
+ col_appenders[pos + 1]->append_null();
+ } else {
+ col_appenders[pos + 1]->append(val, len);
+ }
+ }
+ }
+}
+
+void VectorMeasurementColumnContext::remove_from(
+ std::map<std::string, MeasurementColumnContext*>& column_context_map) {
+ for (const auto& name : column_names_) {
+ column_context_map.erase(name);
+ }
+ delete this;
+}
+
+uint32_t VectorMeasurementColumnContext::available_rows() const {
+ if (!time_iter_ || time_iter_->end()) return 0;
+ return time_iter_->remaining();
+}
+
+int VectorMeasurementColumnContext::bulk_copy_into(
+ std::vector<common::ColAppender*>& col_appenders,
+ common::ColAppender* time_appender, common::RowAppender* row_appender,
+ uint32_t count) {
+ int ret = common::E_OK;
+ const uint32_t time_elem_size = sizeof(int64_t);
+
+ // Bulk copy time column (only when time_appender is provided).
+ if (time_appender) {
+ time_appender->bulk_append_fixed(time_iter_->data_ptr(), count,
+ time_elem_size);
+ }
+
+ // Advance output row count.
+ if (row_appender) {
+ row_appender->add_rows(count);
+ }
+
+ // Bulk copy each value column to its output positions.
+ for (uint32_t c = 0; c < value_iters_.size() && c < pos_in_result_.size();
+ c++) {
+ uint32_t val_elem_size =
+ common::get_data_type_size(value_iters_[c]->get_data_type());
+ char* val_ptr = value_iters_[c]->data_ptr();
+ for (int32_t pos : pos_in_result_[c]) {
+ col_appenders[pos + 1]->bulk_append_fixed(val_ptr, count,
+ val_elem_size);
+ }
+ }
+
+ // Advance all source iterators.
+ time_iter_->advance(count, time_elem_size);
+ for (uint32_t c = 0; c < value_iters_.size(); c++) {
+ uint32_t val_elem_size =
+ common::get_data_type_size(value_iters_[c]->get_data_type());
+ value_iters_[c]->advance(count, val_elem_size);
+ }
+
+ // If source TsBlock exhausted, load next.
+ if (time_iter_->end()) {
+ if (RET_FAIL(get_next_tsblock(false))) return ret;
+ }
+ return ret;
+}
+
} // namespace storage
diff --git a/cpp/src/reader/block/single_device_tsblock_reader.h b/cpp/src/reader/block/single_device_tsblock_reader.h
index 46ac8c417..a0ffca3e2 100644
--- a/cpp/src/reader/block/single_device_tsblock_reader.h
+++ b/cpp/src/reader/block/single_device_tsblock_reader.h
@@ -53,6 +53,9 @@ class SingleDeviceTsBlockReader : public TsBlockReader {
std::vector<MeasurementColumnContext*>& column_contexts);
int fill_ids();
int advance_column(MeasurementColumnContext* column_context);
+ // Fast path for aligned data: all columns share the same timestamps,
+ // so no per-row merge-sort is needed.
+ int has_next_aligned(bool& has_next);
DeviceQueryTask* device_query_task_;
Filter* field_filter_;
@@ -68,6 +71,11 @@ class SingleDeviceTsBlockReader : public TsBlockReader {
int64_t time_column_index_ = 0;
TsFileIOReader* tsfile_io_reader_;
common::PageArena pa_;
+ // Populated in init() when every field column comes from an aligned chunk.
+ // Provides cache-friendly vector iteration for has_next_aligned().
+ bool all_aligned_ = false;
+ uint32_t aligned_col_count_ = 0;
+ std::vector<MeasurementColumnContext*> aligned_vec_;
};
class MeasurementColumnContext {
@@ -91,6 +99,12 @@ class MeasurementColumnContext {
virtual int move_iter() = 0;
+ virtual uint32_t available_rows() const = 0;
+ virtual int bulk_copy_into(
+ std::vector<common::ColAppender*>& col_appenders,
+ common::ColAppender* time_appender, common::RowAppender* row_appender,
+ uint32_t count) = 0;
+
protected:
TsFileIOReader* tsfile_io_reader_;
TsFileSeriesScanIterator* ssi_ = nullptr;
@@ -129,6 +143,11 @@ class SingleMeasurementColumnContext final : public MeasurementColumnContext {
int get_current_time(int64_t& time) override;
int get_current_value(char*& value, uint32_t& len) override;
int move_iter() override;
+ uint32_t available_rows() const override;
+ int bulk_copy_into(std::vector<common::ColAppender*>& col_appenders,
+ common::ColAppender* time_appender,
+ common::RowAppender* row_appender,
+ uint32_t count) override;
private:
std::string column_name_;
@@ -139,21 +158,30 @@ class VectorMeasurementColumnContext final : public MeasurementColumnContext {
public:
explicit VectorMeasurementColumnContext(TsFileIOReader* tsfile_io_reader)
: MeasurementColumnContext(tsfile_io_reader) {}
+ ~VectorMeasurementColumnContext() override;
void fill_into(std::vector<common::ColAppender*>& col_appenders) override;
void remove_from(std::map<std::string, MeasurementColumnContext*>&
column_context_map) override;
int init(DeviceQueryTask* device_query_task,
- const ITimeseriesIndex* time_series_index, Filter* time_filter,
+ const std::vector<std::string>& measurement_names,
+ Filter* time_filter,
std::vector<std::vector<int32_t>>& pos_in_result,
common::PageArena& pa);
int get_next_tsblock(bool alloc_mem) override;
int get_current_time(int64_t& time) override;
int get_current_value(char*& value, uint32_t& len) override;
int move_iter() override;
+ uint32_t available_rows() const override;
+ int bulk_copy_into(std::vector<common::ColAppender*>& col_appenders,
+ common::ColAppender* time_appender,
+ common::RowAppender* row_appender,
+ uint32_t count) override;
private:
+ std::vector<std::string> column_names_;
std::vector<std::vector<int32_t>> pos_in_result_;
+ std::vector<common::ColIterator*> value_iters_;
};
class IdColumnContext {
diff --git a/cpp/src/reader/chunk_reader.cc b/cpp/src/reader/chunk_reader.cc
index 1b3160b72..63bc07583 100644
--- a/cpp/src/reader/chunk_reader.cc
+++ b/cpp/src/reader/chunk_reader.cc
@@ -406,8 +406,6 @@ int ChunkReader::i32_DECODE_TYPED_TV_INTO_TSBLOCK(ByteStream& time_in,
row_appender.backoff_add_row();
continue;
} else {
- /*std::cout << "decoder: time=" << time << ", value=" << value
- * << std::endl;*/
row_appender.append(0, (char*)&time, sizeof(time));
row_appender.append(1, (char*)&value, sizeof(value));
}
@@ -416,6 +414,322 @@ int ChunkReader::i32_DECODE_TYPED_TV_INTO_TSBLOCK(ByteStream& time_in,
return ret;
}
+int ChunkReader::i32_DECODE_TV_BATCH(ByteStream& time_in,
+ ByteStream& value_in,
+ RowAppender& row_appender,
+ Filter* filter) {
+ int ret = E_OK;
+ const int BATCH = 129;
+ int64_t times[BATCH];
+ int32_t values[BATCH];
+
+ while (time_decoder_->has_remaining(time_in)) {
+ if (row_appender.remaining() < (uint32_t)BATCH) {
+ ret = E_OVERFLOW;
+ break;
+ }
+
+ // Block-level time filter check
+ bool block_all_pass = false;
+ if (filter != nullptr) {
+ int64_t block_min, block_max;
+ int block_count;
+ if (time_decoder_->peek_next_block_range_int64(
+ time_in, block_min, block_max, block_count)) {
+ if (!filter->satisfy_start_end_time(block_min, block_max)) {
+ int skipped = 0;
+ time_decoder_->skip_peeked_block_int64(time_in, skipped);
+ value_decoder_->skip_int32(block_count, skipped, value_in);
+ continue;
+ }
+ if (filter->contain_start_end_time(block_min, block_max)) {
+ block_all_pass = true;
+ }
+ }
+ }
+
+ int time_count = 0;
+ int value_count = 0;
+
+ if (RET_FAIL(time_decoder_->read_batch_int64(
+ times, BATCH, time_count, time_in))) {
+ break;
+ }
+ if (time_count == 0) break;
+
+ bool time_mask[BATCH];
+ int pass_count = time_count;
+ if (filter != nullptr && !block_all_pass) {
+ pass_count = filter->satisfy_batch_time(times, time_count,
+ time_mask);
+ }
+
+ if (pass_count == 0) {
+ int skipped = 0;
+ value_decoder_->skip_int32(time_count, skipped, value_in);
+ continue;
+ }
+
+ if (RET_FAIL(value_decoder_->read_batch_int32(
+ values, BATCH, value_count, value_in))) {
+ break;
+ }
+
+ for (int i = 0; i < time_count; ++i) {
+ if (filter != nullptr && !block_all_pass && !time_mask[i]) {
+ continue;
+ }
+ if (filter != nullptr && !block_all_pass &&
+ !filter->satisfy(times[i], (int64_t)values[i])) {
+ continue;
+ }
+ if (UNLIKELY(!row_appender.add_row())) {
+ ret = E_OVERFLOW;
+ break;
+ }
+ row_appender.append(0, (char*)×[i], sizeof(int64_t));
+ row_appender.append(1, (char*)&values[i], sizeof(int32_t));
+ }
+ if (ret != E_OK) break;
+ }
+ return ret;
+}
+
+int ChunkReader::i64_DECODE_TV_BATCH(ByteStream& time_in,
+ ByteStream& value_in,
+ RowAppender& row_appender,
+ Filter* filter) {
+ int ret = E_OK;
+ const int BATCH = 129;
+ int64_t times[BATCH];
+ int64_t values[BATCH];
+
+ while (time_decoder_->has_remaining(time_in)) {
+ if (row_appender.remaining() < (uint32_t)BATCH) {
+ ret = E_OVERFLOW;
+ break;
+ }
+
+ // Block-level time filter check
+ bool block_all_pass = false;
+ if (filter != nullptr) {
+ int64_t block_min, block_max;
+ int block_count;
+ if (time_decoder_->peek_next_block_range_int64(
+ time_in, block_min, block_max, block_count)) {
+ if (!filter->satisfy_start_end_time(block_min, block_max)) {
+ int skipped = 0;
+ time_decoder_->skip_peeked_block_int64(time_in, skipped);
+ value_decoder_->skip_int64(block_count, skipped, value_in);
+ continue;
+ }
+ if (filter->contain_start_end_time(block_min, block_max)) {
+ block_all_pass = true;
+ }
+ }
+ }
+
+ int time_count = 0;
+ int value_count = 0;
+
+ if (RET_FAIL(time_decoder_->read_batch_int64(
+ times, BATCH, time_count, time_in))) {
+ break;
+ }
+ if (time_count == 0) break;
+
+ bool time_mask[BATCH];
+ int pass_count = time_count;
+ if (filter != nullptr && !block_all_pass) {
+ pass_count = filter->satisfy_batch_time(times, time_count,
+ time_mask);
+ }
+
+ if (pass_count == 0) {
+ int skipped = 0;
+ value_decoder_->skip_int64(time_count, skipped, value_in);
+ continue;
+ }
+
+ if (RET_FAIL(value_decoder_->read_batch_int64(
+ values, BATCH, value_count, value_in))) {
+ break;
+ }
+
+ for (int i = 0; i < time_count; ++i) {
+ if (filter != nullptr && !block_all_pass && !time_mask[i]) {
+ continue;
+ }
+ if (filter != nullptr && !block_all_pass &&
+ !filter->satisfy(times[i], values[i])) {
+ continue;
+ }
+ if (UNLIKELY(!row_appender.add_row())) {
+ ret = E_OVERFLOW;
+ break;
+ }
+ row_appender.append(0, (char*)×[i], sizeof(int64_t));
+ row_appender.append(1, (char*)&values[i], sizeof(int64_t));
+ }
+ if (ret != E_OK) break;
+ }
+ return ret;
+}
+
+int ChunkReader::float_DECODE_TV_BATCH(ByteStream& time_in,
+ ByteStream& value_in,
+ RowAppender& row_appender,
+ Filter* filter) {
+ int ret = E_OK;
+ const int BATCH = 129;
+ int64_t times[BATCH];
+ float values[BATCH];
+
+ while (time_decoder_->has_remaining(time_in)) {
+ if (row_appender.remaining() < (uint32_t)BATCH) {
+ ret = E_OVERFLOW;
+ break;
+ }
+
+ // Block-level time filter check
+ bool block_all_pass = false;
+ if (filter != nullptr) {
+ int64_t block_min, block_max;
+ int block_count;
+ if (time_decoder_->peek_next_block_range_int64(
+ time_in, block_min, block_max, block_count)) {
+ if (!filter->satisfy_start_end_time(block_min, block_max)) {
+ int skipped = 0;
+ time_decoder_->skip_peeked_block_int64(time_in, skipped);
+ value_decoder_->skip_float(block_count, skipped, value_in);
+ continue;
+ }
+ if (filter->contain_start_end_time(block_min, block_max)) {
+ block_all_pass = true;
+ }
+ }
+ }
+
+ int time_count = 0;
+ int value_count = 0;
+
+ if (RET_FAIL(time_decoder_->read_batch_int64(
+ times, BATCH, time_count, time_in))) {
+ break;
+ }
+ if (time_count == 0) break;
+
+ bool time_mask[BATCH];
+ int pass_count = time_count;
+ if (filter != nullptr && !block_all_pass) {
+ pass_count = filter->satisfy_batch_time(times, time_count,
+ time_mask);
+ }
+
+ if (pass_count == 0) {
+ int skipped = 0;
+ value_decoder_->skip_float(time_count, skipped, value_in);
+ continue;
+ }
+
+ if (RET_FAIL(value_decoder_->read_batch_float(
+ values, BATCH, value_count, value_in))) {
+ break;
+ }
+
+ for (int i = 0; i < time_count; ++i) {
+ if (filter != nullptr && !block_all_pass && !time_mask[i]) {
+ continue;
+ }
+ if (UNLIKELY(!row_appender.add_row())) {
+ ret = E_OVERFLOW;
+ break;
+ }
+ row_appender.append(0, (char*)×[i], sizeof(int64_t));
+ row_appender.append(1, (char*)&values[i], sizeof(float));
+ }
+ if (ret != E_OK) break;
+ }
+ return ret;
+}
+
+int ChunkReader::double_DECODE_TV_BATCH(ByteStream& time_in,
+ ByteStream& value_in,
+ RowAppender& row_appender,
+ Filter* filter) {
+ int ret = E_OK;
+ const int BATCH = 129;
+ int64_t times[BATCH];
+ double values[BATCH];
+
+ while (time_decoder_->has_remaining(time_in)) {
+ if (row_appender.remaining() < (uint32_t)BATCH) {
+ ret = E_OVERFLOW;
+ break;
+ }
+
+ // Block-level time filter check
+ bool block_all_pass = false;
+ if (filter != nullptr) {
+ int64_t block_min, block_max;
+ int block_count;
+ if (time_decoder_->peek_next_block_range_int64(
+ time_in, block_min, block_max, block_count)) {
+ if (!filter->satisfy_start_end_time(block_min, block_max)) {
+ int skipped = 0;
+ time_decoder_->skip_peeked_block_int64(time_in, skipped);
+ value_decoder_->skip_double(block_count, skipped, value_in);
+ continue;
+ }
+ if (filter->contain_start_end_time(block_min, block_max)) {
+ block_all_pass = true;
+ }
+ }
+ }
+
+ int time_count = 0;
+ int value_count = 0;
+
+ if (RET_FAIL(time_decoder_->read_batch_int64(
+ times, BATCH, time_count, time_in))) {
+ break;
+ }
+ if (time_count == 0) break;
+
+ bool time_mask[BATCH];
+ int pass_count = time_count;
+ if (filter != nullptr && !block_all_pass) {
+ pass_count = filter->satisfy_batch_time(times, time_count,
+ time_mask);
+ }
+
+ if (pass_count == 0) {
+ int skipped = 0;
+ value_decoder_->skip_double(time_count, skipped, value_in);
+ continue;
+ }
+
+ if (RET_FAIL(value_decoder_->read_batch_double(
+ values, BATCH, value_count, value_in))) {
+ break;
+ }
+
+ for (int i = 0; i < time_count; ++i) {
+ if (filter != nullptr && !block_all_pass && !time_mask[i]) {
+ continue;
+ }
+ if (UNLIKELY(!row_appender.add_row())) {
+ ret = E_OVERFLOW;
+ break;
+ }
+ row_appender.append(0, (char*)×[i], sizeof(int64_t));
+ row_appender.append(1, (char*)&values[i], sizeof(double));
+ }
+ if (ret != E_OK) break;
+ }
+ return ret;
+}
+
int ChunkReader::STRING_DECODE_TYPED_TV_INTO_TSBLOCK(ByteStream& time_in,
ByteStream& value_in,
RowAppender& row_appender,
@@ -456,23 +770,21 @@ int ChunkReader::decode_tv_buf_into_tsblock_by_datatype(ByteStream& time_in,
break;
case common::DATE:
case common::INT32:
- // DECODE_TYPED_TV_INTO_TSBLOCK(int32_t, int32, time_in_, value_in_,
- // row_appender);
- ret = i32_DECODE_TYPED_TV_INTO_TSBLOCK(time_in_, value_in_,
- row_appender, filter);
+ ret = i32_DECODE_TV_BATCH(time_in_, value_in_,
+ row_appender, filter);
break;
case TIMESTAMP:
case common::INT64:
- DECODE_TYPED_TV_INTO_TSBLOCK(int64_t, int64, time_in_, value_in_,
- row_appender);
+ ret = i64_DECODE_TV_BATCH(time_in_, value_in_,
+ row_appender, filter);
break;
case common::FLOAT:
- DECODE_TYPED_TV_INTO_TSBLOCK(float, float, time_in_, value_in_,
- row_appender);
+ ret = float_DECODE_TV_BATCH(time_in_, value_in_,
+ row_appender, filter);
break;
case common::DOUBLE:
- DECODE_TYPED_TV_INTO_TSBLOCK(double, double, time_in_, value_in_,
- row_appender);
+ ret = double_DECODE_TV_BATCH(time_in_, value_in_,
+ row_appender, filter);
break;
case common::TEXT:
case common::BLOB:
diff --git a/cpp/src/reader/chunk_reader.h b/cpp/src/reader/chunk_reader.h
index 106b8648b..4ef620a0a 100644
--- a/cpp/src/reader/chunk_reader.h
+++ b/cpp/src/reader/chunk_reader.h
@@ -99,6 +99,22 @@ class ChunkReader : public IChunkReader {
common::ByteStream& value_in,
common::RowAppender& row_appender,
Filter* filter);
+ int i32_DECODE_TV_BATCH(common::ByteStream& time_in,
+ common::ByteStream& value_in,
+ common::RowAppender& row_appender,
+ Filter* filter);
+ int i64_DECODE_TV_BATCH(common::ByteStream& time_in,
+ common::ByteStream& value_in,
+ common::RowAppender& row_appender,
+ Filter* filter);
+ int float_DECODE_TV_BATCH(common::ByteStream& time_in,
+ common::ByteStream& value_in,
+ common::RowAppender& row_appender,
+ Filter* filter);
+ int double_DECODE_TV_BATCH(common::ByteStream& time_in,
+ common::ByteStream& value_in,
+ common::RowAppender& row_appender,
+ Filter* filter);
int STRING_DECODE_TYPED_TV_INTO_TSBLOCK(common::ByteStream& time_in,
common::ByteStream& value_in,
common::RowAppender& row_appender,
@@ -125,7 +141,7 @@ class ChunkReader : public IChunkReader {
* also refer to offset within the chunk (including chunk header).
* It advanced by step of a page header or a page tv data.
*/
- common::ByteStream in_stream_{common::MOD_CHUNK_READER};
+ common::ByteStream in_stream_;
int32_t file_data_buf_size_;
uint32_t chunk_visit_offset_;
@@ -135,8 +151,8 @@ class ChunkReader : public IChunkReader {
Decoder* time_decoder_;
Decoder* value_decoder_;
- common::ByteStream time_in_{common::MOD_CHUNK_READER};
- common::ByteStream value_in_{common::MOD_CHUNK_READER};
+ common::ByteStream time_in_;
+ common::ByteStream value_in_;
char* uncompressed_buf_;
};
diff --git a/cpp/src/reader/filter/and_filter.h b/cpp/src/reader/filter/and_filter.h
index 0d01000f8..fd70c163f 100644
--- a/cpp/src/reader/filter/and_filter.h
+++ b/cpp/src/reader/filter/and_filter.h
@@ -50,6 +50,18 @@ class AndFilter : public BinaryFilter {
right_->contain_start_end_time(start_time, end_time);
}
+ int satisfy_batch_time(const int64_t* times, int count, bool* mask) {
+ bool mask_right[129];
+ left_->satisfy_batch_time(times, count, mask);
+ right_->satisfy_batch_time(times, count, mask_right);
+ int pass = 0;
+ for (int i = 0; i < count; ++i) {
+ mask[i] = mask[i] && mask_right[i];
+ if (mask[i]) ++pass;
+ }
+ return pass;
+ }
+
std::vector<TimeRange*>* get_time_ranges() {
std::vector<TimeRange*>* result = new std::vector<TimeRange*>();
std::vector<TimeRange*>* left_time_ranges = left_->get_time_ranges();
diff --git a/cpp/src/reader/filter/filter.h b/cpp/src/reader/filter/filter.h
index f39dddbae..e53992308 100644
--- a/cpp/src/reader/filter/filter.h
+++ b/cpp/src/reader/filter/filter.h
@@ -63,6 +63,20 @@ class Filter {
ASSERT(false);
return nullptr;
}
+
+ // Batch time filter: evaluate time filter on an array of timestamps.
+ // Writes true/false into @mask for each element.
+ // Returns the number of elements that passed (mask[i] == true).
+ // Default: scalar fallback using satisfy_start_end_time.
+ virtual int satisfy_batch_time(const int64_t* times, int count,
+ bool* mask) {
+ int pass = 0;
+ for (int i = 0; i < count; ++i) {
+ mask[i] = satisfy_start_end_time(times[i], times[i]);
+ if (mask[i]) ++pass;
+ }
+ return pass;
+ }
};
} // namespace storage
diff --git a/cpp/src/reader/filter/or_filter.h b/cpp/src/reader/filter/or_filter.h
index 1d4aa6aa7..10cfde164 100644
--- a/cpp/src/reader/filter/or_filter.h
+++ b/cpp/src/reader/filter/or_filter.h
@@ -50,6 +50,18 @@ class OrFilter : public BinaryFilter {
right_->contain_start_end_time(start_time, end_time);
}
+ int satisfy_batch_time(const int64_t* times, int count, bool* mask) {
+ bool mask_right[129];
+ left_->satisfy_batch_time(times, count, mask);
+ right_->satisfy_batch_time(times, count, mask_right);
+ int pass = 0;
+ for (int i = 0; i < count; ++i) {
+ mask[i] = mask[i] || mask_right[i];
+ if (mask[i]) ++pass;
+ }
+ return pass;
+ }
+
std::vector<TimeRange*>* get_time_ranges() {
std::vector<TimeRange*>* result = new std::vector<TimeRange*>();
std::vector<TimeRange*>* left_time_ranges = left_->get_time_ranges();
diff --git a/cpp/src/reader/filter/time_operator.cc b/cpp/src/reader/filter/time_operator.cc
index 19f33b599..e9c5686fd 100644
--- a/cpp/src/reader/filter/time_operator.cc
+++ b/cpp/src/reader/filter/time_operator.cc
@@ -18,9 +18,17 @@
*/
#include "time_operator.h"
+#include <cstring>
+
#include "common/statistic.h"
#include "utils/storage_utils.h"
+#if defined(__ARM_NEON)
+#include <arm_neon.h>
+#elif defined(ENABLE_SIMD)
+#include "simde/x86/avx2.h"
+#endif
+
namespace storage {
TimeBetween::TimeBetween(int64_t value1, int64_t value2, bool not_between)
@@ -308,4 +316,270 @@ std::vector<TimeRange*>* TimeLtEq::get_time_ranges() {
return result;
}
+// ============================================================================
+// SIMD batch time filter implementations
+// ============================================================================
+
+// Helper: extract 4-bit movemask from 256-bit comparison result (4 x i64)
+#if !defined(__ARM_NEON) && defined(ENABLE_SIMD)
+static inline int simd_movemask_epi64(simde__m256i v) {
+ // movemask_pd reinterprets as double and checks sign bit = high bit of each
+ // 64-bit lane
+ return simde_mm256_movemask_pd(simde_mm256_castsi256_pd(v));
+}
+#endif
+
+int TimeGt::satisfy_batch_time(const int64_t* times, int count, bool* mask) {
+ int pass = 0;
+ int i = 0;
+#if defined(__ARM_NEON)
+ int64x2_t vval = vdupq_n_s64(value_);
+ for (; i + 1 < count; i += 2) {
+ int64x2_t vt = vld1q_s64(times + i);
+ uint64x2_t cmp = vcgtq_s64(vt, vval);
+ mask[i] = vgetq_lane_u64(cmp, 0) != 0;
+ mask[i + 1] = vgetq_lane_u64(cmp, 1) != 0;
+ pass += mask[i] + mask[i + 1];
+ }
+#elif defined(ENABLE_SIMD)
+ simde__m256i vval = simde_mm256_set1_epi64x(value_);
+ for (; i + 3 < count; i += 4) {
+ simde__m256i vt =
+ simde_mm256_loadu_si256((const simde__m256i*)(times + i));
+ // time > value_ => cmpgt(time, value_)
+ simde__m256i cmp = simde_mm256_cmpgt_epi64(vt, vval);
+ int bits = simd_movemask_epi64(cmp);
+ for (int j = 0; j < 4; ++j) {
+ mask[i + j] = (bits >> j) & 1;
+ pass += mask[i + j];
+ }
+ }
+#endif
+ for (; i < count; ++i) {
+ mask[i] = value_ < times[i];
+ if (mask[i]) ++pass;
+ }
+ return pass;
+}
+
+int TimeGtEq::satisfy_batch_time(const int64_t* times, int count, bool* mask) {
+ int pass = 0;
+ int i = 0;
+#if defined(__ARM_NEON)
+ int64x2_t vval = vdupq_n_s64(value_);
+ for (; i + 1 < count; i += 2) {
+ int64x2_t vt = vld1q_s64(times + i);
+ uint64x2_t cmp = vcgeq_s64(vt, vval);
+ mask[i] = vgetq_lane_u64(cmp, 0) != 0;
+ mask[i + 1] = vgetq_lane_u64(cmp, 1) != 0;
+ pass += mask[i] + mask[i + 1];
+ }
+#elif defined(ENABLE_SIMD)
+ simde__m256i vval = simde_mm256_set1_epi64x(value_);
+ for (; i + 3 < count; i += 4) {
+ simde__m256i vt =
+ simde_mm256_loadu_si256((const simde__m256i*)(times + i));
+ // time >= value_ => NOT(cmpgt(value_, time))
+ simde__m256i cmp = simde_mm256_cmpgt_epi64(vval, vt);
+ simde__m256i ncmp = simde_mm256_xor_si256(
+ cmp, simde_mm256_set1_epi64x((int64_t)-1));
+ int bits = simd_movemask_epi64(ncmp);
+ for (int j = 0; j < 4; ++j) {
+ mask[i + j] = (bits >> j) & 1;
+ pass += mask[i + j];
+ }
+ }
+#endif
+ for (; i < count; ++i) {
+ mask[i] = value_ <= times[i];
+ if (mask[i]) ++pass;
+ }
+ return pass;
+}
+
+int TimeLt::satisfy_batch_time(const int64_t* times, int count, bool* mask) {
+ int pass = 0;
+ int i = 0;
+#if defined(__ARM_NEON)
+ int64x2_t vval = vdupq_n_s64(value_);
+ for (; i + 1 < count; i += 2) {
+ int64x2_t vt = vld1q_s64(times + i);
+ uint64x2_t cmp = vcltq_s64(vt, vval);
+ mask[i] = vgetq_lane_u64(cmp, 0) != 0;
+ mask[i + 1] = vgetq_lane_u64(cmp, 1) != 0;
+ pass += mask[i] + mask[i + 1];
+ }
+#elif defined(ENABLE_SIMD)
+ simde__m256i vval = simde_mm256_set1_epi64x(value_);
+ for (; i + 3 < count; i += 4) {
+ simde__m256i vt =
+ simde_mm256_loadu_si256((const simde__m256i*)(times + i));
+ // time < value_ => cmpgt(value_, time)
+ simde__m256i cmp = simde_mm256_cmpgt_epi64(vval, vt);
+ int bits = simd_movemask_epi64(cmp);
+ for (int j = 0; j < 4; ++j) {
+ mask[i + j] = (bits >> j) & 1;
+ pass += mask[i + j];
+ }
+ }
+#endif
+ for (; i < count; ++i) {
+ mask[i] = value_ > times[i];
+ if (mask[i]) ++pass;
+ }
+ return pass;
+}
+
+int TimeLtEq::satisfy_batch_time(const int64_t* times, int count, bool* mask) {
+ int pass = 0;
+ int i = 0;
+#if defined(__ARM_NEON)
+ int64x2_t vval = vdupq_n_s64(value_);
+ for (; i + 1 < count; i += 2) {
+ int64x2_t vt = vld1q_s64(times + i);
+ uint64x2_t cmp = vcleq_s64(vt, vval);
+ mask[i] = vgetq_lane_u64(cmp, 0) != 0;
+ mask[i + 1] = vgetq_lane_u64(cmp, 1) != 0;
+ pass += mask[i] + mask[i + 1];
+ }
+#elif defined(ENABLE_SIMD)
+ simde__m256i vval = simde_mm256_set1_epi64x(value_);
+ for (; i + 3 < count; i += 4) {
+ simde__m256i vt =
+ simde_mm256_loadu_si256((const simde__m256i*)(times + i));
+ // time <= value_ => NOT(cmpgt(time, value_))
+ simde__m256i cmp = simde_mm256_cmpgt_epi64(vt, vval);
+ simde__m256i ncmp = simde_mm256_xor_si256(
+ cmp, simde_mm256_set1_epi64x((int64_t)-1));
+ int bits = simd_movemask_epi64(ncmp);
+ for (int j = 0; j < 4; ++j) {
+ mask[i + j] = (bits >> j) & 1;
+ pass += mask[i + j];
+ }
+ }
+#endif
+ for (; i < count; ++i) {
+ mask[i] = value_ >= times[i];
+ if (mask[i]) ++pass;
+ }
+ return pass;
+}
+
+int TimeEq::satisfy_batch_time(const int64_t* times, int count, bool* mask) {
+ int pass = 0;
+ int i = 0;
+#if defined(__ARM_NEON)
+ int64x2_t vval = vdupq_n_s64(value_);
+ for (; i + 1 < count; i += 2) {
+ int64x2_t vt = vld1q_s64(times + i);
+ uint64x2_t cmp = vceqq_s64(vt, vval);
+ mask[i] = vgetq_lane_u64(cmp, 0) != 0;
+ mask[i + 1] = vgetq_lane_u64(cmp, 1) != 0;
+ pass += mask[i] + mask[i + 1];
+ }
+#elif defined(ENABLE_SIMD)
+ simde__m256i vval = simde_mm256_set1_epi64x(value_);
+ for (; i + 3 < count; i += 4) {
+ simde__m256i vt =
+ simde_mm256_loadu_si256((const simde__m256i*)(times + i));
+ simde__m256i cmp = simde_mm256_cmpeq_epi64(vt, vval);
+ int bits = simd_movemask_epi64(cmp);
+ for (int j = 0; j < 4; ++j) {
+ mask[i + j] = (bits >> j) & 1;
+ pass += mask[i + j];
+ }
+ }
+#endif
+ for (; i < count; ++i) {
+ mask[i] = value_ == times[i];
+ if (mask[i]) ++pass;
+ }
+ return pass;
+}
+
+int TimeNotEq::satisfy_batch_time(const int64_t* times, int count,
+ bool* mask) {
+ int pass = 0;
+ int i = 0;
+#if defined(__ARM_NEON)
+ int64x2_t vval = vdupq_n_s64(value_);
+ uint64x2_t ones = vdupq_n_u64(UINT64_MAX);
+ for (; i + 1 < count; i += 2) {
+ int64x2_t vt = vld1q_s64(times + i);
+ uint64x2_t cmp = veorq_u64(vceqq_s64(vt, vval), ones);
+ mask[i] = vgetq_lane_u64(cmp, 0) != 0;
+ mask[i + 1] = vgetq_lane_u64(cmp, 1) != 0;
+ pass += mask[i] + mask[i + 1];
+ }
+#elif defined(ENABLE_SIMD)
+ simde__m256i vval = simde_mm256_set1_epi64x(value_);
+ for (; i + 3 < count; i += 4) {
+ simde__m256i vt =
+ simde_mm256_loadu_si256((const simde__m256i*)(times + i));
+ simde__m256i eq = simde_mm256_cmpeq_epi64(vt, vval);
+ simde__m256i neq = simde_mm256_xor_si256(
+ eq, simde_mm256_set1_epi64x((int64_t)-1));
+ int bits = simd_movemask_epi64(neq);
+ for (int j = 0; j < 4; ++j) {
+ mask[i + j] = (bits >> j) & 1;
+ pass += mask[i + j];
+ }
+ }
+#endif
+ for (; i < count; ++i) {
+ mask[i] = value_ != times[i];
+ if (mask[i]) ++pass;
+ }
+ return pass;
+}
+
+int TimeBetween::satisfy_batch_time(const int64_t* times, int count,
+ bool* mask) {
+ int pass = 0;
+ int i = 0;
+#if defined(__ARM_NEON)
+ int64x2_t vlo = vdupq_n_s64(value1_);
+ int64x2_t vhi = vdupq_n_s64(value2_);
+ uint64x2_t ones = vdupq_n_u64(UINT64_MAX);
+ for (; i + 1 < count; i += 2) {
+ int64x2_t vt = vld1q_s64(times + i);
+ uint64x2_t ge_lo = vcgeq_s64(vt, vlo);
+ uint64x2_t le_hi = vcleq_s64(vt, vhi);
+ uint64x2_t between = vandq_u64(ge_lo, le_hi);
+ uint64x2_t result = not_ ? veorq_u64(between, ones) : between;
+ mask[i] = vgetq_lane_u64(result, 0) != 0;
+ mask[i + 1] = vgetq_lane_u64(result, 1) != 0;
+ pass += mask[i] + mask[i + 1];
+ }
+#elif defined(ENABLE_SIMD)
+ simde__m256i vlo = simde_mm256_set1_epi64x(value1_);
+ simde__m256i vhi = simde_mm256_set1_epi64x(value2_);
+ simde__m256i ones = simde_mm256_set1_epi64x((int64_t)-1);
+ for (; i + 3 < count; i += 4) {
+ simde__m256i vt =
+ simde_mm256_loadu_si256((const simde__m256i*)(times + i));
+ // time >= lo => NOT(cmpgt(lo, time))
+ simde__m256i ge_lo = simde_mm256_xor_si256(
+ simde_mm256_cmpgt_epi64(vlo, vt), ones);
+ // time <= hi => NOT(cmpgt(time, hi))
+ simde__m256i le_hi = simde_mm256_xor_si256(
+ simde_mm256_cmpgt_epi64(vt, vhi), ones);
+ simde__m256i between = simde_mm256_and_si256(ge_lo, le_hi);
+ simde__m256i result = not_ ? simde_mm256_xor_si256(between, ones)
+ : between;
+ int bits = simd_movemask_epi64(result);
+ for (int j = 0; j < 4; ++j) {
+ mask[i + j] = (bits >> j) & 1;
+ pass += mask[i + j];
+ }
+ }
+#endif
+ for (; i < count; ++i) {
+ bool in_range = (value1_ <= times[i]) && (times[i] <= value2_);
+ mask[i] = not_ ? !in_range : in_range;
+ if (mask[i]) ++pass;
+ }
+ return pass;
+}
+
} // namespace storage
diff --git a/cpp/src/reader/filter/time_operator.h b/cpp/src/reader/filter/time_operator.h
index 29930b88a..f972a4259 100644
--- a/cpp/src/reader/filter/time_operator.h
+++ b/cpp/src/reader/filter/time_operator.h
@@ -47,6 +47,9 @@ class TimeBetween : public Filter {
bool contain_start_end_time(int64_t start_time, int64_t end_time);
std::vector<TimeRange*>* get_time_ranges();
+
+ int satisfy_batch_time(const int64_t* times, int count, bool* mask);
+
FilterType get_filter_type() { return type_; }
private:
@@ -99,6 +102,8 @@ class TimeEq : public Filter {
std::vector<TimeRange*>* get_time_ranges();
+ int satisfy_batch_time(const int64_t* times, int count, bool* mask);
+
FilterType get_filter_type() { return type_; }
private:
@@ -122,6 +127,9 @@ class TimeNotEq : public Filter {
bool contain_start_end_time(int64_t start_time, int64_t end_time);
std::vector<TimeRange*>* get_time_ranges();
+
+ int satisfy_batch_time(const int64_t* times, int count, bool* mask);
+
FilterType get_filter_type() { return type_; }
private:
@@ -146,6 +154,8 @@ class TimeGt : public Filter {
std::vector<TimeRange*>* get_time_ranges();
+ int satisfy_batch_time(const int64_t* times, int count, bool* mask);
+
FilterType get_filter_type() { return type_; }
private:
@@ -169,6 +179,9 @@ class TimeGtEq : public Filter {
bool contain_start_end_time(int64_t start_time, int64_t end_time);
std::vector<TimeRange*>* get_time_ranges();
+
+ int satisfy_batch_time(const int64_t* times, int count, bool* mask);
+
void reset_value(int64_t val) { value_ = val; }
FilterType get_filter_type() { return type_; }
@@ -194,6 +207,8 @@ class TimeLt : public Filter {
std::vector<TimeRange*>* get_time_ranges();
+ int satisfy_batch_time(const int64_t* times, int count, bool* mask);
+
FilterType get_filter_type() { return type_; }
private:
@@ -217,6 +232,9 @@ class TimeLtEq : public Filter {
bool contain_start_end_time(int64_t start_time, int64_t end_time);
std::vector<TimeRange*>* get_time_ranges();
+
+ int satisfy_batch_time(const int64_t* times, int count, bool* mask);
+
FilterType get_filter_type() { return type_; }
private:
diff --git a/cpp/src/reader/result_set.h b/cpp/src/reader/result_set.h
index 216fced4f..c230e0a84 100644
--- a/cpp/src/reader/result_set.h
+++ b/cpp/src/reader/result_set.h
@@ -305,7 +305,7 @@ inline ResultSetIterator ResultSet::iterator() {
return ResultSetIterator(this);
}
-static MAYBE_UNUSED void print_table_result_set(
+MAYBE_UNUSED static void print_table_result_set(
storage::ResultSet* table_result_set) {
if (table_result_set == nullptr) {
std::cout << "TableResultSet is nullptr" << std::endl;
diff --git a/cpp/src/reader/tsfile_series_scan_iterator.cc b/cpp/src/reader/tsfile_series_scan_iterator.cc
index 8130bd8ba..5d9b54a16 100644
--- a/cpp/src/reader/tsfile_series_scan_iterator.cc
+++ b/cpp/src/reader/tsfile_series_scan_iterator.cc
@@ -38,7 +38,6 @@ void TsFileSeriesScanIterator::destroy() {
int TsFileSeriesScanIterator::get_next(TsBlock*& ret_tsblock, bool alloc,
Filter* oneshoot_filter) {
- // TODO @filter
int ret = E_OK;
Filter* filter =
(oneshoot_filter != nullptr) ? oneshoot_filter : time_filter_;
@@ -46,38 +45,52 @@ int TsFileSeriesScanIterator::get_next(TsBlock*& ret_tsblock, bool alloc,
while (true) {
if (!has_next_chunk()) {
return E_NO_MORE_DATA;
+ } else if (is_multi_value_) {
+ // Multi-value aligned path
+ ChunkMeta* time_cm = time_chunk_meta_cursor_.get();
+ std::vector<ChunkMeta*> value_cms;
+ value_cms.reserve(value_chunk_meta_cursors_.size());
+ for (auto& cur : value_chunk_meta_cursors_) {
+ value_cms.push_back(cur.get());
+ }
+ advance_to_next_chunk();
+ chunk_reader_->reset();
+ auto* acr = static_cast<AlignedChunkReader*>(chunk_reader_);
+ if (RET_FAIL(acr->load_by_aligned_meta_multi(time_cm,
+ value_cms))) {
+ }
+ break;
+ } else if (!is_aligned_) {
+ ChunkMeta* cm = get_current_chunk_meta();
+ advance_to_next_chunk();
+ if (filter != nullptr && cm->statistic_ != nullptr &&
+ !filter->satisfy(cm->statistic_)) {
+ continue;
+ }
+ chunk_reader_->reset();
+ if (RET_FAIL(chunk_reader_->load_by_meta(cm))) {
+ }
+ break;
} else {
- if (!is_aligned_) {
- ChunkMeta* cm = get_current_chunk_meta();
- advance_to_next_chunk();
- if (filter != nullptr && cm->statistic_ != nullptr &&
- !filter->satisfy(cm->statistic_)) {
- continue;
- }
- chunk_reader_->reset();
- if (RET_FAIL(chunk_reader_->load_by_meta(cm))) {
- }
- break;
- } else {
- ChunkMeta* value_cm = value_chunk_meta_cursor_.get();
- ChunkMeta* time_cm = time_chunk_meta_cursor_.get();
- advance_to_next_chunk();
- if (filter != nullptr && value_cm->statistic_ != nullptr &&
- !filter->satisfy(value_cm->statistic_)) {
- continue;
- }
- chunk_reader_->reset();
- if (RET_FAIL(chunk_reader_->load_by_aligned_meta(
- time_cm, value_cm))) {
- }
- break;
+ ChunkMeta* value_cm = value_chunk_meta_cursor_.get();
+ ChunkMeta* time_cm = time_chunk_meta_cursor_.get();
+ advance_to_next_chunk();
+ if (filter != nullptr && value_cm->statistic_ != nullptr &&
+ !filter->satisfy(value_cm->statistic_)) {
+ continue;
+ }
+ chunk_reader_->reset();
+ if (RET_FAIL(chunk_reader_->load_by_aligned_meta(
+ time_cm, value_cm))) {
}
+ break;
}
}
}
if (IS_SUCC(ret)) {
if (alloc) {
- ret_tsblock = alloc_tsblock();
+ ret_tsblock =
+ is_multi_value_ ? alloc_tsblock_multi() : alloc_tsblock();
}
ret = chunk_reader_->get_next_page(ret_tsblock, filter, *data_pa_);
}
@@ -95,6 +108,12 @@ void TsFileSeriesScanIterator::revert_tsblock() {
int TsFileSeriesScanIterator::init_chunk_reader() {
int ret = E_OK;
is_aligned_ = itimeseries_index_->get_data_type() == common::VECTOR;
+
+ // Check if this is a multi-value aligned index
+ if (is_aligned_ && itimeseries_index_->get_value_column_count() > 1) {
+ return init_chunk_reader_multi();
+ }
+
if (!is_aligned_) {
void* buf =
common::mem_alloc(sizeof(ChunkReader), common::MOD_CHUNK_READER);
@@ -134,6 +153,54 @@ int TsFileSeriesScanIterator::init_chunk_reader() {
return ret;
}
+int TsFileSeriesScanIterator::init_chunk_reader_multi() {
+ int ret = E_OK;
+ is_multi_value_ = true;
+
+ void* buf = common::mem_alloc(sizeof(AlignedChunkReader),
+ common::MOD_CHUNK_READER);
+ auto* acr = new (buf) AlignedChunkReader;
+ chunk_reader_ = acr;
+
+ // Init time cursor
+ time_chunk_meta_cursor_ =
+ itimeseries_index_->get_time_chunk_meta_list()->begin();
+
+ // Init all value cursors
+ uint32_t num_cols = itimeseries_index_->get_value_column_count();
+ value_chunk_meta_cursors_.resize(num_cols);
+ for (uint32_t c = 0; c < num_cols; c++) {
+ value_chunk_meta_cursors_[c] =
+ itimeseries_index_->get_value_chunk_meta_list(c)->begin();
+ }
+
+ // Init chunk reader
+ if (RET_FAIL(acr->init(read_file_,
+ itimeseries_index_->get_measurement_name(),
+ itimeseries_index_->get_data_type(),
+ time_filter_))) {
+ return ret;
+ }
+
+ // Load first chunk set
+ ChunkMeta* time_cm = time_chunk_meta_cursor_.get();
+ std::vector<ChunkMeta*> value_cms;
+ value_cms.reserve(num_cols);
+ for (uint32_t c = 0; c < num_cols; c++) {
+ value_cms.push_back(value_chunk_meta_cursors_[c].get());
+ }
+
+ if (RET_FAIL(acr->load_by_aligned_meta_multi(time_cm, value_cms))) {
+ return ret;
+ }
+
+ // Advance cursors
+ time_chunk_meta_cursor_++;
+ for (auto& cur : value_chunk_meta_cursors_) cur++;
+
+ return ret;
+}
+
TsBlock* TsFileSeriesScanIterator::alloc_tsblock() {
ChunkHeader& ch = chunk_reader_->get_chunk_header();
@@ -154,4 +221,29 @@ TsBlock* TsFileSeriesScanIterator::alloc_tsblock() {
return tsblock_;
}
-} // end namespace storage
\ No newline at end of file
+TsBlock* TsFileSeriesScanIterator::alloc_tsblock_multi() {
+ auto* acr = static_cast<AlignedChunkReader*>(chunk_reader_);
+
+ // Time column
+ ColumnSchema time_cd("time", common::INT64, common::SNAPPY,
+ common::TS_2DIFF);
+ tuple_desc_.push_back(time_cd);
+
+ // Value columns
+ uint32_t num_cols = acr->get_value_column_count();
+ for (uint32_t c = 0; c < num_cols; c++) {
+ ChunkHeader& ch = acr->get_value_chunk_header(c);
+ ColumnSchema value_cd(ch.measurement_name_, ch.data_type_,
+ ch.compression_type_, ch.encoding_type_);
+ tuple_desc_.push_back(value_cd);
+ }
+
+ tsblock_ = new TsBlock(&tuple_desc_);
+ if (E_OK != tsblock_->init()) {
+ delete tsblock_;
+ tsblock_ = nullptr;
+ }
+ return tsblock_;
+}
+
+} // end namespace storage
diff --git a/cpp/src/reader/tsfile_series_scan_iterator.h b/cpp/src/reader/tsfile_series_scan_iterator.h
index ad6fe8d94..5f409d04b 100644
--- a/cpp/src/reader/tsfile_series_scan_iterator.h
+++ b/cpp/src/reader/tsfile_series_scan_iterator.h
@@ -48,7 +48,8 @@ class TsFileSeriesScanIterator {
tuple_desc_(),
tsblock_(nullptr),
time_filter_(nullptr),
- is_aligned_(false) {}
+ is_aligned_(false),
+ is_multi_value_(false) {}
~TsFileSeriesScanIterator() { destroy(); }
int init(std::shared_ptr<IDeviceID> device_id,
const std::string& measurement_name, ReadFile* read_file,
@@ -69,11 +70,29 @@ class TsFileSeriesScanIterator {
Filter* oneshoot_filter = nullptr);
void revert_tsblock();
+ // Multi-value: number of value columns in the TsBlock
+ uint32_t get_value_column_count() const {
+ if (is_multi_value_ && chunk_reader_) {
+ auto* acr = static_cast<AlignedChunkReader*>(chunk_reader_);
+ return acr->get_value_column_count();
+ }
+ return 1;
+ }
+
+ bool is_multi_value() const { return is_multi_value_; }
+
friend class TsFileIOReader;
private:
int init_chunk_reader();
+ int init_chunk_reader_multi();
FORCE_INLINE bool has_next_chunk() const {
+ if (is_multi_value_) {
+ // All value cursors advance in lockstep; check first one
+ return !value_chunk_meta_cursors_.empty() &&
+ value_chunk_meta_cursors_[0] !=
+ itimeseries_index_->get_value_chunk_meta_list(0)->end();
+ }
if (is_aligned_) {
return value_chunk_meta_cursor_ !=
itimeseries_index_->get_value_chunk_meta_list()->end();
@@ -83,7 +102,10 @@ class TsFileSeriesScanIterator {
}
}
FORCE_INLINE void advance_to_next_chunk() {
- if (is_aligned_) {
+ if (is_multi_value_) {
+ time_chunk_meta_cursor_++;
+ for (auto& cur : value_chunk_meta_cursors_) cur++;
+ } else if (is_aligned_) {
time_chunk_meta_cursor_++;
value_chunk_meta_cursor_++;
} else {
@@ -94,6 +116,7 @@ class TsFileSeriesScanIterator {
return chunk_meta_cursor_.get();
}
common::TsBlock* alloc_tsblock();
+ common::TsBlock* alloc_tsblock_multi();
private:
ReadFile* read_file_;
@@ -106,12 +129,16 @@ class TsFileSeriesScanIterator {
common::SimpleList<ChunkMeta*>::Iterator chunk_meta_cursor_;
common::SimpleList<ChunkMeta*>::Iterator time_chunk_meta_cursor_;
common::SimpleList<ChunkMeta*>::Iterator value_chunk_meta_cursor_;
+ // Multi-value: one cursor per value column
+ std::vector<common::SimpleList<ChunkMeta*>::Iterator>
+ value_chunk_meta_cursors_;
IChunkReader* chunk_reader_;
common::TupleDesc tuple_desc_;
common::TsBlock* tsblock_;
Filter* time_filter_;
bool is_aligned_ = false;
+ bool is_multi_value_ = false;
};
} // end namespace storage
diff --git a/cpp/src/simd_ts_2diff.h b/cpp/src/simd_ts_2diff.h
new file mode 100644
index 000000000..740e3393a
--- /dev/null
+++ b/cpp/src/simd_ts_2diff.h
@@ -0,0 +1,408 @@
+//
+// Created by 李烁麟 on 25-8-4.
+//
+
+#ifndef SIMD_TS_2DIFF_H
+#define SIMD_TS_2DIFF_H
+
+#include <cstdint>
+#include <cstring>
+#include <iostream>
+
+#include "simde/x86/avx2.h"
+#include "utils.h"
+
+static int N = 128;
+
+enum class predict_type {
+ EQ,
+ GT,
+ GE,
+ LE,
+ LT,
+ BT,
+};
+
+enum class block_action { All, None, NeedScan };
+
+struct predictor {
+ predict_type type_;
+ int32_t value_;
+ int32_t rvalue_;
+
+ bool satisfy(int32_t value) {
+ switch (type_) {
+ case predict_type::EQ:
+ return value_ == value;
+ case predict_type::GT:
+ return value > value_;
+ case predict_type::GE:
+ return value >= value_;
+ case predict_type::LT:
+ return value < value_;
+ case predict_type::LE:
+ return value <= value_;
+ case predict_type::BT:
+ return value >= value_ && value <= rvalue_;
+ default:
+ return false;
+ }
+ }
+
+ block_action block_check(int32_t lvalue, int32_t rvalue) {
+ switch (type_) {
+ case predict_type::LT: {
+ if (lvalue >= value_) return block_action::None;
+ if (rvalue < value_) return block_action::All;
+ return block_action::NeedScan;
+ }
+ case predict_type::LE: {
+ if (lvalue > value_) return block_action::None;
+ if (rvalue <= value_) return block_action::All;
+ return block_action::NeedScan;
+ }
+ case predict_type::GT: {
+ if (rvalue <= value_) return block_action::None;
+ if (lvalue > value_) return block_action::All;
+ return block_action::NeedScan;
+ }
+ case predict_type::GE: {
+ if (rvalue < value_) return block_action::None;
+ if (lvalue >= value_) return block_action::All;
+ return block_action::NeedScan;
+ }
+ case predict_type::EQ: {
+ if (rvalue < value_ || lvalue > value_)
+ return block_action::None;
+ if (lvalue <= value_ && rvalue >= value_)
+ return block_action::NeedScan;
+ return block_action::NeedScan;
+ }
+ }
+ return block_action::NeedScan;
+ }
+};
+
+class simd_ts_2diff_decoder {
+ public:
+ simd_ts_2diff_decoder(uint8_t *data_array, int32_t data_size,
+ predictor *predict = nullptr)
+ : data_array_(data_array), data_size_(data_size), predict_(predict) {
+ bit_width_ = 0;
+ write_index_ = 0;
+ delta_min_ = 0;
+ previous_value_ = 0;
+ position_ = 0;
+ bits_left_ = 0;
+ };
+
+ bool decode(int32_t *out, int32_t *offset_map, int &size) {
+ if (position_ < data_size_) {
+ position_ += handle_one_block(data_array_ + position_, out,
+ offset_map, size);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ static inline void drain_batch_compact(const int32_t out_u32[8],
+ uint8_t mask, uint32_t position,
+ std::vector<int32_t> &values,
+ std::vector<int32_t> &offsets) {
+ if (!mask) return;
+ const int k = __builtin_popcount(mask);
+ values.reserve(values.size() + k);
+ offsets.reserve(offsets.size() + k);
+ int32_t tmp_vals[8];
+ int32_t tmp_offs[8];
+ int n = 0;
+ uint32_t m = mask;
+ while (m) {
+ int j = __builtin_ctz(m);
+ m &= (m - 1);
+ tmp_vals[n] = out_u32[j];
+ tmp_offs[n] = position + (uint32_t)j + 1;
+ ++n;
+ }
+ values.insert(values.end(), tmp_vals, tmp_vals + n);
+ offsets.insert(offsets.end(), tmp_offs, tmp_offs + n);
+ }
+
+ size_t handle_one_block(uint8_t *data_array, int32_t *value,
+ int32_t *offset_map, int &size) {
+ uint8_t *in = data_array;
+ write_index_ = read_ui32(in);
+ bit_width_ = read_ui32(in);
+ delta_min_ = read_ui32(in);
+ previous_value_ = read_ui32(in);
+ int32_t block_min_lower =
+ previous_value_ + (delta_min_ >= 0 ? 0 : N) * delta_min_;
+ int32_t delta_upper_bound = (1 << bit_width_) - 1;
+ int32_t max_delta = delta_min_ + delta_upper_bound;
+ int32_t block_max_upper = previous_value_ +
+ delta_min_ * (max_delta > 0 ? 1 : 0) +
+ (max_delta > 0 ? N : 0) * delta_upper_bound;
+ block_action action =
+ predict_ == nullptr
+ ? block_action::All
+ : predict_->block_check(block_min_lower, block_max_upper);
+ if (action == block_action::None) {
+ return sizeof(write_index_) + sizeof(bit_width_) +
+ sizeof(delta_min_) + sizeof(previous_value_) +
+ (bit_width_ * write_index_ + 7) / 8;
+ }
+
+ if (predict_ != nullptr) {
+ if (predict_->satisfy(previous_value_)) {
+ value[size++] = previous_value_;
+ offset_map[0] = 0;
+ }
+ } else {
+ value[size++] = previous_value_;
+ offset_map[0] = 0;
+ }
+
+ for (int i = 0; i < write_index_;) {
+ if (!can_simd_decode8(i, in, data_array + data_size_)) {
+ const int remain_data = write_index_ - i;
+ previous_value_ =
+ handle_normal_data(in, previous_value_, remain_data, i,
+ value, offset_map, size);
+ i += remain_data;
+ } else {
+ int32_t out_u32[8];
+ uint8_t mask;
+ previous_value_ =
+ handle_batch_data(in, bit_width_, delta_min_, i,
+ previous_value_, out_u32, mask);
+ if (mask != 0) {
+ if(mask == 0xFF) {
+ memcpy(value +size, out_u32, 8 * sizeof(int32_t));
+ for (int k = 0; k < 8; ++k) offset_map[size + k] = i + k + 1;
+ size += 8;
+ } else {
+ uint8_t m = mask;
+ while(m) {
+ int tz = __builtin_ctz((unsigned )m);
+ value[size] = out_u32[tz];
+ offset_map[size] = tz + i + 1;
+ ++ size;
+ m &= m -1;
+ }
+ }
+ }
+ i += 8;
+ }
+ }
+ return sizeof(write_index_) + sizeof(bit_width_) + sizeof(delta_min_) +
+ sizeof(previous_value_) + (bit_width_ * write_index_ + 7) / 8;
+ }
+
+ int32_t handle_normal_data(uint8_t *&in, int32_t base, int cap,
+ int position, int32_t *out, int32_t *offset,
+ int &size) {
+ int32_t basement = base;
+ for (int i = 0; i < cap; ++i) {
+ int32_t value = read_long(bit_width_, in);
+ value += (basement + delta_min_);
+ basement = value;
+ if (predict_ != nullptr) {
+ if (predict_->satisfy(value)) {
+ out[size] = value;
+ offset[size++] = position + i + 1;
+ }
+ } else {
+ out[size] = value;
+ offset[size++] = position + i + 1;
+ }
+ }
+ return basement;
+ }
+
+ int32_t read_long(int bits, uint8_t *&in) {
+ int32_t value = 0;
+ while (bits > 0) {
+ read_byte_if_empty(in);
+ if (bits > bits_left_ || bits == 8) {
+ auto d = (uint8_t)(buffer_ & ((1 << bits_left_) - 1));
+ value = (value << bits_left_) + (d & 0xFF);
+ bits -= bits_left_;
+ bits_left_ = 0;
+ } else {
+ auto d = (uint8_t)((((uint8_t)buffer_) >> (bits_left_ - bits)) &
+ ((1 << bits) - 1));
+ value = (value << bits) + (d & 0xFF);
+ bits_left_ -= bits;
+ bits = 0;
+ }
+ if (bits <= 0) {
+ break;
+ }
+ }
+ return value;
+ }
+
+ void read_byte_if_empty(uint8_t *&in) {
+ if (bits_left_ == 0) {
+ memcpy(&buffer_, in, 1);
+ bits_left_ = 8;
+ in += 1;
+ }
+ }
+
+ bool can_simd_decode8(int pos, const uint8_t* cur, const uint8_t * end) {
+ if (write_index_ - pos < 8) {
+ return false;
+ }
+ const int32_t overhead_bits = (bit_width_ >= 16) ? 64 : 32;
+ const int32_t rest_data = 8;
+ const int32_t need_bits = (rest_data - 1) * bit_width_ + overhead_bits;
+ const size_t remain_bits = size_t(end - cur) * 8;
+ return need_bits <= remain_bits;
+ }
+
+ int32_t handle_batch_data(const uint8_t *in, int32_t w, int32_t min_delta,
+ int32_t ind, int32_t base, int32_t out_u32[8],
+ uint8_t &mask) {
+ // This function decodes 8 values from the input data array,
+ // producing the final results that satisfy the prediction.
+
+ // Set byte-reversal method to convert little-endian data to big-endian
+ // format.
+ static const simde__m256i SHUF_REV8 = simde_mm256_setr_epi8(
+ 7, 6, 5, 4, 3, 2, 1, 0, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3,
+ 2, 1, 0, 15, 14, 13, 12, 11, 10, 9, 8);
+
+ static const simde__m128i SHUF_REV4 = simde_mm_setr_epi8(
+ 3, 2, 1, 0, 7, 6, 5, 4, 11, 10, 9, 8, 15, 14, 13, 12);
+
+ const simde__m128i VMIN4 = simde_mm_set1_epi32(min_delta);
+
+ int32_t basement = base;
+ mask = 0;
+
+ // Decode using two loops.
+ for (int32_t grp = 0; grp < 2; ++grp) {
+ int32_t pos0 = grp * 4u * w + ind * w;
+ int32_t pos[4] = {pos0 + 0 * w, pos0 + 1 * w, pos0 + 2 * w,
+ pos0 + 3 * w};
+ // The byte items start
+ int32_t bidx_s[4] = {pos[0] >> 3, pos[1] >> 3, pos[2] >> 3,
+ pos[3] >> 3};
+ // The offset items start
+ int32_t off_s[4] = {pos[0] & 7, pos[1] & 7, pos[2] & 7, pos[3] & 7};
+
+ simde__m128i IDX128 =
+ simde_mm_setr_epi32(bidx_s[0], bidx_s[1], bidx_s[2], bidx_s[3]);
+ simde__m128i OFF128 =
+ simde_mm_setr_epi32(off_s[0], off_s[1], off_s[2], off_s[3]);
+
+ simde__m128i V4;
+
+ if (w <= 16) {
+ const int rshift = 32 - w;
+ simde__m128i w32_le =
+ simde_mm_i32gather_epi32((const int *)in, IDX128, 1);
+ simde__m128i w32_be = simde_mm_shuffle_epi8(w32_le, SHUF_REV4);
+ simde__m128i U32 = simde_mm_sllv_epi32(w32_be, OFF128);
+ simde__m128i RS32 = simde_mm_set1_epi32(rshift);
+ simde__m128i V32 = simde_mm_srlv_epi32(U32, RS32);
+ V4 = V32;
+ } else {
+ const int rshift = 64 - w;
+ simde__m256i w64_le = simde_mm256_i32gather_epi64(
+ (const long long *)in, IDX128, 1);
+ simde__m256i w64_be =
+ simde_mm256_shuffle_epi8(w64_le, SHUF_REV8);
+ simde__m256i OFF64 = simde_mm256_cvtepu32_epi64(OFF128);
+ simde__m256i U64 = simde_mm256_sllv_epi64(w64_be, OFF64);
+ simde__m256i V64 =
+ simde_mm256_srl_epi64(U64, simde_mm_cvtsi32_si128(rshift));
+ simde__m256i V32_8 = V64;
+ simde__m256i perm =
+ simde_mm256_setr_epi32(0, 2, 4, 6, 0, 0, 0, 0);
+ simde__m256i comp =
+ simde_mm256_permutevar8x32_epi32(V32_8, perm);
+ V4 = simde_mm256_castsi256_si128(comp);
+ }
+ V4 = simde_mm_add_epi32(V4, VMIN4);
+ simde__m128i t;
+ t = simde_mm_slli_si128(V4, 4);
+ V4 = simde_mm_add_epi32(V4, t);
+ t = simde_mm_slli_si128(V4, 8);
+ V4 = simde_mm_add_epi32(V4, t);
+ simde__m128i C4 = simde_mm_set1_epi32((int)basement);
+ V4 = simde_mm_add_epi32(V4, C4);
+ if (predict_ != nullptr) {
+ simde__m128i m4;
+ simde__m128i a = simde_mm_set1_epi32(predict_->value_);
+ switch (predict_->type_) {
+ case predict_type::EQ: {
+ m4 = simde_mm_cmpeq_epi32(V4, a);
+ break;
+ }
+ case predict_type::GT: {
+ m4 = simde_mm_cmpgt_epi32(V4, a);
+ break;
+ }
+ case predict_type::GE: {
+ m4 = simde_mm_xor_si128(simde_mm_cmpgt_epi32(a, V4),
+ simde_mm_set1_epi32(-1));
+ break;
+ }
+ case predict_type::LT: {
+ m4 = simde_mm_cmplt_epi32(V4, a);
+ break;
+ }
+ case predict_type::LE: {
+ m4 = simde_mm_xor_si128(simde_mm_cmpgt_epi32(V4, a),
+ simde_mm_set1_epi32(-1));
+ break;
+ }
+ case predict_type::BT: {
+ int32_t lo = predict_->value_;
+ int32_t hi = predict_->rvalue_;
+ simde__m128i A = simde_mm_set1_epi32(lo);
+ simde__m128i B = simde_mm_set1_epi32(hi);
+ simde__m128i left =
+ simde_mm_xor_si128(simde_mm_cmpgt_epi32(A, V4),
+ simde_mm_set1_epi32(-1));
+ simde__m128i right =
+ simde_mm_xor_si128(simde_mm_cmpgt_epi32(V4, B),
+ simde_mm_set1_epi32(-1));
+ m4 = simde_mm_and_si128(left, right);
+ break;
+ }
+ default:
+ break;
+ }
+ uint8_t four_bits = 0;
+ four_bits =
+ simde_mm_movemask_ps(simde_mm_castsi128_ps(m4)) & 0xf;
+ mask |= (uint8_t)(four_bits << (grp * 4));
+ } else {
+ mask = 0xff;
+ }
+
+ simde_mm_storeu_si128((simde__m128i *)(out_u32 + grp * 4), V4);
+ basement = out_u32[grp * 4 + 3];
+ }
+ return basement;
+ }
+
+ private:
+ uint8_t *data_array_;
+ int bit_width_;
+ int write_index_;
+ int32_t data_size_;
+ int32_t position_;
+ int32_t delta_min_;
+ int32_t previous_value_;
+ predictor *predict_;
+
+ uint8_t buffer_;
+ int bits_left_;
+};
+
+#endif // SIMD_TS_2DIFF_H
diff --git a/cpp/src/ts_2diff.h b/cpp/src/ts_2diff.h
new file mode 100644
index 000000000..e2315764f
--- /dev/null
+++ b/cpp/src/ts_2diff.h
@@ -0,0 +1,267 @@
+//
+// Created by 李烁麟 on 25-8-3.
+//
+
+#ifndef TS_2DIFF_H
+#define TS_2DIFF_H
+
+#include <cstdint>
+#include <cstring>
+#include <iostream>
+
+#include "utils.h"
+
+template <typename>
+struct always_false : std::false_type {};
+
+template <typename T,
+ typename =
+ typename std::enable_if<std::is_same<T, int32_t>::value ||
+ std::is_same<T, int64_t>::value>::type>
+class ts_2diff_encoder {
+ public:
+ ts_2diff_encoder() {
+ block_size_ = 128;
+ delta_array_ = new T[block_size_];
+ write_index_ = -1;
+ bits_left_ = 8;
+ buffer_ = 0;
+ delta_array_min_ = 0;
+ delta_array_max_ = 0;
+ first_value_ = 0;
+ previous_value_ = 0;
+ cap_ = 1024;
+ cur_position_ = 0;
+ data_array_ = (uint8_t *)malloc(cap_);
+ }
+
+ ~ts_2diff_encoder() { free(data_array_); }
+
+ void do_encode(T value) {
+ // Record first value.
+ if (write_index_ == -1) {
+ first_value_ = value;
+ previous_value_ = first_value_;
+ write_index_++;
+ return;
+ }
+
+ T delta = value - previous_value_;
+ previous_value_ = value;
+
+ if (write_index_ == 0) {
+ delta_array_min_ = delta;
+ delta_array_max_ = delta;
+ }
+
+ delta_array_min_ = std::min(delta, delta_array_min_);
+ delta_array_max_ = std::max(delta, delta_array_max_);
+ delta_array_[write_index_++] = delta;
+
+ if (write_index_ >= block_size_) {
+ flush_internal();
+ }
+ }
+
+ int32_t flush(uint8_t *&data) {
+ if (write_index_ != -1) {
+ flush_internal();
+ }
+ data = data_array_;
+ return cur_position_;
+ }
+
+ // Flush data into local data array.
+ void flush_internal() {
+ uint8_t *data = nullptr;
+ int data_size = write_to_binary(data);
+ if (data_size == 0) {
+ return;
+ }
+ if (cap_ - cur_position_ < data_size) {
+ cap_ *= 2;
+ data_array_ = (uint8_t *)realloc(data_array_, cap_);
+ }
+ memcpy(data_array_ + cur_position_, data, data_size);
+ free(data);
+ cur_position_ += data_size;
+ }
+
+ int write_to_binary(uint8_t *&data) {
+ uint8_t *value = nullptr;
+ if (write_index_ == -1) {
+ return 0;
+ }
+
+ for (int i = 0; i < write_index_; ++i) {
+ delta_array_[i] -= delta_array_min_;
+ }
+
+ int bit_width = 0;
+ T delta_max = delta_array_max_ - delta_array_min_;
+ while (delta_max > 0) {
+ bit_width++;
+ delta_max >>= 1;
+ }
+
+ int size = 4 * 4 + (bit_width * write_index_ + 7)/8;
+ value = (uint8_t *)malloc(size);
+ data = value;
+
+ write_uint32(write_index_, value);
+ write_uint32(bit_width, value);
+ if constexpr (std::is_same<T, int32_t>::value) {
+ write_uint32(static_cast<uint32_t>(delta_array_min_), value);
+ write_uint32(static_cast<uint32_t>(first_value_), value);
+ } else if constexpr (std::is_same<T, int64_t>::value) {
+ write_uint64(static_cast<uint64_t>(delta_array_min_), value);
+ write_uint64(static_cast<uint64_t>(first_value_), value);
+ } else {
+ static_assert(always_false<T>::value, "Unsupport type T");
+ }
+
+ for (int i = 0; i < write_index_; i++) {
+ write_bits(delta_array_[i], bit_width, value);
+ }
+
+ if (bits_left_ != 0 && bits_left_ != 8) {
+ bits_left_ = 0;
+ flush_byte_if_full(value);
+ }
+ write_index_ = -1;
+ return size;
+ }
+
+ void write_bits(int64_t value, int bits, uint8_t *&out_stream) {
+ while (bits > 0) {
+ int shift = bits - bits_left_;
+ if (shift >= 0) {
+ buffer_ |=
+ (uint8_t)((value >> shift) & ((1 << bits_left_) - 1));
+ bits -= bits_left_;
+ bits_left_ = 0;
+ } else {
+ shift = bits_left_ - bits;
+ buffer_ |= (uint8_t)(value << shift);
+ bits_left_ -= bits;
+ bits = 0;
+ }
+ flush_byte_if_full(out_stream);
+ }
+ }
+
+ void flush_byte_if_full(uint8_t *&out_stream) {
+ if (bits_left_ == 0) {
+ memcpy(out_stream, &buffer_, 1);
+ out_stream++;
+ buffer_ = 0;
+ bits_left_ = 8;
+ }
+ }
+
+ private:
+ int block_size_;
+ T *delta_array_;
+ T first_value_;
+ T previous_value_;
+ T delta_array_min_;
+ T delta_array_max_;
+ uint8_t buffer_;
+ uint8_t *data_array_ = nullptr;
+ int32_t cap_;
+ int32_t cur_position_;
+ int bits_left_;
+ int write_index_;
+};
+
+template <typename T,
+ typename =
+ typename std::enable_if<std::is_same<T, int32_t>::value ||
+ std::is_same<T, int64_t>::value>::type>
+class ts_2diff_decoder {
+ public:
+ explicit ts_2diff_decoder(uint8_t *data_array) {
+ write_index_ = -1;
+ bits_left_ = 0;
+ stored_value_ = 0;
+ buffer_ = 0;
+ delta_min_ = 0;
+ previous_value_ = 0;
+ bit_width_ = 0;
+ current_index_ = 0;
+ data_array_ = data_array;
+ }
+
+ T decode() {
+ T ret_value = 0;
+ if (current_index_ == 0) {
+ write_index_ = read_ui32(data_array_);
+ bit_width_ = read_ui32(data_array_);
+ if constexpr (std::is_same<T, int32_t>::value) {
+ delta_min_ = read_ui32(data_array_);
+ previous_value_ = read_ui32(data_array_);
+ } else if constexpr (std::is_same<T, int64_t>::value) {
+ delta_min_ = read_ui64(data_array_);
+ previous_value_ = read_ui64(data_array_);
+ } else {
+ static_assert(always_false<T>::value, "Unsupport type T");
+ }
+ ret_value = previous_value_;
+ if (write_index_ == 0) {
+ current_index_ = 0;
+ } else {
+ current_index_ = 1;
+ }
+ return ret_value;
+ }
+ if (current_index_++ >= write_index_) {
+ current_index_ = 0;
+ }
+ stored_value_ = read_long(bit_width_, data_array_);
+ ret_value = stored_value_ + previous_value_ + delta_min_;
+ previous_value_ = ret_value;
+ return ret_value;
+ }
+ void read_byte_if_empty(uint8_t *&in) {
+ if (bits_left_ == 0) {
+ memcpy(&buffer_, in, 1);
+ bits_left_ = 8;
+ in += 1;
+ }
+ }
+ int64_t read_long(int bits, uint8_t *&in) {
+ int64_t value = 0;
+ while (bits > 0) {
+ read_byte_if_empty(in);
+ if (bits > bits_left_ || bits == 8) {
+ auto d = (uint8_t)(buffer_ & ((1 << bits_left_) - 1));
+ value = (value << bits_left_) + (d & 0xFF);
+ bits -= bits_left_;
+ bits_left_ = 0;
+ } else {
+ auto d = (uint8_t)((((uint8_t)buffer_) >> (bits_left_ - bits)) &
+ ((1 << bits) - 1));
+ value = (value << bits) + (d & 0xFF);
+ bits_left_ -= bits;
+ bits = 0;
+ }
+ if (bits <= 0 && current_index_ == 0) {
+ break;
+ }
+ }
+ return value;
+ }
+
+ private:
+ T stored_value_;
+ T delta_min_;
+ T previous_value_;
+ uint8_t buffer_;
+ int bits_left_;
+ int bit_width_;
+ int write_index_;
+ int current_index_;
+ uint8_t *data_array_;
+};
+
+#endif // TS_2DIFF_H
diff --git a/cpp/src/utils.h b/cpp/src/utils.h
new file mode 100644
index 000000000..56b95b659
--- /dev/null
+++ b/cpp/src/utils.h
@@ -0,0 +1,55 @@
+//
+// Created by 李烁麟 on 25-8-4.
+//
+
+#ifndef UTILS_H
+#define UTILS_H
+#include <iostream>
+
+static uint32_t read_ui32(uint8_t *&data) {
+ uint8_t buf[4];
+ memcpy(buf, data, 4);
+ data += 4;
+ uint32_t ui32 = buf[0];
+ ui32 = (ui32 << 8) | (buf[1] & 0xFF);
+ ui32 = (ui32 << 8) | (buf[2] & 0xFF);
+ ui32 = (ui32 << 8) | (buf[3] & 0xFF);
+ return ui32;
+}
+
+static uint64_t read_ui64(uint8_t *&data) {
+ uint8_t buf[8];
+ memcpy(buf, data, 8);
+ data += 8;
+ uint64_t ui64 = buf[0];
+ for (int i = 1; i < 8; ++i) {
+ ui64 = (ui64 << 8) | (buf[i] & 0xFF);
+ }
+ return ui64;
+}
+
+static void write_uint32(const uint32_t ui32, uint8_t *&value) {
+ uint8_t buf[4];
+ buf[0] = static_cast<uint8_t>((ui32 >> 24) & 0xFF);
+ buf[1] = static_cast<uint8_t>((ui32 >> 16) & 0xFF);
+ buf[2] = static_cast<uint8_t>((ui32 >> 8) & 0xFF);
+ buf[3] = static_cast<uint8_t>((ui32)&0xFF);
+ memcpy(value, buf, 4);
+ value += 4;
+}
+
+static void write_uint64(const uint64_t ui64, uint8_t *&value) {
+ uint8_t buf[8];
+ buf[0] = static_cast<uint8_t>((ui64 >> 56) & 0xFF);
+ buf[1] = static_cast<uint8_t>((ui64 >> 48) & 0xFF);
+ buf[2] = static_cast<uint8_t>((ui64 >> 40) & 0xFF);
+ buf[3] = static_cast<uint8_t>((ui64 >> 32) & 0xFF);
+ buf[4] = static_cast<uint8_t>((ui64 >> 24) & 0xFF);
+ buf[5] = static_cast<uint8_t>((ui64 >> 16) & 0xFF);
+ buf[6] = static_cast<uint8_t>((ui64 >> 8) & 0xFF);
+ buf[7] = static_cast<uint8_t>((ui64)&0xFF);
+ memcpy(value, buf, 8);
+ value += 8;
+}
+
+#endif // UTILS_H
diff --git a/cpp/test/encoding/gorilla_codec_test.cc b/cpp/test/encoding/gorilla_codec_test.cc
index 47056a6db..f28e9cefe 100644
--- a/cpp/test/encoding/gorilla_codec_test.cc
+++ b/cpp/test/encoding/gorilla_codec_test.cc
@@ -207,4 +207,192 @@ TEST_F(GorillaCodecTest, DoubleEncodingDecodingBoundaryValues) {
}
}
+// ── Batch decode tests (exercises the raw-pointer GorillaBitReader path) ──
+
+TEST_F(GorillaCodecTest, Int32BatchDecode) {
+ storage::IntGorillaEncoder encoder;
+ common::ByteStream stream(1024, common::MOD_DEFAULT);
+ const int N = 500;
+ int32_t expected[N];
+ for (int i = 0; i < N; i++) {
+ expected[i] = i * 7 - 100;
+ EXPECT_EQ(encoder.encode(expected[i], stream), common::E_OK);
+ }
+ encoder.flush(stream);
+
+ // Copy to a contiguous buffer and wrap (simulates production path)
+ uint32_t total = stream.total_size();
+ std::vector<uint8_t> buf(total);
+ uint32_t got = 0;
+ stream.read_buf(buf.data(), total, got);
+ ASSERT_EQ(got, total);
+
+ common::ByteStream wrapped(common::MOD_DEFAULT);
+ wrapped.wrap_from((const char*)buf.data(), total);
+
+ storage::IntGorillaDecoder decoder;
+ int32_t out[N];
+ int decoded = 0;
+ int total_decoded = 0;
+ while (decoder.has_remaining(wrapped) && total_decoded < N) {
+ int batch = std::min(129, N - total_decoded);
+ int actual = 0;
+ EXPECT_EQ(decoder.read_batch_int32(out + total_decoded, batch, actual,
+ wrapped),
+ common::E_OK);
+ if (actual == 0) break;
+ total_decoded += actual;
+ }
+ ASSERT_EQ(total_decoded, N);
+ for (int i = 0; i < N; i++) {
+ EXPECT_EQ(out[i], expected[i]) << "mismatch at index " << i;
+ }
+}
+
+TEST_F(GorillaCodecTest, Int64BatchDecode) {
+ storage::LongGorillaEncoder encoder;
+ common::ByteStream stream(1024, common::MOD_DEFAULT);
+ const int N = 500;
+ int64_t expected[N];
+ for (int i = 0; i < N; i++) {
+ expected[i] = (int64_t)i * 13 - 200;
+ EXPECT_EQ(encoder.encode(expected[i], stream), common::E_OK);
+ }
+ encoder.flush(stream);
+
+ uint32_t total = stream.total_size();
+ std::vector<uint8_t> buf(total);
+ uint32_t got = 0;
+ stream.read_buf(buf.data(), total, got);
+
+ common::ByteStream wrapped(common::MOD_DEFAULT);
+ wrapped.wrap_from((const char*)buf.data(), total);
+
+ storage::LongGorillaDecoder decoder;
+ int64_t out[N];
+ int total_decoded = 0;
+ while (decoder.has_remaining(wrapped) && total_decoded < N) {
+ int batch = std::min(129, N - total_decoded);
+ int actual = 0;
+ EXPECT_EQ(decoder.read_batch_int64(out + total_decoded, batch, actual,
+ wrapped),
+ common::E_OK);
+ if (actual == 0) break;
+ total_decoded += actual;
+ }
+ ASSERT_EQ(total_decoded, N);
+ for (int i = 0; i < N; i++) {
+ EXPECT_EQ(out[i], expected[i]) << "mismatch at index " << i;
+ }
+}
+
+TEST_F(GorillaCodecTest, FloatBatchDecode) {
+ storage::FloatGorillaEncoder encoder;
+ common::ByteStream stream(1024, common::MOD_DEFAULT);
+ const int N = 300;
+ std::vector<float> expected(N);
+ for (int i = 0; i < N; i++) {
+ expected[i] = (float)i * 1.5f - 50.0f;
+ EXPECT_EQ(encoder.encode(expected[i], stream), common::E_OK);
+ }
+ encoder.flush(stream);
+
+ uint32_t total = stream.total_size();
+ std::vector<uint8_t> buf(total);
+ uint32_t got = 0;
+ stream.read_buf(buf.data(), total, got);
+
+ common::ByteStream wrapped(common::MOD_DEFAULT);
+ wrapped.wrap_from((const char*)buf.data(), total);
+
+ storage::FloatGorillaDecoder decoder;
+ std::vector<float> out(N);
+ int total_decoded = 0;
+ while (decoder.has_remaining(wrapped) && total_decoded < N) {
+ int batch = std::min(129, N - total_decoded);
+ int actual = 0;
+ EXPECT_EQ(decoder.read_batch_float(out.data() + total_decoded, batch,
+ actual, wrapped),
+ common::E_OK);
+ if (actual == 0) break;
+ total_decoded += actual;
+ }
+ ASSERT_EQ(total_decoded, N);
+ for (int i = 0; i < N; i++) {
+ EXPECT_FLOAT_EQ(out[i], expected[i]) << "mismatch at index " << i;
+ }
+}
+
+TEST_F(GorillaCodecTest, DoubleBatchDecode) {
+ storage::DoubleGorillaEncoder encoder;
+ common::ByteStream stream(1024, common::MOD_DEFAULT);
+ const int N = 300;
+ std::vector<double> expected(N);
+ for (int i = 0; i < N; i++) {
+ expected[i] = (double)i * 2.7 - 100.0;
+ EXPECT_EQ(encoder.encode(expected[i], stream), common::E_OK);
+ }
+ encoder.flush(stream);
+
+ uint32_t total = stream.total_size();
+ std::vector<uint8_t> buf(total);
+ uint32_t got = 0;
+ stream.read_buf(buf.data(), total, got);
+
+ common::ByteStream wrapped(common::MOD_DEFAULT);
+ wrapped.wrap_from((const char*)buf.data(), total);
+
+ storage::DoubleGorillaDecoder decoder;
+ std::vector<double> out(N);
+ int total_decoded = 0;
+ while (decoder.has_remaining(wrapped) && total_decoded < N) {
+ int batch = std::min(129, N - total_decoded);
+ int actual = 0;
+ EXPECT_EQ(decoder.read_batch_double(out.data() + total_decoded, batch,
+ actual, wrapped),
+ common::E_OK);
+ if (actual == 0) break;
+ total_decoded += actual;
+ }
+ ASSERT_EQ(total_decoded, N);
+ for (int i = 0; i < N; i++) {
+ EXPECT_DOUBLE_EQ(out[i], expected[i]) << "mismatch at index " << i;
+ }
+}
+
+TEST_F(GorillaCodecTest, Int32BatchSkip) {
+ storage::IntGorillaEncoder encoder;
+ common::ByteStream stream(1024, common::MOD_DEFAULT);
+ const int N = 200;
+ int32_t expected[N];
+ for (int i = 0; i < N; i++) {
+ expected[i] = i * 3;
+ EXPECT_EQ(encoder.encode(expected[i], stream), common::E_OK);
+ }
+ encoder.flush(stream);
+
+ uint32_t total = stream.total_size();
+ std::vector<uint8_t> buf(total);
+ uint32_t got = 0;
+ stream.read_buf(buf.data(), total, got);
+
+ common::ByteStream wrapped(common::MOD_DEFAULT);
+ wrapped.wrap_from((const char*)buf.data(), total);
+
+ storage::IntGorillaDecoder decoder;
+ // Skip first 50 values
+ int skipped = 0;
+ EXPECT_EQ(decoder.skip_int32(50, skipped, wrapped), common::E_OK);
+ EXPECT_EQ(skipped, 50);
+ // Read next 50 values
+ int32_t out[50];
+ int actual = 0;
+ EXPECT_EQ(decoder.read_batch_int32(out, 50, actual, wrapped),
... 986581 lines suppressed ...