Build1 distinct publisher3 min readUpdated
A Metal GROUP BY kernel ran 1.7x slower than single-threaded std::unordered_map. The diagnosis came from sweeping one parameter, not from a profiler.
The Engineer · Build desk
Compiled by The EngineerSomething wrong?How this is made
A maintainer of a Metal-backed GPU SQL engine ran his own benchmark on ten million rows and lost to a single CPU thread walking a hash map by 1.7x [2][3][4]. The fix was one integer, but the transferable part is the method: he swept the workload's only free parameter before opening anything else, and the shape of the result named the bug class [6].
The engine is a DuckDB community extension that runs aggregates and GROUP BY on Apple Silicon through Metal, according to a writeup published on dev.to [1][24]. At 10M rows and 1,024 groups, single-threaded std::unordered_map posted a 14.65 ms median wall time against 25.39 ms for the Metal path on an M4 Max [2][3].
Varying group cardinality across six points showed CPU time rising gently, from 14.62 ms at 8 groups to 30.37 ms at 65,536 [7]. The GPU line did not behave like that: 6.50 ms at 256 groups, 25.09 ms at 1,024, then partial recovery to 13.30 ms at 65,536 [8]. His reading is that a curve means the algorithm degrades, while a cliff followed by recovery means a branch chose differently [9]. The engine had been printing that choice to stderr all along: radix-opt at 256 groups, slot-lock at 1,024 [10]. Note what the grid alone bought before any code was read. Two adjacent sample points localised the fault to a 4x-wide interval [2].
The dispatcher selected the slot-lock kernel whenever the expected group count sat between kSlotLockMinGroups = 1,024 and kSlotLockSafeCap = 16,000,000 [11]. That kernel builds 4,096 fixed hash partitions with threadgroup-resident slot tables, a layout intended for millions of groups; hand it 1,024 groups and the partitions collapse onto a handful of slots while thousands of threads serialise behind slot locks [12].
An existing environment override let both kernels be raced at fixed cardinality rather than trusting the comment above the constant [13]. Radix-opt stayed between 7.32 and 8.17 ms from 256 through 65,536 groups, a spread of about 1.12x, while slot-lock fell from 27.67 ms to 13.33 ms over the same range [14][15][1]. They crossed at 131,072 groups, 11.24 ms against 10.32 ms [14][15][16]. The threshold was therefore 128x below the measured crossover, and everything in the 64x-wide band between them got the slower kernel, by up to 3.1x [17].
Setting the constant to 131,072 moved auto-dispatch from 24.59 ms to 8.13 ms at 1,024 groups and from 23.62 ms to 7.85 ms at 2,048, gains of 3.02x and 3.01x, with 2.59x at 8,192 and 1.63x at 65,536 [18][19]. Against the single CPU thread, the 1,024-group case went from 0.60x to 1.81x, and 65,536 groups from 2.32x to 3.77x [20]. He reports 96 of 96 unit checks and 72 of 72 SQL tests still passing, with the benchmark checking every result against a CPU reference on each run [21]. The measurement table was left in the comment above the constant [23].
The scale check is the honest part. Crossover sits near 131K groups at 1M rows and drifts to roughly 196K at 50M, so a fixed 131,072 is conservative at the high end, costing about 8% in one cell while being far better across the 1K to 64K band [22].
Two things to watch. First, any dispatcher threshold in your own stack that was reasoned rather than measured, because those constants live precisely where nobody benchmarks. Second, whether that upward drift with row count eventually forces this threshold to become a function of input size instead of a literal [22]. All numbers here are the author's own, from one machine, unreplicated [24].
Follow any of these and your For You feed starts watching them — no settings page required.
Ranked by verification strength, evidence, and original report placement.
The project is a GPU SQL engine implemented as a DuckDB community extension that runs aggregates and GROUP BY on Apple Silicon via Metal.
In the benchmark gpudb-groupby-bench with rows=10000000 and groups=1024, the single-threaded std::unordered_map CPU path had a median wall time of 14.65 ms.
In the same run (10M rows, 1024 groups), the Metal path on an Apple M4 Max had a median wall time of 25.39 ms.
The author describes the GPU as taking 1.7x longer than one CPU thread walking a hash map.
The bug was a single integer constant, and fixing it was a 3.0x speedup.
The author advises against opening a GPU profiler first, arguing a single bad data point tells you nothing about shape, and instead swept the one parameter the workload had: group cardinality.
Evidence-backed comparisons of source perspectives and observed adoption signals. Read the methodology
Which Builder, Operator, and Investor concerns the observed source mix emphasized—not a truth score.
Evidence, demonstrated adoption, hype gap, incentives, and confidence are assessed independently, each on its own current evidence. How these are measured.
Unusually detailed but wholly self-reported and unreproducible as published
For a single-source blog post the evidentiary discipline is high: paired CPU/Metal sweeps with medians of 3, forced-kernel races with medians of 5, quoted dispatcher source and diff, cross-scale crossover checks at 1M and 50M rows, and a claim that every benchmark result is verified against a CPU reference alongside 96/96 unit and 72/72 SQL test passes. What caps the score is that every number comes from one maintainer on one Apple M4 Max, the project and repository are never named or linked, no variance or environment details are given, and the CPU comparator is deliberately a single-threaded std::unordered_map rather than a parallel production baseline.
Only the maintainer's own benchmark exercises it
The only usage evidence in the cluster is the author running the project's own benchmark harness; there are no downloads, releases, third-party deployments, named users or DuckDB extension distribution figures. The author further states that the defective cardinality band was unreachable through the normal query path, so even within the single known installation the affected code was exercised only by the benchmark that publishes performance numbers.
Dramatic framing, but the author supplies the deflating caveat himself
The headline framing (a GPU database losing to one CPU thread, a 3.0x fix from one constant) is fully backed by the tables presented, so the overstatement is modest. It is positive rather than zero because the practical impact is narrower than the framing implies: by the author's own analysis the outer hybrid planner never routed real queries into the broken band, so the measured 1.63x-3.02x gains accrue to the project's benchmark rather than to any user's query latency, and the impressive CPU-relative ratios are computed against a single CPU thread.
Contest submission written by the same person who ran and publishes the benchmarks
The piece is explicitly a submission to DEV's Summer Bug Smash powered by Sentry, a sponsored writing contest that rewards vivid bug narratives, and it is authored by the maintainer of the code being measured, who both produced the failure and produced the numbers proving the fix. That combination creates a real incentive toward dramatic framing and favourable comparators. Mitigating factors are that the author discloses the contest context up front, publishes the raw tables, and volunteers the finding that the defect was unreachable in production, which cuts against his own headline.
Internally consistent single account, no external corroboration
The technical narrative is coherent and arithmetically self-consistent across five separate tables, the code excerpts match the described behaviour, and the fix's effect is reported with correctness checks, which supports moderate confidence in the specific mechanism. Confidence is held below that level because there is exactly one publisher, one author, one machine and no identifiable artifact to verify against, so nothing in the cluster can be independently checked or generalised beyond this project and Apple Silicon.
build
Four indexes, none of them covering: the 78-second page and the one index that fixed it1 distinct publisher
build
Short Build Roots Do Not Fix MAX_PATH: Unreal Cook Failures Are a Windows Setting1 distinct publisher
build
The fence was fine, the test was green, and the injection still worked1 distinct publisher
build
A Prometheus that had written nothing for hours passed every health check1 distinct publisher
Distinct publishers with included, body-backed reporting in this cluster.
dev.to
1 article · August 21, 2026