Where the flop count stopped predicting the time

The block size a recursion still has

A recursive elimination is sold as having no block size, and every real one switches to plain loops below some width. Swept over that width, the traffic is a staircase with its steps at the halvings of n, and its cliff sits where the blocked elimination's does — the first panel wider than √M − 2 moves 1.53 to 4.47 times the words, on six memories of six. On three caches the innermost decides, and a third cache costs every tuned block up to 14 per cent and the recursion nothing.

Worth reading first: The same arithmetic at a different price · The recursion that was never told the memory.

The recursion that was never told the memory measured a recursive LU factorisation against the best blocked elimination a scan could tune, on a counted memory, and found it within 10 to 28 per cent at six memories of eight and ahead at the other two, without reading the memory’s size. Its recursion halved all the way down: the only base case of the whole factorisation was a panel of one column, and the triangular solves and the updates split until they were single multiply-adds.

That essay ended on a caveat it did not measure. No recursive code halves to one entry. The overhead of a call — entering a function, working out which half is which, returning — is free on a counted memory and not free on a processor, and so every implementation switches to plain loops below some width. The caveat said that the switch “is a block size again, chosen for the processor’s arithmetic rather than its cache”. The measurement here is whether the second half of that sentence is true. It is not: the switch is chosen for arithmetic and it lands on the cache, with the cliff and the rule the blocked elimination has.

The figure at the head of the page is the whole finding on one memory. Every other figure asks where it comes from and whether it survives a machine with more than one cache.

A parameter the recursion rounds

A base case of b columns means: when a panel is at most b columns wide, factor it by the ordinary right-looking loop — search each column for its pivot, swap whole rows, scale, update the rest of the panel — and when a triangular solve or an update has every dimension at most b, do it by three nested loops. Wider problems split in half as before. The panel’s loop updates only the panel’s own columns, and everything to its right is left to the recursive solve and update exactly as at a base of one, so each column is still searched only after every update from its left has reached it. The pivots are therefore the unblocked elimination’s, and they are checked rather than argued: at every base case measured the permutation is identical, the operation count is the same 585,200 multiply-adds and divisions as an integer, and the residual is 2.9·10⁻¹⁶.

What changes is the width of the panels the recursion actually factors, and that is not b. A matrix of 96 columns halves to 48, 24, 12, 6, 3, and then to panels of 1 and 2. A base case stops the halving at the first width that fits it, so every base from 3 to 5 produces panels of 3, every base from 6 to 11 panels of 6, and every base from 12 to 23 panels of 12. The recursion rounds its one parameter to a halving of n. Bases that round to the same panels move exactly the same number of words — not approximately; the counts are integers and they are equal — and the curve in the figure at the head is a staircase with its risers at 3, 6, 12 and 24.

That rounding is a property worth having. A base case set anywhere between 6 and 11 is the same program as far as the memory can tell, so a library that gets its base case wrong by a factor of less than two has not got it wrong at all.

Calls made by the recursive elimination against its base case, n = 96The number of recursive calls — panel factorisations, triangular solves and updates — made by the recursive elimination of a 96-by-96 matrix, against its base case, on logarithmic axes. With a base of one it makes 581,542. Panels of 3 make 23,562, 25 times fewer; panels of 6 make 2,932, 198 times fewer; panels of 12 make 356, 1,634 times fewer; panels of 24 make 42, 13,846 times fewer. The count depends on the panel widths alone and not on any memory.110¹10²10³10⁴10⁵10⁶base case, columnsrecursive calls3 wide: 23,5626 wide: 2,93212 wide: 35624 wide: 42a base of one: 581,542 callsthe overhead a real machine pays and the model does not
Fig. 1 Calls made by the recursion against its base case. A base of one makes 581,542; panels of 3 make 23,562, panels of 6 make 2,932, panels of 12 make 356 and panels of 24 make 42 — thirteen thousand times fewer than a base of one. The count depends on the panel widths and on nothing about the memory.

What the switch buys

The reason to switch is in the call count, and the call count is enormous. Halving to single entries, the recursion makes 581,542 calls on a 96-by-96 matrix, nearly one call for every one of its 585,200 operations, since most of the calls end in a single multiply-add. Panels of 3 cut that to 23,562, twenty-five times fewer. Panels of 6 cut it to 2,932, two hundred times fewer, and panels of 12 to 356, sixteen hundred times fewer.

