Where the flop count stopped predicting the time

The same arithmetic at a different price

A blocked and an unblocked elimination perform 72,568 operations each — the same operations, associated differently — choose the same pivots, and return a factorisation identical to the last bit: ‖PA − LU‖/‖A‖ = 4.487946226420872·10⁻¹⁶ in both. One of them moves 41,332 words between fast and slow memory and the other moves 19,476.

Worth reading first: Elimination is a sequence of choices.

Every cost claim on this site so far has been a count of arithmetic. n³/3 multiply-adds for an elimination, 2mn² for a Householder QR, one matrix–vector product per conjugate gradient step. Those counts are correct, they are the ones every textbook gives, and they have not decided which of two implementations is faster for about thirty years.

The reason is not that the counts are wrong. It is that the arithmetic is not what a large computation spends its time on. A modern processor performs an arithmetic operation in a fraction of a nanosecond and fetches a word from main memory in something like a hundred of them, so a computation that fetches each of its numbers once per operation is idle for 99% of its life whatever the operation count says.

This essay makes that measurable, on the algorithm this site has drawn since its foundation phase.

The same arithmetic, twice, with M = 144 words of fast memoryThree counters against the matrix size on logarithmic axes. The operation count is one curve, because both orderings perform exactly the same operations — 338,120 of them at n = 80. The words moved between fast and slow memory are two curves a factor of 3.24 apart. The factorisations they produce are identical to the last bit.10²10³10⁴10⁵matrix size ncountoperations, bothwords, unblockedwords, blocked (b = 6)the answer does not move‖PA − LU‖/‖A‖, unblocked2.8·10⁻¹⁶‖PA − LU‖/‖A‖, blocked2.8·10⁻¹⁶difference between them0the dashed curve is both orderings' operation countthe solid pair is what they cost
Fig. 1 Three counters against the matrix size, on logarithmic axes, with 144 words of fast memory. The dashed curve is the operation count — one curve, because both orderings perform exactly the same operations. The two solid curves are the words each moves. The badge prints what both of them returned.

The two orderings

Unblocked is the textbook loop, and it is what matrix.js has done since the site was built. Find the pivot in column k, swap, scale the column below it, then update the whole trailing submatrix with a rank-one product. Repeat n times.

Blocked defers. Factor a panel of b columns — pivoting inside it, updating only its own columns — and then apply the whole panel’s worth of updates to the trailing submatrix at once, as a rank-b product rather than b rank-one products.

The arithmetic is identical. The same numbers are multiplied by the same numbers and added to the same accumulators; what changes is the order the accumulations happen in and, crucially, how many times each entry of the trailing block is visited. The unblocked version touches every trailing entry once per column of elimination. The blocked version touches it once per panel.

That is the entire idea, and stated that way it sounds like it must be a straightforward win.

Three counters, and two of them do not move

The figure carries three and the argument is that only one of them differs.

Operations, compared as integers. At n = 48 both orderings perform exactly 72,568 of them. Not “the same to within a percent” — the same number, and the assertion compares them with === rather than to a tolerance, because they are counts and a tolerance on a count is a way of not noticing that two algorithms are doing different amounts of work.

The pivot sequence. Both choose the same one, which is asserted and is not obvious: a blocked factorisation pivots within the panel, with less of the matrix in view than the unblocked version has. On a general matrix that can pick a different sequence, which is why LAPACK’s dgetrf applies the panel’s row interchanges to the rest of the matrix afterwards. Here it does not, and the assertion says so — because if it did, the residual comparison below would be between two different factorisations and would mean nothing.

And the residual. ‖PA − LU‖/‖A‖ comes out at 4.487946226420872·10⁻¹⁶ from both. Every digit. That is a stronger statement than the assertion needs — it requires agreement to 10⁻¹⁵ — and it is worth printing at full length because it settles the only question a reader could reasonably raise about the comparison. Reassociating floating-point arithmetic is not in general innocent; this site has an essay about exactly that. Here the reassociation happens to fall out bit-identically, and the badge says so rather than the caption.

The words moved: 41,332 against 19,476. A factor of 2.12, on the identical computation.

Gaussian elimination on a 4×4, one step at a timeFour copies of the same matrix: as given, and after each of the three elimination steps. The pivot in use is outlined and the entries reduced to zero are greyed.21-13-3-121-212-443-12as givenrows in the order 1 2 3 443-1201.251.252.502.51.5-30-0.5-0.52after step 1pivot 443-1202.51.5-3000.5400-0.21.4after step 2pivot 2.543-1202.51.5-3000.540003after step 3pivot 0.5‖PA − LU‖/‖A‖0largest multiplier0.75row order 4 3 2 1the pivot is chosen
Fig. 2 The algorithm both orderings are performing, one step at a time. Every entry the rank-one update touches at step k is touched again at step k+1, and with a fast memory smaller than the trailing block none of them is still resident when it is wanted. That is the whole of the unblocked ordering’s traffic.

