Repository navigation
Optimize scattered BatchEqual with bounded container decoding - #589
Open
perfloop-agent wants to merge 3 commits into
Open
perfloop-agent wants to merge 3 commits into
perfloop-agent wants to merge 3 commits into
Conversation
Signed-off-by: Perfloop Agent <agent@perfloop.ai> Signed-off-by: Tomás Senart <ts@perfloop.ai>
Signed-off-by: Perfloop Agent <agent@perfloop.ai> Signed-off-by: Tomás Senart <ts@perfloop.ai>
Signed-off-by: Perfloop Agent <agent@perfloop.ai> Signed-off-by: Tomás Senart <ts@perfloop.ai>
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.
Description
Our earlier PR #533 (perfloop-agent, merged as
7900bdc) made scatteredBatchEqualqueries that reach its scan slower than the trie it replaced, because the scan calledGetValue, which probes every bit plane, for every existing column. In the AgeRandom p2-w2 row below, elapsed time rose from 521 ms with the trie to 3,257 ms with #533's scan, while allocated bytes fell from 230 MB to 158 MB; about 112 MB of the scan's allocation is the materialized column-ID array (4 bytes for each of 28,043,123 columns).This replaces the general scan with bounded container decoding. A bit plane is a bitmap of columns whose value has one particular bit set. The scan first applies the bits shared by all query values, then decodes up to sixteen varying planes into small keys and tests those keys against a query bitset. If fewer than 100,000 columns remain after the fixed planes, the unchanged trie finishes the query on those columns. Otherwise, a key that covers every varying plane matches directly; queries with more than sixteen varying bits, and containers with fewer than 1,024 remaining columns, still receive an exact
GetValuecheck.Type of Change
Changes Made
What was changed?
Why was it changed?
The old fallback traded lower allocations for repeated full-value reconstruction. Removing the column-ID array alone does not remove those bit-plane lookups. Without a fixed-plane filter, columns from other categories also reach value checks.
How was it changed?
GOMAXPROCSand the scratch budget. Nonpositive arguments select the CPU count before those caps. Inputs retain BSI's existing caller-provided concurrency guards.Testing
The Go tests passed for the BitSliceIndexing package. The committed differentials compare against a
GetValuescan on the deserialized age fixture, narrow and wide indexes, composite values, run-optimized bitmaps, signed cubes, extra planes above bit 63, stray plane bits, sparse IDs, prefix collisions, the maximum column ID and worker argument categories. They also check result isolation. The age differentials use fewer worker arguments and skip withtesting.Short(); the synthetic shapes retain negative, default, explicit and large worker arguments.We also compared results with the pre-#533 trie locally; that check is not part of this PR.
Formatting
The changed Go files are formatted, and the package's type-conversion check passes.
Fuzzing
Not run. The template's smat fuzzer exercises the root
roaringpackage, notBitSliceIndexing.Performance Impact
These are local runs, not production measurements. All arms used Go 1.24.4 and the same benchmark inputs. Each row has three interleaved triplets, with varied arm order and a fresh process per invocation. Values are separate arm medians and median paired percentage changes; negative changes mean lower cost. M128 and M200 are controls, not improvement claims.
The scan comparator is base
33586ce, which contains #533's scan, plus this PR's benchmark files andBitSliceIndexing/bsi_scattered_test.go. Both comparison binaries compile the same added native tests; those tests are not run during timing. The trie comparator is a modified build of that same base using theBatchEqualbody from public commit 873a1e356cf5dd8688f43c7fcd82dccd159a9b63. It is not a measurement of that entire historical checkout.The Go runtime reported four available CPUs.
p2-w2meansGOMAXPROCS=2and worker argument 2;p2-w0meansGOMAXPROCS=2and worker argument 0;default-w0leavesGOMAXPROCSat its default of four and passes worker argument 0. For the candidate, p2-w0 and p2-w2 run the same two workers: worker argument 0 selects four CPUs, andGOMAXPROCScaps that at two. Cube shortcuts and narrowed tries do not launch scan workers.The age rows use the checked-in fixture and its authoritative existence bitmap. Synthetic rows have 300,000 columns: fourteen-bit values, random wide values with 200 or 1,000 query values, stride-two column IDs with 1,000 query values, and wide values sharing their top sixteen bits with 200 query values. Composite22 and CompositeWide store a category and a sixteen-bit code, with the category starting at bit 16 or bit 40 respectively. They query 200 codes within one category, so filtering the fixed category bits matters. About 4,700 columns remain after the category filter, so these rows measure the narrowed trie, not the scan workers.
Go benchmark-format per-metric medians for AgeRandom with two-way Go parallelism:
Complete local comparison matrix
Running Benchmarks
Compile one test binary per arm. For each row, run the three arms once each, in a different order each time:
Repeat that triplet three times per row. For the
p2rows, setGOMAXPROCSto 2 before starting the process; leave it unset for the default rows. SelectDefaultWorkersfor zero-worker-argument cases. Use one shape per invocation:AgeRandomandAgeNearCubeuse1x;Narrow14,Wide200,Wide1000,WideStride2andWideSharedPrefixuse8x;Composite22andCompositeWideuse64x. The existingBenchmarkBatchEqualM128Scattereduses1x, andBenchmarkBatchEqualM128/BenchmarkBatchEqualM200use256x, with the same worker subbenchmarks.For both comparison arms, first copy
BitSliceIndexing/bsi_scattered_test.gofrom this PR so that the same native test coverage is compiled in. For the trie comparison, start from base33586cewithout any of this PR'sbsi.gochanges, add a benchmark-only method with theBatchEqualbody from873a1e3(which calls the unchangedmatchTrie), and route the benchmark call to it. For the scan comparison, add this PR's benchmark files to base33586cewithout any of this PR'sbsi.gochanges.Performance Analysis
All measured scattered shapes allocated fewer bytes than the old scan. The p2-w0 Wide200 row has an after median of 4115493 ns/op versus 5343757 ns/op with the trie, with a median paired change of -23.48%. Composite rows and the contiguous controls contain estimates in both directions. These short local comparisons do not by themselves establish a statistical no-regression guarantee. In some rows the arm medians and the median paired change differ in sign; for example, p2-w2 WideSharedPrefix has an after median of 5420248 ns/op against 5318325 ns/op for the trie, but a median paired change of -8.95%. With three pairs, both estimates are within run-to-run noise.
Limits:
B/opmeasures Go allocated bytes, not peak resident memory.The measurements do not establish production traffic frequency or impact.
Breaking Changes
No API signature or query-result changes. The documented worker caps can select fewer workers than requested.
Related Issues
Follow-up to #533.
Additional Notes
The existing scan/trie entry thresholds and result run-optimization behavior are unchanged. The trie implementation is unchanged. Because the scan now receives the normalized query map, queries with eight or fewer values allocate that map on the heap (two allocations per call).
Case and evidence
Made by Perfloop. Human sponsor: Tomás Senart. To ask for a change, comment or review in plain words. How Perfloop handles pull requests.