None of that appears in the words moved, because the model counts words and not calls. It is the one cost the model sets aside, and it is real: each of those calls is an entry and a return and a handful of index calculations, which on hardware can outweigh a single multiply-add many times over. So the pressure from the processor runs one way, towards a large base case, and it is steep — every doubling of the panel width divides the calls by about eight, 23,562 to 2,932 to 356 to 42, which is what an update whose leaves number (n/b)³ should do.

If the switch were only about overhead, the right base case would be as large as the recursion’s advantage allows, which is to say as large as possible. The next figure is why it is not.

And what it costs: a cliff at M2\sqrt{M} - 2

On 144 words of fast memory the pure recursion moves 126,742 words, 1.16 times the best blocked elimination’s 109,202 at a block of 10. A base case up to 10 columns — panels of 3 or 6 — moves at most 130,739, three per cent more. A base case of 12 moves 323,422: 2.55 times the pure recursion, and 2.96 times the tuned block. One halving separates a base case that costs three per cent from one that costs a factor of two and a half.

The edge sits between 10 and 12, and the number to set beside it is the one a block size is a property of the machine measured for the blocked elimination: its best block is exactly M2\sqrt{M} - 2, which at 144 words is 10, and a block one column wider than the best sits on the far side of a cliff. The recursion’s panels meet the same cliff at the same place.

Words moved by the recursion against its base case, a fast memory of 64 words, n = 96The recursive elimination of a 96-by-96 matrix with its base case swept from 1 to 24 columns, on a fast memory of 64 words, on a logarithmic base axis; the curve is the words crossing the boundary divided by the best blocked elimination's words there. A base case is rounded to a panel width a halving of 96 reaches — 1, 2, 3, 6, 12, 24 — so the traffic changes only at those widths. With panels no wider than √M − 2 = 6 for the 64-word memory, the recursion moves at most 1.01 times its words at a base of one; panels 12 wide move 1.71 times as many. The operation count, the pivots and the residual are the same at every base.110¹123base case, columnswords ÷ the best block's√M − 2 = 6M = 64M = 64base of one1.3panels ≤ 6, worst1.3panels of 122.2the base case is a block size, rounded to a halvingand it has the block's cliff at √M − 2
Fig. 2 The same sweep on 64 words, where M2\sqrt{M} - 2 = 6. Panels of 3 cost the recursion 1.01 times its words at a base of one and panels of 6 cost 0.77 times — fewer than the pure recursion and fewer than the best tuned block. Panels of 12 move 1.71 times as many.

Across six memories from 36 to 576 words the rule holds without exception. Measured against the pure recursion on the same memory, every base case whose panels are no wider than M2\sqrt{M} - 2 moves at most 1.16 times the words, and the first panel width past M2\sqrt{M} - 2 moves 1.53, 1.71, 2.22, 3.18, 4.00 and 4.47 times as many on memories of 36, 64, 100, 256, 400 and 576 words. The three-line model the earlier essay started from, M/3\sqrt{M/3}, would put the edge below panels that measure safe on 64, 100 and 256 words — at 4.6 against a safe panel of 6, 5.8 against 6, and 9.2 against 12 — which is the same factor by which that model was found to be low for the block itself.

The 64-word memory has a surprise in it. There M2\sqrt{M} - 2 is exactly 6, a halving of 96, and a base case of 6 lands the recursion’s panels on the edge from the safe side. The traffic falls to 0.77 times the pure recursion’s — below the best tuned block’s 158,119 words, at about 155,700. A recursion with its base case set by the blocked rule beats the blocked elimination tuned by that rule, because it has the block’s efficiency at the leaves and the recursion’s safety above them.

Words moved by the recursion against its base case, a fast memory of 576 words, n = 96The recursive elimination of a 96-by-96 matrix with its base case swept from 1 to 24 columns, on a fast memory of 576 words, on a logarithmic base axis; the curve is the words crossing the boundary divided by the best blocked elimination's words there. A base case is rounded to a panel width a halving of 96 reaches — 1, 2, 3, 6, 12, 24 — so the traffic changes only at those widths. With panels no wider than √M − 2 = 22 for the 576-word memory, the recursion moves at most 1.16 times its words at a base of one; panels 24 wide move 4.47 times as many. The operation count, the pivots and the residual are the same at every base.110¹12345base case, columnswords ÷ the best block's√M − 2 = 22M = 576M = 576base of one0.94panels ≤ 22, worst1.1panels of 244.2the base case is a block size, rounded to a halvingand it has the block's cliff at √M − 2
Fig. 3 The sweep on 576 words, where M2\sqrt{M} - 2 = 22. Panels of 6 cost nothing; panels of 12, still on the safe side, cost 1.16 times the pure recursion; panels of 24 move 4.47 times as many.

