11================================================================================
2- QuART k-fp: Why the Sequential findSlot is Slower than the Parallel findSlot
2+ QuART k-fp: Why the Sequential findSlot is Slower than the Branchless findSlot
33 A profiling report + branch-misprediction explanation
44================================================================================
55
@@ -18,7 +18,7 @@ Benchmark : test_kfp, K=3, three sorted BoDS streams interleaved uniformly at
1818* The Sequential findSlot is slower because of BRANCH MISPREDICTION, not because
1919 of data copying or cache misses.
2020
21- * perf proves it: the Sequential and Parallel modes have an IDENTICAL L1 data-
21+ * perf proves it: the Sequential and Branchless modes have an IDENTICAL L1 data-
2222 cache miss rate (0.14% vs 0.15%), but Sequential suffers ~35M MORE branch
2323 mispredictions (55.2M vs 19.7M). Each misprediction is a ~15-20 cycle pipeline
2424 flush.
@@ -28,7 +28,7 @@ Benchmark : test_kfp, K=3, three sorted BoDS streams interleaved uniformly at
2828 uniformly at random, each key matches a random one of the 3 slots, so that
2929 branch is fundamentally unpredictable (~1/3 taken, no pattern).
3030
31- * The Parallel mode is faster because it converts K unpredictable branches into
31+ * The Branchless mode is faster because it converts K unpredictable branches into
3232 K branchless comparisons + ONE highly-predictable branch ("did ANY slot
3333 match?", ~96% taken) + one ctz instruction to extract which slot.
3434
@@ -39,7 +39,7 @@ Benchmark : test_kfp, K=3, three sorted BoDS streams interleaved uniformly at
3939 REGRESSES this uniform-random benchmark.
4040
4141* Decision: leave the Sequential findSlot as the clean, honest baseline. The
42- Parallel mode is the answer when speed matters.
42+ Branchless mode is the answer when speed matters.
4343
4444
4545--------------------------------------------------------------------------------
@@ -58,7 +58,7 @@ upper bytes:
5858findSlot has two implementations selected at compile time by the SearchMode
5959template parameter:
6060
61- Parallel (default) - branchless K-way compare, collapsed to one branch.
61+ Branchless (default) - K-way compare, collapsed to one branch.
6262 Sequential - the classic early-exit scan: test slots one at a
6363 time, stop at the first match.
6464
@@ -84,7 +84,7 @@ SEQUENTIAL (the early-exit scan; trees/QuART_kfp.h):
8484 }
8585 return {-1, MatchType::NO_MATCH};
8686
87- PARALLEL (branchless K-way match; trees/QuART_kfp.h):
87+ BRANCHLESS ( K-way match; trees/QuART_kfp.h):
8888
8989 unsigned match = 0;
9090 for (int i = 0; i < K; i++)
@@ -103,7 +103,7 @@ compiler.
103103
104104To measure each mode in isolation, test_kfp gained dedicated run modes:
105105
106- ./build/test_kfp par <maxkeys> # only the Parallel QuART_kfp<3>
106+ ./build/test_kfp par <maxkeys> # only the Branchless QuART_kfp<3>
107107 ./build/test_kfp seq <maxkeys> # only the Sequential QuART_kfp<3,FIFO,Sequential>
108108 ./build/test_kfp ff <maxkeys> # only the FREQ_FILTER variant
109109 ./build/test_kfp kfp <maxkeys> # all three
@@ -132,9 +132,9 @@ Hotspot attribution:
132132 4. RESULTS (perf stat, isolated, 20M keys/stream)
133133--------------------------------------------------------------------------------
134134
135- Metric Sequential Parallel Delta
135+ Metric Sequential Branchless Delta
136136 ----------------------- ------------ ----------- ----------------
137- Throughput (timed) 39.4 M/s 54.1 M/s Parallel 1.37x
137+ Throughput (timed) 39.4 M/s 54.1 M/s Branchless 1.37x
138138 task-clock 1528 ms 1114 ms
139139 instructions 12.05 B 11.80 B seq +2%
140140 cycles 5.58 B 3.94 B seq +42%
@@ -163,7 +163,7 @@ early-exit comparison branches.
163163The two telling facts:
164164
165165 (a) The L1 data-cache miss RATE is identical (0.14% vs 0.15%). The Sequential
166- scan does NOT copy structs and does NOT miss cache more than Parallel . The
166+ scan does NOT copy structs and does NOT miss cache more than Branchless . The
167167 hypothesis that "copying instead of references" causes the slowdown is
168168 DISPROVEN by the data. (The only real copies in the insert path are in
169169 loadSlot/saveToSlot, and the FP_INSERT hot path already uses a reference
@@ -234,10 +234,10 @@ THE SEQUENTIAL BRANCH HAS NO PATTERN
234234
235235
236236--------------------------------------------------------------------------------
237- 7. HOW THE PARALLEL DESIGN SOLVES IT
237+ 7. HOW THE BRANCHLESS DESIGN SOLVES IT
238238--------------------------------------------------------------------------------
239239
240- The Parallel version never branches per slot. Conceptually, for K=3:
240+ The Branchless version never branches per slot. Conceptually, for K=3:
241241
242242 bit0 = (slots[0].cached_upper == keyUpper); // branchless: cmp + sete
243243 bit1 = (slots[1].cached_upper == keyUpper); // branchless
@@ -253,7 +253,7 @@ Three things change:
253253 with NO jump and NO control-flow change. There is nothing to mispredict --
254254 the CPU computes all three comparisons unconditionally. Because the three
255255 are independent (no data dependency), they issue on separate execution ports
256- in the same cycles. That parallelism is what gives the mode its name .
256+ in the same cycles -- and none of them is a branch, which names the mode .
257257 Result for a key from stream B: match = 0b010.
258258
259259 2. THE ONE REMAINING BRANCH IS PREDICTABLE.
@@ -267,7 +267,7 @@ Three things change:
267267 __builtin_ctz(match) (count-trailing-zeros), a single instruction returning
268268 the index of the lowest set bit (0b010 -> 1). No guessing.
269269
270- Intuition: Parallel shows the package to all three people AT ONCE; they answer
270+ Intuition: Branchless shows the package to all three people AT ONCE; they answer
271271 simultaneously and you read off who raised a hand. The only thing you "predict"
272272 is "did somebody raise a hand?", and the answer is almost always yes.
273273
@@ -278,7 +278,7 @@ THE CORE IDEA
278278
279279 Sequential : in CONTROL FLOW (a chain of data-dependent branches)
280280 -> the predictor must guess random outcomes -> ~55M flushes.
281- Parallel : in the DATAPATH (branchless sete compares + ctz), leaving only
281+ Branchless : in the DATAPATH (branchless sete compares + ctz), leaving only
282282 one PREDICTABLE branch ("any match?") -> ~20M misses, 1.37x.
283283
284284 You cannot make the random "which slot" question predictable; that is the
@@ -350,7 +350,7 @@ regresses the uniform benchmark.
350350 NOT cache, NOT instruction count.
351351
352352* On THIS benchmark the Sequential scan is already at its floor. No change that
353- keeps it sequential improves it; the parallel restructuring is the only thing
353+ keeps it sequential improves it; the branchless restructuring is the only thing
354354 that helps, and that mode already exists.
355355
356356* The one realistic lever is the temporal-locality hint, which only pays off if
@@ -359,7 +359,7 @@ regresses the uniform benchmark.
359359 construction, so it cannot show that win and in fact regresses ~2-6%.
360360
361361* DECISION: leave the Sequential findSlot as the clean, honest baseline. Use the
362- Parallel mode when throughput matters (1.37x here). If a future experiment uses
362+ Branchless mode when throughput matters (1.37x here). If a future experiment uses
363363 clustered/bursty interleaves, revisit the opt-in hint as a third SearchMode.
364364
365365
@@ -372,7 +372,7 @@ regresses the uniform benchmark.
372372 cmake --build build --target test_kfp
373373
374374 # Isolated timing
375- ./build/test_kfp par 20000000 # Parallel ~54 M/s
375+ ./build/test_kfp par 20000000 # Branchless ~54 M/s
376376 ./build/test_kfp seq 20000000 # Sequential ~39 M/s
377377 ./build/test_kfp kfp 20000000 # both + FREQ_FILTER, prints 1.37x speedup
378378
@@ -384,7 +384,7 @@ regresses the uniform benchmark.
384384 L1-dcache-loads,L1-dcache-load-misses ./build-prof/test_kfp seq 20000000
385385 perf stat -e ... ./build-prof/test_kfp par 20000000
386386
387- Code under study: trees/QuART_kfp.h, function findSlot (Parallel branchless
387+ Code under study: trees/QuART_kfp.h, function findSlot (Branchless
388388 block and the Sequential early-exit scan).
389389
390390================================================================================
0 commit comments