What determinism costs
Worth reading first: The sum that cannot be wrong · The same program, twice · The order they are added in.
The sum that cannot be wrong establishes that order-independent summation exists. The question that follows immediately is what it costs, and the answer has two halves that point in different directions.
The first half of the answer is on the figure. The second half is not, and is the more important of the two.
Reading the figure
Six policies, one vector of 2,048 numbers with a summation condition number of about 10⁶.
Runtime order — one add per element, one accumulator, whatever ordering the runtime picks. This is the baseline and it is the fastest thing on the figure.
A fixed partition — the same loop with the thread count pinned. Identical cost, identical accuracy, and reproducible only across runs that agree about the partition, which is why its marker is open rather than filled.
Pre-rounded, one pass — three operations an element, order-independent, and the least accurate policy on the figure at 2.3·10⁻⁷.
Pre-rounded, two passes — seven operations, order-independent, and on this vector exactly the correctly rounded answer.
Compensated — four operations, accurate, and not reproducible, which is the finding of the previous essay.
Exact accumulation — about twelve operations and a list of partials, correctly rounded, and order-independent by definition.
Nothing sits in the bottom left. There is no policy that is both as cheap as the baseline and independent of the order, and there cannot be one: the baseline’s cheapness is exactly its freedom to round wherever the arithmetic lands, and removing that freedom is the whole of what the other policies do.
The operation count is the half that misleads
Now the half of the answer that is not on the figure, and it is the one this site has a field about.
The same arithmetic at a different price measures two eliminations performing 72,568 operations each, choosing the same pivots, returning a factorisation identical to the last bit — and moving 41,332 and 19,476 words between fast and slow memory. The operation count is identical and the cost is not, by a factor of two.
A reduction is the extreme case of that. It reads n numbers and performs n adds, so it is memory-bound on every machine built in the last thirty years: the loop spends its time waiting for data and the arithmetic unit is idle most of the way. Adding two more operations per element to a loop that is waiting for memory costs approximately nothing.
So the honest reading of the figure’s horizontal axis is not three times the cost. It is:
- the one-pass pre-rounded policy is nearly free on a memory-bound reduction, because the arithmetic it adds fits in time the loop was already spending;
- the two-pass policy costs a second pass over the data, which on a vector that does not fit in cache is a genuine doubling — not because of the seven operations, but because of the second traversal;
- exact accumulation costs what its state costs. Forty words of live state per accumulator and a data-dependent inner loop is the description of something that does not vectorise, and a reduction that does not vectorise gives up a factor that has nothing to do with its operation count.
Which reorders the figure. On flops, the ordering is 1, 3, 4, 7, 12. On what a reduction actually spends, it is closer to: the baseline and one-pass pre-rounding are the same speed, two-pass pre-rounding is twice that, and exact accumulation is a different kind of loop.
The state column, which is the one to read
Every policy on the figure carries a second number that the horizontal axis does not show: how much live state it needs per accumulator.
| policy | operations | state | order-independent |
|---|---|---|---|
| runtime order | 1 | 1 | no |
| fixed partition | 1 | 8 | at one partition count |
| pre-rounded, one pass | 3 | 1 | yes |
| pre-rounded, two passes | 7 | 2 | yes |
| compensated | 4 | 2 | no |
| exact accumulation | 12 | ~40 | yes |
One accumulator vectorises trivially — the compiler keeps four or eight of them in registers, which is the same trick the length that changes the kernel is about. Two is still fine. Forty is not, and the pattern of writes into them depends on the values, which defeats the vectoriser for a second reason.
That is the whole argument for the pre-rounded policies over the exact one, and it is an argument about registers rather than about arithmetic.
What each one is actually for
Prices are only useful next to purposes, so:
Runtime order is right whenever no comparison is downstream. A residual printed in a convergence log, a norm reported at the end, a diagnostic. Most reductions are in this category and this field is not an argument for changing them.
A fixed partition is the cheapest thing that helps and it is a partial answer. It removes the thread count as a variable and leaves the scheduler in place, so it fixes reproducibility within one machine’s configuration and not between two. It is worth doing because it is free, and worth not mistaking for a solution.
One-pass pre-rounding is for a reduction whose verdict matters and whose value does not. A rank threshold, a definiteness sign, a convergence test: the comparison needs the two machines to agree, and 10⁻⁷ of the largest element is far below any threshold worth setting. This is the case the policy was invented for and it is nearly free.
Two-pass pre-rounding is for a reduction whose value matters too, which is most residuals. Seven operations, two passes, correctly rounded on the vector measured here, and the same on every machine. If this field has one recommendation it is this one.
Compensation is for accuracy alone, on a machine that is not going to be compared with another one. It is the best accuracy-per-operation on the figure and it buys nothing this field is about.
Exact accumulation is for ground truth. It is what every measurement in this field is against, which is a use that justifies its cost completely: a reference implementation runs once and an inner loop runs 10⁹ times.
What it costs inside a solver, which is the number anybody wants
Operations per element is the right axis for a summation policy and the wrong axis for a decision. The decision is whether to spend it inside something, so here is the same price paid inside a conjugate gradient step.
One step is a matrix–vector product, three vector updates, and two inner products. Only the last of those is a reduction, so only the last is affected — and how much it matters depends entirely on how expensive the product is. Counting operations on a problem with n = 200:
| the matrix | one-pass | two-pass | compensated | exact |
|---|---|---|---|---|
| dense, 200 per row | 1.01× | 1.03× | 1.01× | 1.05× |
| sparse, 27 per row | 1.06× | 1.19× | 1.10× | 1.35× |
| sparse, 5 per row | 1.22× | 1.67× | 1.33× | 2.22× |
On a dense matrix the whole question is noise: making both reductions exactly accumulated costs five per cent of a step, and the answer stops depending on the machine. On a five-point stencil — the model problem this site’s iterative field is built on, at five nonzeros a row — exact accumulation doubles the step, and one-pass pre-rounding costs 22%.
That table is the practical content of the essay and its shape is worth stating: the cheaper the matrix, the dearer the reproducibility, because the reductions are a larger share of a step whose product is cheap. A solver on a dense matrix can have bitwise reproducibility for nothing. A solver on a sparse one is making a real choice.
And it points at the right intervention on the expensive side: one-pass pre-rounding at 22% on the sparse problem buys agreement in the stopping test, which is what a stopping test is a race shows costs 674 to 690 iterations — a spread of 2.4%. Spending 22% of a step to remove a 2.4% spread in the step count is a bad trade if the only goal is the count, and a good one if a verdict downstream needs two machines to agree.
What no summation policy buys
Three of this field’s results are outside the reach of everything on the hero figure, and a reader budgeting for reproducibility should know which they are before spending anything.
The kernel cutoff. A library that switches from one accumulator to four at a length threshold is changing algorithm, not ordering, and no policy applied to either kernel makes the two agree. The step in the length that changes the kernel is a property of a version number.
The contraction. Whether a multiply-add was fused is decided at compile time, and both forms are conforming. A reproducible summation over products that were themselves rounded differently is reproducibly wrong in two different ways. One multiply the compiler removed is that measurement.
The algorithm. Two libraries computing a QR by Householder and by a tall-skinny tree return different numbers, both backward stable, and the difference is not an ordering at all — it is a reduction that changes the order, where the tree is the better of the two.
So the recommendation this figure supports is narrower than make everything reproducible. It is: identify the reductions a verdict is read from, price them against the work around them, and spend there. The rest of the program’s variability has other causes and other repairs.
Where the second pass stops being free
One detail of the two-pass policy deserves its own paragraph, because it is the difference between a 3% cost and a 67% one in the table above, and it is not visible in either the operation count or the accuracy.
The second pass reads the vector again. If the vector fits in cache, that read is nearly free and the seven operations are what the policy costs. If it does not, the second pass is a second traversal of main memory, and on a memory-bound reduction that is a genuine doubling — the whole cost of the loop, paid twice, for a policy whose arithmetic is trivial.
The repair is to fuse the two passes: snap, accumulate, and accumulate the residue, all in one traversal, at the price of a second accumulator and a second δ that has to be fixed in advance rather than computed from the residues. Fixing it in advance costs accuracy — the residue’s largest element is not known until the first pass has run — so the fused version is a policy between the two, and which of the three is right depends on whether the data fits in cache.
That is the same arithmetic at a different price in miniature, and it is the reason this essay’s table is a table of operation counts with a warning attached rather than a table of times: the operation count is the same on every machine and the answer to which policy is not.
The comparison nobody runs
A closing observation about why the figure is unusual rather than standard.
Every library that ships a reduction has measured its speed. Many have measured its accuracy against a reference. Almost none publish the third column, because the third column requires running the same input twice under different partitionings and comparing bits — which is not a benchmark, it is a test, and it lives somewhere else entirely from a timing.
The consequence is that the trade-off in the hero figure is not visible from any documentation. A caller choosing between two routines can find out which is faster and which is more accurate, and cannot find out which returns the same answer twice. That is the gap what a regression test can ask for measures from the consumer’s side, where the same missing column turns into a tolerance nobody can choose.
And it is the reason this field’s recommendation is stated in terms of the caller’s own data rather than in terms of a library’s. κ of the caller’s sum is computable in one pass; which reduction the library performed is not discoverable at all.
A note on the axis that is missing
The figure has cost and accuracy on it and not the third property, because the third property is not a number: a policy either returns the same bits or it does not. That is why the reproducible ones are drawn as filled markers rather than as a third axis, and it is worth saying plainly, because the temptation to make it continuous is strong and produces nonsense.
How reproducible is not a quantity. The count of distinct answers over four hundred permutations — 303, 72, 119, 1, 1 in the sum that cannot be wrong — looks like one, and it is not: it is a property of the sample rather than of the policy, and a longer sample moves every number above one and none of the ones equal to it. The only honest reading of that column is binary, and the only honest way to draw it is a mark.
The same applies to the spread in ulps that the earlier essays report. It is a useful size, it predicts what a tolerance has to clear, and it is not a measure of reproducibility — a policy with a spread of two ulps is exactly as irreproducible as one with a spread of two million, and it will fail a bitwise test just as reliably. What the small spread buys is that a tolerant test can be written, which is the whole subject of what a regression suite can ask for.
At other settings
What links here
Computed from the collection, not written here: the essays that point at this one.
Shares its objects with
Essays that name at least two of the same things, and that neither author linked.
- A block size is a property of the machine — both name data movement, flop count, memory hierarchy
- The order the products are taken in — both name flop count, memory hierarchy
- Where the format starts paying — both name data movement, flop count
Named objects
A flat tag is an object no other essay names yet.
Bitwise reproducibilityData movementExact accumulationFlop countKahan summationMemory hierarchyPre-rounded summationReproducible summation