The 576-word memory has the opposite lesson. The pure recursion was already ahead of the tuned block there, at 0.94, and panels of 12 — safely under the edge of 22 — cost it 16 per cent, which spends that lead and more. Panels of 24 move 4.47 times as many words. So “at most 1.16 on the safe side” is a real cost at some memories rather than a rounding: the safe side of the cliff is not free, it is only not catastrophic.

Why the blocked rule: a leaf is a block

The rule transfers because the object is the same. Below its base case the recursion is the blocked elimination’s kernel: a panel of w columns factored in place, a w-by-w triangular solve done by loops, a w-by-w-by-w product done by loops. Its working set is a few tiles of width w plus the column being searched, and whether that fits the fast memory is the question the scan over block sizes answered, with the answer M2\sqrt{M} - 2. Above the base case, the recursion never has more than one leaf’s worth of data in its hands at once, so nothing it does above the leaves can rescue a leaf that does not fit.

That is also why the rounding matters in both directions. It protects a base case from small errors, and it cannot protect it from a large one: the step from 6 to 12 is a doubling, and on a memory whose edge sits between them the doubling carries the recursion over the cliff in one move, with no intermediate width to warn anyone. The same arithmetic at a different price found that every ordering of these operations is the same factorisation to the last bit; this is the sharpest case of it, since two base cases that differ by one column can be the same program or a program moving three times the words, and the arithmetic cannot tell which.

On three caches, the innermost decides

The recursion’s case for having no block size was strongest on a machine with several caches, where no single block can be right for all of them. A base case is a single block. So the question on such a machine is which cache it has to be right for.

Words moved by the recursion against its base case, caches of 36, 256 and 1,024 words, n = 96The recursive elimination of a 96-by-96 matrix with its base case swept from 1 to 24 columns, on caches of 36, 256 and 1,024 words, on a logarithmic base axis; each curve is the words crossing one boundary divided by the best blocked elimination's words there. A base case is rounded to a panel width a halving of 96 reaches — 1, 2, 3, 6, 12, 24 — so the traffic changes only at those widths. With panels no wider than √M − 2 = 4 for the 36-word inner cache, the recursion moves at most 1.00 times its words at a base of one; panels 6 wide move 1.53 times as many at the inner boundary and 1.05 and 1.00 at the others. The operation count, the pivots and the residual are the same at every base.110¹1234base case, columnswords ÷ the best block's√M − 2 = 4inner, 36middle, 256outer, 1,024inner cache, 36base of one1.2panels ≤ 4, worst1.2panels of 61.9the base case is a block size, rounded to a halvingand it has the block's cliff at √M − 2
Fig. 4 The base-case sweep on caches of 36, 256 and 1,024 words, each curve over the best block at its boundary. Panels of 3 cost nothing at any boundary. Panels of 6 move 1.53 times the pure recursion’s words at the inner boundary, and 1.05 and 1.00 times at the other two.

On caches of 36, 256 and 1,024 words, panels of 3 leave all three boundaries where the pure recursion put them: 1.211, 1.099 and 0.928 times each boundary’s best block, against 1.215, 1.097 and 0.928. Panels of 6 go over the inner cache’s edge — 362\sqrt{36} - 2 is 4 — and the inner boundary jumps to 1.861 while the middle moves to 1.153 and the outer does not move. Panels of 12 are safe for the middle cache and better than the pure recursion there, 1.017 against 1.097, while the inner boundary stays at 1.663. Panels of 24 go over the middle cache’s edge as well, and every boundary is at more than 1.3.

So the base case, like a block, can be right for one cache at a time, and the only choice that costs nothing anywhere is the one sized for the innermost. That is the smallest cache, which means the smallest panels and the most calls: on this machine, panels of 3 and 23,562 calls rather than panels of 12 and 356. The trade the recursion was supposed to escape comes back one level down — the processor wants wide leaves, the innermost cache wants narrow ones — and it is decided by the innermost cache’s M2\sqrt{M} - 2 alone.

A third cache, measured against the blocks

The earlier essay predicted that the recursion’s advantage over tuned blocks should grow with the number of caches, since every added level is another memory a single block can be wrong about. The prediction is measurable directly: take a machine with two caches, add a middle one between them, and see what changes.