What “a word moved” means here, and what it does not

There is no timing anywhere in this field and there will not be. A measured runtime on the machine that built this page is a fact about that machine: it depends on the cache sizes, the associativity, the compiler, what else was running, and it cannot be checked against anything. This site’s whole proposition is that every number is measured against something that was not measured, and a duration has nothing to hold it against.

What is counted instead is a model. A fast memory of M words, a slow one holding the matrix, and a count of the times an entry is touched that is not already resident — with M named on every figure, because nothing in the model is optimal until M is named. That is the same discipline algorithms-data-structures.com uses for its external-memory field, and the boundary between the two sites is already drawn: that site owns cost counted in block transfers as a subject, and this one is licensed for blocked and tiled numerical algorithms, where the block size is chosen for a cache and the question is what the reordering does to the answer.

Which is why there is a third counter here that the other site does not have.

The cache is modelled as fully associative with least-recently-used eviction, and that is a choice worth stating rather than burying. Real caches are set-associative and their conflict misses are a large part of why tuned libraries pick the block sizes they do. Modelling that would make every number here depend on an associativity nobody stated. The effect this essay is about — that the order of the same arithmetic changes the traffic by a factor — is present in the idealised model, is the part that generalises, and makes every count here a lower bound on a real machine’s.

Growth factor under partial pivoting to n = 24: the bound, the worst case, and realityGrowth factor against matrix size on a logarithmic vertical axis. The two-to-the-n bound rises as a straight line; Wilkinson's matrix sits exactly on it; random matrices stay near one.0510152025110¹10²10³10⁴10⁵10⁶10⁷10⁸10⁹10¹⁰matrix size ngrowth factor max|u| / max|a|the 2ⁿ⁻¹ boundworst of 30 randommedian randomWilkinson's matrix sits on the bound30 Gaussian matrices per sizeat n = 24: bound 8.4·10⁶, worst 3.6
Fig. 3 What the two orderings do not differ in. Both perform the identical eliminations and both produce the identical growth, so every stability claim the elimination field makes about one holds unchanged for the other — which is what makes the traffic comparison a comparison rather than a trade.

Deferring the update is not the saving

The first version of this file was wrong in an instructive way, and the wrongness is worth keeping in view because it is the mistake the idea invites.

Written the obvious way — defer the updates, then apply them in one nested loop over the trailing block — the blocked ordering moved 13,163 words against the unblocked version’s 13,018 at n = 32 with M = 64. Worse. The operation count was identical, the residual was identical, and the thing the whole exercise was for went the wrong way by 1%.

Deferring the update is not the saving. The saving is arranging the deferred update so that its working set fits. Three things have to be resident at once while the rank-b product runs: the tile of the trailing block being accumulated into, the strip of L beside it and the strip of U above it — about 3b² words. Written as one loop over the whole trailing block, the strips are far longer than that and every inner iteration evicts what the next one is about to read.

Tiling it — iterate over b×b tiles of the trailing block, and accumulate each tile completely before moving on — is a four-line change with no arithmetic in it and takes the same measurement to 19,476 against 41,332.

The relation 3b² ≈ M is where the next essay’s block size comes from, and it arrives here as a consequence of getting the loop right rather than as a tuning parameter. A blocked algorithm whose block does not fit is an unblocked algorithm with extra bookkeeping.

Words moved against the block size, n = 48, M = 100A U-shaped curve on logarithmic axes. Too small a block refactors the panel too often; too large a block does not fit in the fast memory and the tiled update evicts what it is about to read. The minimum is at b = 8, which is √M − 2. The three-line count says √(M/3) = 5, which has the right scaling and the wrong constant.110¹block size bwords movedthe count: √(M/3) = 5measured best: b = 8words movedat the best block1.6·10⁴at b = 13.9·10⁴at b = 243.9·10⁴derived from M with no measurement, and scannedthe two agree
Fig. 4 The block size the curve above was drawn at, and what happens either side of it. The left arm is a gradual waste and the right is a cliff — one column past where the working set fits, the traffic triples. The next essay is about where that boundary is.

Why the flop count ever worked

It is worth being fair to the count, because it was not always wrong and the reason it stopped being right is a fact about hardware rather than about algorithms.

On a machine where a memory access and an arithmetic operation cost about the same — which is what the machines these algorithms were analysed on were like — a count of operations is a count of everything, because the accesses are proportional to the operations and the constant is one. The n³/3 is then a genuine prediction.

