Repository navigation
perf: kll_sketch allocates its items on first use - #558
Open
rustyconover wants to merge 2 commits into
Open
rustyconover wants to merge 2 commits into
rustyconover wants to merge 2 commits into
Conversation
This was referenced Oct 28, 2026
proost
requested changes
Oct 29, 2026
proost
left a comment
Member
There was a problem hiding this comment.
Idea is good, But null checking isn't enough.
For example, sort_level_zero methods should check null for items_.
And we should guard all null cases in one place not each call sites.
The constructor allocated storage for k items up front, so every empty sketch held k * sizeof(T) bytes (1.6 KB for the default k with doubles). Code that creates many sketches that stay empty or small (one per group in a grouped aggregation) paid for all of it. Allocate the items on the first update or non-empty merge instead. The copy and converting constructors only allocate if the source has items; the destructor and the iterator already handle an unallocated buffer. Results and the serialized format are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Check for an empty sketch in get_sorted_view before sorting level zero or forming item ranges. This shared entry point is the only caller of sort_level_zero, so no per-call-site null checks or eager allocation are needed. Exercise empty views after construction, copies, moves, assignments and deserialization, then verify that first updates still allocate and sort correctly. The new custom-type regression reproduces the null-pointer offset under UBSan before the fix.
rustyconover
force-pushed
the
feat/kll-lazy-items
branch
from
October 29, 2026 16:32
84a5c37 to
cc006f0
Compare
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
kll_sketch's constructor allocates storage forkitems straight away. For the defaultk = 200withdoubleitems, that's 1.6 KB per sketch before anything has been added.That's costly when an application creates many sketches that stay empty or small, for example one per group in a grouped aggregation. Query engines also create short-lived partial states that are only ever merged into, or that see a single row each before being combined.
This PR allocates
items_on the firstupdate()or non-emptymerge()instead:internal_update(), which bothupdate()and the level-zero part ofmerge()go through, allocates on demand.merge()also allocates before merging the higher levels, because the other sketch's level zero can be empty while its higher levels aren't.const_iteratoralready handleitems_ == nullptr.get_sorted_view()returns an empty view before sorting or constructing item ranges when the sketch is empty. This single check protects both paths without allocating item storage;sort_level_zero()is private and is called only from this guarded entry point.Results and the serialized format are unchanged.
Tests
Regression coverage uses
test_allocatorto check:The new custom-type empty-view test reproduces
applying non-zero offset 1600 to null pointerunder UBSan before the guard is added.Validation:
Context
We found this in the DuckDB DataSketches extension. For GROUP BY queries with many groups, DuckDB creates a separate aggregate state for each input row and merges them later, and the eager allocation made a KLL aggregate over 2M groups use tens of GB. DuckDB made the same change to its own t-digest for the same reason.
req_sketch(its first compactor) andtdigest(centroids_andbuffer_) allocate eagerly too; if this approach is welcome, I can send the same change for them.🤖 Generated with Claude Code