Every speed, as a fraction of a ceiling measured on the same machine
A raw timing says nothing on its own: it depends on the part, the power state, the compiler and the size of the problem. A fraction of a measured ceiling says how much of the machine a kernel is using and, more usefully, how much is left. Several of the conclusions here are that there is nothing left, and that is a result rather than a disappointment.
Every figure was taken on one laptop: an Intel Core Ultra 5 238V, four Lion Cove performance cores and four Skymont efficiency cores, no simultaneous multithreading, AVX2 and no AVX-512, with an integrated Arc 130V sharing the package and the power budget. None of these numbers should be read as a property of the algorithms alone.
Read the spread column
The protocol is a settling warm-up whose trials are discarded, a fixed number of timed trials, the median reported rather than the best of them, an interquartile range beside every median, a drift figure comparing the second half of a run against the first, a cool-down between configurations, and a thermal canary bracketing the whole session. Every one of those exists because the target is a laptop. A best-of-N on a part that throttles reports the trial taken before the fan noticed; a median with a spread beside it reports the machine. A row carrying a twenty-seven per cent interquartile range does not support a conclusion that a three per cent row would.
| Probe | Sustained | Spread | Drift |
|---|---|---|---|
| Read bandwidth | 95.68 GB/s | 1.5% | 0.9% |
| Triad bandwidth | 75.23 GB/s | 1.3% | 0.9% |
| Fused multiply-add | 330.25 Gflop/s | 2.2% | −0.4% |
| Divide and square root | 12.21 Gop/s | 3.3% | 1.2% |
Two of those rows decide most of what follows. Read bandwidth is seventy-one per cent of the nominal figure: the specification says about 135 GB/s and a read-only stream over a buffer sixty-four times the size of the last-level cache reaches 95.68 GB/s. A roofline drawn against 135 would place every kernel further below the line than it is and would blame the kernel for the memory controller. And the divide and square root ceiling is a twenty-seventh of the multiply-add ceiling. Every pairwise interaction contains one square root and one division, and they retire on a unit that is only partly pipelined and that the other eighteen operations do not use. That ratio, not the multiply-add peak, is what bounds this algorithm on this part.
The CPU direct kernel
| Kernel | Threads | Median | Spread | Drift | Of the peak | Of the divide ceiling |
|---|---|---|---|---|---|---|
| scalar | 1 | 177.6 ms | 1.9% | −0.1% | 2.3% | 6.2% |
| scalar | 8 | 35.57 ms | 2.2% | 0.4% | 11.4% | 30.9% |
| avx2 | 1 | 53.13 ms | 3.5% | −1.6% | 7.6% | 20.7% |
| avx2 | 8 | 13.80 ms | 3.7% | 4.0% | 29.5% | 79.7% |
The last column is the result and the one before it is the trap. At 29.5% of the multiply-add peak it looks as though two thirds of the machine is going to waste. At 79.7% of the square root and division throughput it is clear that almost nothing is: two operations in every twenty compete for a unit the other eighteen cannot use, and the missing fifth is those eighteen, the loads and the loop competing for issue slots. Vectorising and threading together are worth 12.87x in double precision and 22.28x in single. Further work on this kernel has to change the arithmetic rather than the code.
Eight cores that are not alike
The two kinds of core differ far more in vector throughput than in scalar: the ratio between them is 2.17 on the scalar kernel and 6.34 on the vector one. Vectorising therefore made the machine faster and made it less parallel, and both are real. Eight cores are worth 4.63 performance cores under AVX2, and a speedup on this part should be read against that rather than against eight.
| Scheme | Speedup | Of the 4.63 limit | Performance cores idle |
|---|---|---|---|
| Equal fixed shares | 3.63x | 78% | 83.4% |
| Work stealing | 4.13x | 89% | 8.2% |
The strongest result there is not the speedup. The scheduler was measured on the scalar kernel first, and a prediction was written down: that the balance it found would follow the hardware ratio without changes when the kernel was vectorised. It did. The share the performance cores took moved from 2.38 to one to 5.29 to one, tracking a hardware ratio that had gone from 2.17 to 6.34, and nothing in the executor changed between the two measurements. It holds no weights, no calibration and no topology. A weight tuned against the scalar kernel would have been wrong by a factor of nearly three by the time the vector kernel existed, and would have been wrong silently.
The tree, and the device
Barnes-Hut costs N1.24 fitted over two and a half decades, against 1.11 for exactly N log N and 2.0 for direct summation, and its crossover against direct summation is at about 6100 particles. That is high compared with the figures usually quoted, and it is high for a good reason: the opponent is an AVX2 kernel at four fifths of its hardware ceiling on eight cores rather than a scalar loop.
A GPU does not execute work-items independently: it executes them in sub-groups of 32 that share one instruction pointer, so a tree walk written as though each work-item were a thread has every lane switched off while the others finish the nodes it did not need. Walking the sub-group together, with one node index for all 32 lanes and a lane that has accepted a cell masked out until the group leaves that subtree, steps through about twenty-five per cent more nodes and takes under a third of the time, because the nodes it adds are ones the hardware was already executing under a divergence mask. One session gives 3.47x, 3.30x and 2.94x; a second gives 3.96x, 2.92x and 2.51x. The honest claim is about three times rather than any one of those figures, and that is the claim the project makes.
The largest tractable configuration is about 2.1 million particles, at 603 ms per force evaluation. Nothing about the device stopped it there. What stopped it is that a session has to finish. The bottleneck now is not on the GPU at all: the Morton sort on the host is 40 to 46 per cent of every GPU tree evaluation, so a phase spent making the traversal three times faster has succeeded in making the sort the bottleneck.
How much of this reproduces
The honest answer has two halves and both are part of the result. For configurations this part can sustain, the figures reproduce: the double-precision session behind the kernel table has interquartile ranges from 1.3 to 5.0 per cent on every row, its thermal canary moved 5.2 per cent across nine minutes of load, and its repeat row came back within 0.4 per cent.
For sustained eight-thread vector work, they do not always. The session before the one quoted produced 22.4 ms for the eight-thread AVX2 row rather than 13.8, with a 27 per cent spread, a 34 per cent drift inside the row, and a canary that spiked to 2.09 times its starting duration. That is the part hitting its power limit partway through a measurement. The difference between the two sessions is roughly the difference between a machine that had been under load for the previous hour and one that had not, and both of them are this machine. The harness reported all of that rather than averaging it away, which is what it is for.
Reproducing it
And, for the GPU, with the oneAPI compiler. Let the machine idle for several minutes first. The last of these is by a wide margin the longest session the project runs, since it drives the GPU and all eight cores in turn at sizes where one evaluation is most of a second.
The programs write their tables as CSV. The sessions these figures come
from are committed beside the reports, in
docs/performance/.
Next: four solvers behind one interface ·
play the cluster run