What changed is that arithmetic got faster by about five orders of magnitude and memory latency by about two. The ratio between them is now roughly a hundred, and a computation that does one operation per fetched word runs at one per cent of the machine’s arithmetic rate. Everything about the design of numerical libraries since the 1980s follows from that number: the BLAS levels are a classification by operations per word, and level 3 exists because a matrix–matrix product does n³ operations on n² words and level 1 does n on n.

The blocked elimination above is that classification applied to elimination. The rank-one update is a level-2 operation, n² operations on n² words; the rank-b update is level 3, n²b on n². The whole of the difference in the figure is that ratio.

What this does not claim

Not that blocking is a different complexity class. It is not, and the next essay measures the exponents: both orderings’ traffic grows like n³ — fitted at 3.182 and 3.185 — and blocking buys a constant of about 2.1 at every size drawn. The word “communication-avoiding” suggests something stronger than that, and for this algorithm at these sizes it is not stronger than that.

And not that the model is a machine. The 2.12 is the factor in an idealised memory with M = 144 words. On a real processor with three levels of cache, prefetching, and a compiler that unrolls the inner loop, the factor between a naive and a tuned LU is nearer ten, and almost none of the extra is in this model. What the model gets right is the sign and the mechanism, which is what an essay can be about.

Wilkinson's 7×7 matrix and its upper triangular factorThe matrix on the left has ones on the diagonal, minus ones below it and a column of ones at the right. On the right, its U factor, whose last column doubles down the rows to sixty-four.1·····1-11····1-1-11···1-1-1-11··1-1-1-1-11·1-1-1-1-1-111-1-1-1-1-1-11A1·····1·1····2··1···4···1··8····1·16·····132······64U‖PA − LU‖/‖A‖0growth factor64the 2ⁿ⁻¹ bound64Row interchanges performed: 0.Partial pivoting had nothing to choose.every entry is 0, 1 or −1the bound is attained here
Fig. 5 The elimination field’s own worst case, which both orderings inherit unchanged. Reassociating an elimination does not touch its stability, so the matrix that attains the 2^(n−1) growth bound attains it either way — which is what makes the traffic the only thing to compare.

The three counters are three different kinds of claim

Worth separating, because the figure puts them on one axis and they are not one kind of thing.

The operation count is a combinatorial fact. It is decided by the algorithm before any machine is chosen, it is an integer, and it is the same integer on every machine that has ever existed. That is why it is compared with ===: a tolerance on it would be a tolerance on a fact about a loop nest.

The residual is a numerical fact. It depends on the arithmetic, on the order of the accumulations, and on the matrix. It has no reason to be identical between the two orderings and it is — which is a measurement rather than a theorem, and the badge prints all seventeen digits of it for that reason. Reassociating floating-point additions is not innocent in general; this site has an essay whose entire subject is a sum whose value depends on the order it is taken in. Here the reassociation happens to produce the identical rounding at every step, because the blocked update accumulates the same products into the same accumulator in the same sequence — it merely defers when that sequence runs.

The word count is a model. It depends on M, on the eviction policy, on the layout, and on the loop order — and it is the only one of the three that a reader could reasonably argue with. Which is why M appears in the title of the figure rather than in a footnote.

A field that had only the first two would be the elimination field. A field that had only the third would be algorithms-data-structures.com’s. Having all three on one figure is what makes the sentence the same arithmetic at a different price a measurement rather than a slogan.

What a reader should take from the factor of 2.12

Not that a blocked LU is twice as fast, which is a claim about a machine this field does not make.

The transferable statement is about which quantity to instrument. A reader who profiles an elimination and finds it slower than n³/3 suggests has two hypotheses available: the operation count is wrong, or something other than operations is being paid for. The first is checkable in an afternoon and is almost always wrong; the second requires a different instrument, and the instrument is a count of memory traffic rather than a count of arithmetic.

This site has been separating the algorithm’s fault from the problem’s fault since its foundation phase, and the identity underneath it — forward error ⪅ condition number × backward error — exists because a wrong answer has two possible authors and they are separately measurable. The cost field is the same move applied to time rather than to accuracy. A slow computation has two possible authors, and the flop count measures exactly one of them.

Where the traffic actually goes

The counters say the two orderings differ by 2.12. Reading why off the algorithm rather than off the number takes one more count, and it is the count that makes the block size predictable at all.

The unblocked update at step k touches every entry of an (n−k)² trailing block, and it does that for every k. Summed, that is

Σₖ (n − k)²  ≈  n³/3

entries touched, and — this is the part that matters — none of them are reused between steps. Step k reads a trailing entry, updates it, moves on, and step k+1 reads it again after having read everything else in between. With M much smaller than the trailing block, everything is evicted before it is wanted, so the touches are misses and the traffic is n³/3.

The blocked update touches the same entries, in tiles, b columns of elimination at a time. Each trailing entry is now read once and written once per panel rather than per column, so the count falls by a factor of b — provided the tile’s working set stays resident, which is the whole of the next essay.

n³/(3b) against n³/3. At b = 6 that predicts a factor of six, and the measurement is 2.12.

The gap between six and 2.12 is not a discrepancy to be explained away; it is the panel factorisation, which is unblocked by construction and is not accelerated at all. A blocked algorithm blocks the trailing update and leaves the panel alone, so the saving is bounded by how much of the work is in the trailing update — which is most of it asymptotically and rather less of it at n = 48. That is also why the ratio in the figure creeps up slightly with size.

Words moved against the block size, n = 64, M = 100A U-shaped curve on logarithmic axes. Too small a block refactors the panel too often; too large a block does not fit in the fast memory and the tiled update evicts what it is about to read. The minimum is at b = 8, which is √M − 2. The three-line count says √(M/3) = 5, which has the right scaling and the wrong constant.110¹10⁵block size bwords movedthe count: √(M/3) = 5measured best: b = 8words movedat the best block3.8·10⁴at b = 11.4·10⁵at b = 249.4·10⁴derived from M with no measurement, and scannedthe two agree
Fig. 6 What happens either side of a block size, which is the next essay’s subject. A gradual slope on the left and a cliff on the right: one column past where the working set fits, the traffic triples.

The level the operation lives at

There is a vocabulary for this and it is worth having, because it turns the essay’s measurement into a rule of thumb that transfers.

The BLAS — the interface every numerical library sits on — classifies its operations into three levels, and the classification is not by what they compute. It is by operations per word:

level example words touched operations ratio
1 y ← αx + y 2n 2n 1
2 y ← Ax + y 2n² 2
3 C ← AB + C 3n² 2n³ 2n/3

Levels 1 and 2 have a constant ratio. However large the problem, they perform a bounded number of operations per word fetched, so on a machine where a fetch costs a hundred operations they run at a few per cent of the arithmetic rate and there is nothing any implementation can do about it.

Level 3 is the only one whose ratio grows with the problem, and that is the entire reason it is a level. A matrix–matrix product touches 3n² words and performs 2n³ operations, so the arithmetic per word is proportional to n, and a large enough product spends as little of its life waiting as one likes.

The unblocked elimination’s inner loop is a rank-one update: a level-2 operation, ratio 2. The blocked one’s is a rank-b update: level 3, ratio proportional to b. The 2.12 in the figure is that substitution and nothing else, and it is why “cast the algorithm in terms of level-3 operations” is the single design rule that all of LAPACK is organised around.

Which also says where the rule stops. An algorithm with no level-3 formulation available — a sparse triangular solve, a Krylov iteration whose only operation is a matrix–vector product — cannot be blocked into one, and its cost genuinely is what the naive count says. That is not a failure of effort; it is a property of the computation.

The same arithmetic, twice, with M = 400 words of fast memoryThree counters against the matrix size on logarithmic axes. The operation count is one curve, because both orderings perform exactly the same operations — 338,120 of them at n = 80. The words moved between fast and slow memory are two curves a factor of 3.41 apart. The factorisations they produce are identical to the last bit.10²10⁴10⁵matrix size ncountoperations, bothwords, unblockedwords, blocked (b = 11)the answer does not move‖PA − LU‖/‖A‖, unblocked2.8·10⁻¹⁶‖PA − LU‖/‖A‖, blocked2.8·10⁻¹⁶difference between them0the dashed curve is both orderings' operation countthe solid pair is what they cost
Fig. 7 The same three counters with a fast memory nearly three times larger. Both curves move down, the blocked one further, and the operation count does not move at all — because it never contained M.

What is left

The block size, which is the next essay: the curve above is drawn at one b and the trade behind it has an interior optimum that the model predicts from M alone.

The other direction of reassociation, which is the third essay in this field. Blocking reorders an elimination and gets the same factorisation bit for bit. A tall-skinny QR reorders a reduction and gets a different one — a different sequence of roundings, the same backward stability — and the reason it is safe is a property this site has been measuring since its foundation phase.

The permutation this essay avoided. The matrices here are diagonally dominant by construction, so both orderings pick the identity permutation and the comparison is between two factorisations of the same matrix. On a general matrix a blocked factorisation can and does choose a different pivot sequence, and then the two residuals are of two different objects and the bit-identical agreement above is not available. What that costs in stability is a real question with a literature and it is not measured here.

What links here

Computed from the collection, not written here: the essays that point at this one.

Reads more easily once this is understood

Essays that name this one as worth reading first.

Named objects

A flat tag is an object no other essay names yet.

Blocked algorithmCacheData movementFlop countLoop orderLU factorisationMemory hierarchyResidual