Words at three cache boundaries against the block size, caches of 36, 256 and 1,024 words, n = 96Three curves on a logarithmic block axis, each the words moved across one cache boundary divided by that boundary's own minimum. The inner cache of 36 words is best served by b = 3, which costs 2.16 times the best at its worst boundary; the middle cache of 256 words is best served by b = 14, which costs 1.61 times the best at its worst boundary; the outer cache of 1,024 words is best served by b = 16, which costs 3.34 times the best at its worst boundary. The block that minimises the worst of the three is b = 14, at 1.61. The recursive elimination, drawn as three dots to the right because it has no block size, is at 1.21, 1.10, 0.93.110¹10²123456block size bwords ÷ that boundary's bestinner, 36 wordsmiddle, 256 wordsouter, 1,024 wordsinner 1.21×middle 1.10×outer 0.93×the recursionone block for three caches: b = 14, worst 1.61×recursion 1.21×, told no size
Fig. 5 Words at three boundaries against the block size, caches of 36, 256 and 1,024 words. The inner cache wants b = 3, the middle b = 14 and the outer b = 16; the block tuned to the outer cache costs 3.34 times the best at its worst boundary. The block that minimises the worst of the three is 14, at 1.61. The recursion is at 1.21, 1.10 and 0.93.

On caches of 36, 256 and 1,024 words the three boundaries want blocks of 3, 14 and 16. The block tuned to the outer cache alone costs 3.34 times the best at its worst boundary; the one tuned to the inner costs 2.16; the one tuned to the middle, 1.61. The best compromise a scan told all three sizes can find is the middle cache’s own block of 14, at 1.61 at its worst boundary. The recursion, told none of them, is at 1.21, 1.10 and 0.93. The compromise is better at the middle boundary, where it is that cache’s own best and so exactly 1; the recursion is better at its worst, 1.21 against 1.61, which is the comparison a machine with all three caches actually pays.

Words at three cache boundaries against the block size, caches of 16, 64 and 400 words, n = 96Three curves on a logarithmic block axis, each the words moved across one cache boundary divided by that boundary's own minimum. The inner cache of 16 words is best served by b = 2, which costs 2.02 times the best at its worst boundary; the middle cache of 64 words is best served by b = 6, which costs 1.34 times the best at its worst boundary; the outer cache of 400 words is best served by b = 10, which costs 2.13 times the best at its worst boundary. The block that minimises the worst of the three is b = 6, at 1.34. The recursive elimination, drawn as three dots to the right because it has no block size, is at 1.35, 1.28, 0.96.110¹10²1234block size bwords ÷ that boundary's bestinner, 16 wordsmiddle, 64 wordsouter, 400 wordsinner 1.35×middle 1.28×outer 0.96×the recursionone block for three caches: b = 6, worst 1.34×recursion 1.35×, told no size
Fig. 6 Caches of 16, 64 and 400 words. The inner cache wants b = 2, the middle b = 6 and the outer b = 10; the compromise is 6, at 1.34. The recursion is at 1.35, 1.28 and 0.96 — level with the compromise, and limited by the smallest cache.

The machine that tests the recursion hardest is the one with the smallest inner cache. On 16, 64 and 400 words the best blocks are 2, 6 and 10, the compromise is 6 at 1.34, and the recursion is at 1.35, 1.28 and 0.96 — within one per cent of the compromise, and limited by the inner boundary, where its factor-of-two quantisation is a factor of two on a very small size.

The worst boundary's ratio for the best single block with two caches and with three, beside the recursion, n = 96For five machines, three bars each: the worst ratio to a boundary's best of the block that minimises it with an inner and an outer cache, the same with a middle cache added, and the recursion's worst ratio with all three. On caches of 16, 100 and 576 words the best block goes from 1.340 to 1.340 and the recursion is at 1.348; on caches of 25, 144 and 900 words the best block goes from 1.168 to 1.188 and the recursion is at 1.161; on caches of 36, 256 and 1,024 words the best block goes from 1.578 to 1.610 and the recursion is at 1.215; on caches of 16, 64 and 400 words the best block goes from 1.242 to 1.341 and the recursion is at 1.348; on caches of 36, 196 and 784 words the best block goes from 1.422 to 1.622 and the recursion is at 1.215. The middle cache makes the best block up to 14% worse.n = 96 · each bar the worst of a machine's boundary ratios · shorter is better16 and 576 — best block1.340with 100 between — best block1.340with three caches — the recursion1.34825 and 900 — best block1.168with 144 between — best block1.188with three caches — the recursion1.16136 and 1024 — best block1.578with 256 between — best block1.610with three caches — the recursion1.21516 and 400 — best block1.242with 64 between — best block1.341with three caches — the recursion1.34836 and 784 — best block1.422with 196 between — best block1.622with three caches — the recursion1.215a middle cache is one more memory a block can be wrong aboutthe recursion was told none of them
Fig. 7 Five machines, with and without a middle cache: the best single block’s worst ratio with two caches, the same with the middle cache added, and the recursion’s worst ratio with all three. The middle cache makes the best block up to 14 per cent worse and leaves the recursion within one per cent of it on every machine.

Across five machines, adding the middle cache moves the best compromise from 1.340 to 1.340, 1.168 to 1.188, 1.578 to 1.610, 1.242 to 1.341 and 1.422 to 1.622 — never better, since a block now has one more boundary to be measured at, and up to 14 per cent worse. The block tuned to the outer cache alone suffers far more: its worst ratio goes from 2.00, 1.94, 1.58, 2.02 and 1.61 to 2.57, 2.83, 3.34, 2.13 and 3.23, because it is now also measured at a middle cache it was never tuned for.

The recursion’s worst ratio with all three caches is 1.348, 1.161, 1.215, 1.348 and 1.215. It is never more than one per cent behind the three-cache compromise and ahead of it on three machines of five. The prediction was right in direction and modest in size, and the mechanism is worth stating precisely: the recursion’s advantage grows because the blocks get worse, not because the recursion gets better. On four machines of five its worst boundary is the innermost — 1.348 whenever that cache is 16 words, 1.215 whenever it is 36 — and the middle cache leaves it exactly where it was. On the fifth, with an inner cache of 25 words, the middle cache becomes the recursion’s worst boundary too, moving it from 0.985 to 1.161, and the compromise still moves further. On the 16-and-400-word machine the effect is the difference between losing to the compromise by 8.5 per cent with two caches, 1.348 against 1.242, and drawing with it, 1.348 against 1.341, with three.

What a recursive code should do with its base case

The practical rule is short and it comes from two measurements that agree. Set the base case by the innermost cache’s M2\sqrt{M} - 2, and let the recursion round it. Below that width the switch removes most of the calls — twenty-five times fewer at panels of 3, two hundred at panels of 6 — for at most sixteen per cent in words and usually nothing. At one halving past it the recursion moves one and a half to four and a half times the words, on every memory measured, and on a machine with several caches it does so at the innermost boundary first.

The recursion’s claim to need no knowledge of the machine survives in a weaker and more accurate form. It needs one number, the size of the smallest cache, and only to within a factor of two, because the halving rounds everything in between. Every other memory on the machine it serves without being told — which is the property the order the products are taken in found for contractions and the order decides the memory for sparse fill, applied to the traffic of one dense factorisation: the cost is decided by the ordering, and the ordering here needs one fact about the hardware rather than one per cache.

It also says what the model’s silence about calls was hiding. On the counted memory a base case of one looked like the purest form of the idea, and in the words it is: nothing below M2\sqrt{M} - 2 moves fewer. But the counted memory is exactly the instrument that cannot see why no one ships it, and a model that costs everything except the thing a design is responding to will recommend the design no one uses. The calls figure is on the page for that reason.

Still open: recursion for QR, and matrices that do not halve evenly

The matrix measured here has 96 columns, which is 3 times a power of two, so every halving is exact and the panel widths form a clean sequence of 3, 6, 12, 24. A matrix of 100 columns halves to 50, 25, 12 and 13, 6 and 7, 3 and 4 — two widths at every level — and its staircase has intermediate steps, one of which may sit on the far side of an edge the other does not reach. Whether that blurs the cliff or doubles it is measurable with the same sweep and is not measured here.

The larger open question is the one the recursion’s first measurement named. Householder QR has a recursive formulation, with the same halving of columns and a compact representation of the reflections accumulated at each level, and a blocked formulation that is what libraries ship. Doing it twice and a reduction that changes the order measured QR’s traffic between processors; its traffic between caches, and whether its base case obeys the same M2\sqrt{M} - 2 when the kernel is a block reflector rather than an elimination, is the next measurement to make. And tall matrices bring back the panel, where the swap that is not optional searches a whole column at every step — the one part of an elimination neither blocking nor recursion has yet been allowed to change.

Shares its objects with

Essays that name at least two of the same things, and that neither author linked.

Named objects

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

Blocked algorithmCache obliviousData movementFlop countLU factorisationMemory hierarchyPartial pivotingRecursive factorisation