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/09/17 09:58:29 UTC
(tsfile) 04/05: docs(cpp): add ALP profiling analysis and benchmark filters
This is an automated email from the ASF dual-hosted git repository.
ColinLeeo pushed a commit to branch colin/alp-codec
in repository https://gitbox.apache.org/repos/asf/tsfile.git
commit 957d8c7210b6775bae34c9aea70b6ee0c3b3183a
Author: ColinLee <sh...@163.com>
AuthorDate: Thu Sep 17 17:43:53 2026 +0800
docs(cpp): add ALP profiling analysis and benchmark filters
---
cpp/bench_mark/alp_vs_gorilla_benchmark.cc | 30 ++++-
cpp/docs/alp-profiling-analysis.md | 145 +++++++++++++++++++++
cpp/docs/images/alp-float-decimal-encode-pprof.png | Bin 0 -> 323073 bytes
cpp/docs/images/gorilla-float-decimal-pprof.png | Bin 0 -> 296772 bytes
4 files changed, 168 insertions(+), 7 deletions(-)
diff --git a/cpp/bench_mark/alp_vs_gorilla_benchmark.cc b/cpp/bench_mark/alp_vs_gorilla_benchmark.cc
index d471fcac0..35661704b 100644
--- a/cpp/bench_mark/alp_vs_gorilla_benchmark.cc
+++ b/cpp/bench_mark/alp_vs_gorilla_benchmark.cc
@@ -283,7 +283,9 @@ std::vector<T> MakeConstantData(uint64_t count) {
}
template <typename T>
-void RunAll(const std::string& type_name, uint32_t repetitions) {
+void RunAll(const std::string& type_name, uint32_t repetitions,
+ const std::string& codec_filter,
+ const std::string& pattern_filter) {
const uint64_t count = 1024 * 1024;
const common::TSDataType data_type =
sizeof(T) == sizeof(float) ? common::FLOAT : common::DOUBLE;
@@ -307,10 +309,17 @@ void RunAll(const std::string& type_name, uint32_t repetitions) {
<< std::setw(14) << "dec(MB/s)" << std::setw(10) << "ok"
<< std::endl;
for (size_t c = 0; c < cases.size(); ++c) {
- PrintResult(RunCase("ALP", common::ALP, cases[c].name, cases[c].values,
- repetitions));
- PrintResult(RunCase("GORILLA", common::GORILLA, cases[c].name,
- cases[c].values, repetitions));
+ if (!pattern_filter.empty() && cases[c].name != pattern_filter) {
+ continue;
+ }
+ if (codec_filter.empty() || codec_filter == "ALP") {
+ PrintResult(RunCase("ALP", common::ALP, cases[c].name,
+ cases[c].values, repetitions));
+ }
+ if (codec_filter.empty() || codec_filter == "GORILLA") {
+ PrintResult(RunCase("GORILLA", common::GORILLA, cases[c].name,
+ cases[c].values, repetitions));
+ }
}
(void)data_type;
}
@@ -322,9 +331,16 @@ int main(int argc, char** argv) {
if (argc > 1) {
repetitions = static_cast<uint32_t>(std::max(1, atoi(argv[1])));
}
+ const std::string codec_filter = argc > 2 ? argv[2] : "";
+ const std::string type_filter = argc > 3 ? argv[3] : "";
+ const std::string pattern_filter = argc > 4 ? argv[4] : "";
std::cout << "ALP vs GORILLA codec benchmark, best of " << repetitions
<< " runs" << std::endl;
- RunAll<float>("FLOAT", repetitions);
- RunAll<double>("DOUBLE", repetitions);
+ if (type_filter.empty() || type_filter == "FLOAT") {
+ RunAll<float>("FLOAT", repetitions, codec_filter, pattern_filter);
+ }
+ if (type_filter.empty() || type_filter == "DOUBLE") {
+ RunAll<double>("DOUBLE", repetitions, codec_filter, pattern_filter);
+ }
return 0;
}
diff --git a/cpp/docs/alp-profiling-analysis.md b/cpp/docs/alp-profiling-analysis.md
new file mode 100644
index 000000000..214d2217c
--- /dev/null
+++ b/cpp/docs/alp-profiling-analysis.md
@@ -0,0 +1,145 @@
+<!--
+
+ 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.
+
+-->
+
+# ALP Profiling Analysis
+
+This note explains why the ALP codec still loses to Gorilla in some cases
+even though the value kernels are SIMD-accelerated. The profiles were collected
+with gperftools on the FLOAT decimal benchmark case.
+
+## Profile Setup
+
+```bash
+CPUPROFILE=/tmp/alp_float.prof \
+CPUPROFILE_FREQUENCY=1000 \
+DYLD_INSERT_LIBRARIES=/opt/homebrew/lib/libprofiler.dylib \
+./cpp/build/alp/alp_vs_gorilla_benchmark 500 ALP FLOAT decimal
+
+/opt/homebrew/bin/pprof --svg \
+ ./cpp/build/alp/alp_vs_gorilla_benchmark \
+ /tmp/alp_float.prof > /tmp/alp_float.svg
+```
+
+The same command was repeated with `GORILLA` for comparison.
+
+## ALP FLOAT Decimal
+
+
+
+Flat profile:
+
+| Function | Flat | Cumulative |
+| --- | ---: | ---: |
+| `storage::AlpEncoderBase::flush` | 83.8% | 90.9% |
+| `storage::alp::AlpAppendU32` | 4.1% | 4.2% |
+| `storage::AlpDecoderBase::ensure_initialized` | 1.9% | 7.4% |
+| `storage::alp::AlpUnpackBits` | 1.8% | 1.8% |
+| `storage::alp::AlpDecodeValues` | 2.2% | 2.2% |
+| `storage::AlpDecoderBase::read_batch_float` | 0.6% | 8.0% |
+
+The encode path dominates. Decode is already a small fraction of total time.
+
+## Gorilla FLOAT Decimal
+
+
+
+Flat profile:
+
+| Function | Flat | Cumulative |
+| --- | ---: | ---: |
+| `storage::GorillaEncoder::compress_value` | 29.0% | 72.3% |
+| `common::ByteStream::write_buf` | 27.2% | 43.3% |
+| `storage::FloatGorillaDecoder::read_batch_float` | 22.0% | 22.0% |
+| `storage::FloatGorillaEncoder::encode` | 2.2% | 74.5% |
+| `storage::Encoder::encode_batch` | 1.9% | 76.4% |
+
+Gorilla spends most of its time in its scalar bit writer and in the ByteStream
+write path. Its encode is not SIMD either; it is simply a very efficient
+scalar bit stream for the FLOAT decimal pattern.
+
+## Per-Block Breakdown
+
+A separate microbenchmark measured one 1024-value block with the same decimal
+pattern:
+
+| Stage | FLOAT (ms/block) | DOUBLE (ms/block) |
+| --- | ---: | ---: |
+| `AlpChooseFactorExponent` | 0.004 | 0.009 |
+| `AlpEncodeValues` (SIMD) | 0.001 | 0.001 |
+| `AlpPackBits` (scalar) | 0.009 | 0.006 |
+| `AlpEncodePage` total | 0.012 | 0.017 |
+
+The SIMD value kernel is only about 6-8% of ALP encode time. The rest is the
+scalar exponent/factor search and the scalar bit-packing loop.
+
+## Why ALP Still Loses
+
+1. **Encode is not end-to-end SIMD.**
+ The SIMD path covers `value -> integer` conversion and exception detection,
+ but `AlpPackBits` is scalar and, for FLOAT, accounts for roughly 75% of the
+ encode time. That is why ALP FLOAT encode is slower than Gorilla even though
+ the value kernel is SIMD.
+
+2. **The exponent/factor search is scalar.**
+ `AlpChooseFactorExponent` evaluates candidate `(factor, exponent)` pairs on
+ sampled values. It is about 33% of FLOAT encode and about 53% of DOUBLE
+ encode. For DOUBLE, this is the largest single encode cost.
+
+3. **Non-decimal data falls back to PLAIN.**
+ Smooth/random/constant data often has too many exceptions for ALP to win.
+ The encoder then emits a PLAIN block. In that case SIMD cannot help because
+ the data is not ALP-encoded at all; Gorilla is usually the better codec for
+ those columns.
+
+4. **Gorilla has a dedicated constant path.**
+ Gorilla compresses constant blocks extremely well and decodes them very
+ quickly. ALP intentionally does not special-case that pattern, so Gorilla
+ wins the constant rows of the benchmark.
+
+5. **The benchmark is on Apple Silicon.**
+ SIMDe maps the AVX2-style kernels to NEON here. On x86, a native `-mavx2`
+ translation unit and runtime dispatch are still required to get the AVX2
+ peak; without those flags SIMDe may emulated AVX2 with SSE2.
+
+## Next Optimizations
+
+1. **SIMD bit packing.**
+ Pack four values at a time with the same 64-bit-window layout used by the
+ SIMD unpack kernel. This directly targets the largest FLOAT encode cost.
+
+2. **SIMD or cheaper exponent/factor selection.**
+ Evaluate the candidate pairs on the 32-value sample with SIMD, or reduce the
+ candidate set with an early-exit estimate. This targets the DOUBLE encode
+ cost.
+
+3. **Remove per-block allocations.**
+ Reuse scratch vectors for `encoded`, `adjusted`, `bitmap`, and `body`
+ instead of constructing them per block. Decode the final values directly
+ into the caller's output buffer rather than an intermediate `values_`
+ vector.
+
+4. **Adaptive codec selection.**
+ Choose Gorilla for constant/high-entropy columns and ALP for decimal-like
+ columns. This avoids the PLAIN fallback cases where Gorilla is better.
+
+5. **Native x86 dispatch.**
+ Add an AVX2 object library and CPU detection so x86 users get native AVX2
+ without `-march=native`, plus an optional AVX-512 path for DOUBLE.
diff --git a/cpp/docs/images/alp-float-decimal-encode-pprof.png b/cpp/docs/images/alp-float-decimal-encode-pprof.png
new file mode 100644
index 000000000..24f1d70af
Binary files /dev/null and b/cpp/docs/images/alp-float-decimal-encode-pprof.png differ
diff --git a/cpp/docs/images/gorilla-float-decimal-pprof.png b/cpp/docs/images/gorilla-float-decimal-pprof.png
new file mode 100644
index 000000000..19a062030
Binary files /dev/null and b/cpp/docs/images/gorilla-float-decimal-pprof.png differ