Skip to content

perf(tdigest): reuse cumulative weights in CDF and PMF queries - #289

Merged
tisonkun merged 10 commits into
apache:mainfrom
tisonkun:tison/tdigest-query-followups
Oct 5, 2026
Merged

tisonkun merged 10 commits into
apache:mainfrom
tisonkun:tison/tdigest-query-followups

Conversation

@tisonkun

@tisonkun tisonkun commented Oct 4, 2026 •

Copy link
Copy Markdown
Member

Summary

cdf and pmf previously recomputed cumulative centroid weights from the beginning for each split point. They now accumulate integer weights as sorted split points advance, sharing scalar rank's anchors and interpolation without allocating a prefix array.

The final diff contains only the query implementation and its changelog entry. Collection routing, empty-digest fast returns, compatibility decoders, existing tests, public signatures, and serialization layouts match the base branch.

Performance

Temporary library-level probes on Apple ARM64 with release builds, using the median of three interleaved runs against base e7f1b9f. The final implementation is 9b1bb86; the digests use k = 200 and 100,000 input values.

Workload Base This PR
CDF, 1 split point 59.8 ns 54.7 ns
CDF, 2 split points 92.4 ns 75.9 ns
CDF, 100 split points 3.49 us 2.49 us

CDF allocates only its result vector. These are workload-specific microbenchmarks, not an application-wide performance claim.

Validation

  • cargo x check, cargo x test (745 tests), and cargo x lint pass on the final implementation.
  • Differential probes match base across 1,260 query states, including extreme finite values, repeated means, signed zero, and large weights. CDF matches scalar ranks bit-for-bit, PMF matches their differences, and serialized output is preserved.
  • Additional differential probes preserve results for 10,000 collections and 8,840 valid or malformed compatibility inputs.

When the combined weight of collected inputs is at most k / 2, no two
centroids fit under the K_2 limit, so the k-way heap merge only
reproduces the stable-sorted concatenation one centroid at a time.
Take the same sort path as mixed compressed/buffered inputs, which
already skips the merge scan at these counts.
cdf and pmf answered each split point with an independent rank query,
rescanning the centroid weights twice per point. Precompute each
centroid's starting weight once; integer prefix differences are exact,
so results are bit-identical while each point costs two binary
searches.
The view layer already returns None for empty centroid sequences, and
compressing an empty buffer is a no-op, so the early returns in
TDigestMut::cdf, pmf, quantile, and quantiles only duplicated the view
contract. TDigestMut::rank keeps its check: the empty digest's sentinel
extrema would otherwise answer out-of-range queries instead of None.
The COMPAT_DOUBLE and COMPAT_FLOAT arms ran the same validation
pipeline with only scalar widths, the capacities field, and message
labels differing. Parameterize them behind CompatLayout, keeping every
error message byte-identical.
Shapes with a combined weight of at most k / 2 exercise the no-merge
fast paths in both borrowed and collected merging. Also document the
faster multi-point cdf and pmf in the changelog.
Reuse the weight below the previous anchor instead of allocating a full
prefix array for every CDF call. Sorted split points make the lower
anchors monotonic, so each query only sums the newly crossed weights.

Keep the existing rank interpolation shared with scalar queries and
scope the changelog claim to queries with many split points.
Restore the separate double and float decoding paths. The shared
CompatLayout reader adds calls to each centroid iteration and regresses
throughput; keeping the existing loops also removes the layout helpers.

Validation rules, conversion order, and error messages stay unchanged.
Add k=440 to the existing 220-weight interleaved-input fixture and remove
the duplicate small-input test. This covers the exact k/2 boundary while
retaining weighted ties, both compression directions, and later updates.
@tisonkun tisonkun changed the title perf(tdigest): skip unmergeable collection scans and share cdf prefix pass perf(tdigest): reuse CDF weight scans and skip small collection heaps Oct 4, 2026
A combined weight below k / 2 proves that no centroids can merge, but
sorting the concatenated inputs is not consistently cheaper than merging
their existing sorted runs. Restore the original routing after the sort
path regressed two compressed 50-row inputs by about 11%.

Remove the benchmark shapes and test parameter added for that routing.
The existing interleaved, mixed-input, and weighted collection tests remain
unchanged from main.

Validation: cargo test --package tests-integration --test tdigest_test
(52 tests passed).
Return None from cdf, pmf, quantile, and quantiles before entering the
compression and view path for an empty digest. The view-level checks
remain necessary for frozen queries but do not make these early returns
redundant for performance.

Keep argument validation before the early returns, preserving the
existing panic contracts even when the digest is empty.

Validation: cargo x check, cargo x test (745 tests), and cargo x lint.
@tisonkun tisonkun changed the title perf(tdigest): reuse CDF weight scans and skip small collection heaps perf(tdigest): reuse cumulative weights in CDF and PMF queries Oct 5, 2026
@tisonkun
tisonkun enabled auto-merge (squash) October 5, 2026 00:22
@tisonkun
tisonkun merged commit 48165ed into apache:main Oct 5, 2026
10 checks passed
@tisonkun
tisonkun deleted the tison/tdigest-query-followups branch October 5, 2026 00:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant