What the machine does

Eight cells at once

The anti-diagonal fill order exists because its cells do not depend on one another, and every table filled here has been walked one cell at a time anyway. Computed eight at a time, a step touches 5.71 cache lines on the layout that stores the table by diagonals and 10.87 on the one that stores it by rows — and per cell the first keeps falling to 0.42 while the second stops at 1.27. The prediction that a diagonal step would touch three or four lines was wrong, and line-aligning each diagonal only takes it to 4.94.

The table stored the way it is filled measured what happens when an edit-distance table is stored by anti-diagonals rather than by rows. The anti-diagonal fill order keeps its 513 rounds and its miss rate falls from 31.1% to 8.7%; row order, on the same layout, becomes the order that strides.

Every fill on that page is a serial walk. The reason to want the anti-diagonal order at all is that the cells of one anti-diagonal depend on nothing but earlier diagonals, so they can all be computed at once — the order that has a depth is the argument that this is what a fill order’s depth means — and nothing there used it. This page computes a diagonal in steps of several cells and counts what a step costs.

The unit has to change with the question. A serial fill is measured in misses per cell, because the cells arrive one at a time and the cache decides. A step of eight cells fetches its operands together, so what matters is how many distinct lines it has to have in hand at once, and how that number grows as the step widens. A column computed in machine words is the same substitution made at the level below — a column of the same table computed as bit operations on whole words rather than as a loop over cells — and it changed the unit in the same way, from cells to words.

That is also why this page does not report a speed-up. Nothing here is timed, and a step of eight cells is one instruction on a machine with a vector unit and eight on a machine without; what a layout decides is the memory traffic a step needs, and the rest belongs to a machine.

What a step touches

A step of eight cells touches 5.71 lines stored by diagonals and 10.87 stored by rows — not the three or four a run of eight adjacent cells would suggestDistinct cache lines one step touches, against the step's width, on logarithmic axes, for a 512 × 512 table under three layouts of the same cells. Each cell reads its three neighbours and writes itself; the step is a run of cells along one anti-diagonal, which have no dependence on one another. stored by diagonals: 3.12 at a step of 1, 3.50 at a step of 2, 4.24 at a step of 4, 5.71 at a step of 8, 8.58 at a step of 16, 14.05 at a step of 32, 24.07 at a step of 64. by diagonals, each line-aligned: 3.12 at a step of 1, 3.49 at a step of 2, 3.98 at a step of 4, 4.94 at a step of 8, 7.80 at a step of 16, 13.26 at a step of 32, 23.27 at a step of 64. stored by rows: 2.25 at a step of 1, 3.50 at a step of 2, 5.97 at a step of 4, 10.87 at a step of 8, 20.43 at a step of 16, 38.72 at a step of 32, 72.23 at a step of 64.124816326410step width, cellscache lines a stepstored by diagonalsby diagonals, each line-alignedstored by rows512 × 512, lines of 8 cellsone step of an anti-diagonal
Fig. 1 Distinct cache lines one step touches, against the step’s width in cells, on logarithmic axes, for a 512 × 512 table under three layouts of the same cells; a line holds eight cells. Stored by diagonals: 3.12 lines for a step of one cell, 4.24 for four, 5.71 for eight, 8.58 for sixteen, 24.07 for sixty-four. Stored by rows: 2.25, 5.97, 10.87, 20.43 and 72.23. By diagonals with each diagonal aligned to a line: 3.12, 3.98, 4.94, 7.80 and 23.27.

At a step of eight cells the two layouts differ by a factor of 1.9 — and both numbers are worth reading against the prediction that preceded them.

The row layout’s was right. A step of eight cells on eight consecutive anti-diagonal positions touches eight different rows, and a row is 513 cells, so the eight cells are in eight different lines and their neighbours above are in eight more. The prediction was “at least nine”; it is 10.87.

The diagonal layout’s was wrong. The prediction was three or four, on the reasoning that a step’s three read runs and its write run are each a contiguous stretch of eight cells and a line holds eight cells. The measurement is 5.71. A contiguous run of eight cells spans one line only when it starts at a multiple of eight, and there is nothing making it do so: each anti-diagonal begins wherever the previous one ended, and a step begins wherever the diagonal’s first cell puts it. A run of eight cells at an arbitrary offset spans two lines seven times in eight.

The alignment, which does not fix it

If the residual is misalignment, aligning should remove it.

The third series on that plate is the same layout with every anti-diagonal padded to start at a multiple of the line size — at most seven wasted cells a diagonal, which on a 512 × 512 table is about 7,000 cells of waste against 263,000 of table, under three per cent. It takes a step of eight from 5.71 lines to 4.94.

That is a real improvement and it is not the predicted three or four. Aligning each diagonal’s start does not align each step: the first step of a diagonal begins at the diagonal’s first cell, and the cells of a diagonal that has been cut short by the table’s edge do not begin at index zero. So about a third of the steps still straddle. Getting to four would need the step boundaries aligned as well, which means padding each diagonal at both ends and accepting a step that computes cells the table does not have.

The number to carry is not 5.71 or 4.94 but the gap between them and the prediction: a run of cells is not a line, and the difference is where the run starts. That is the same kind of finding as the aligned bucket of eight entries, which was one cache line only because it was aligned to one.

Per cell, which is what a wider step is bought for

A step of sixty-four cells touching twenty-four lines sounds worse than a step of eight touching six. Divided by the cells, it is not.

Per cell the row layout stops improving at 1.27 lines and the diagonal one keeps falling to 0.42 — and at one cell a step the row layout is the cheaper of the twoDistinct cache lines one step touches per cell, against the step's width, on logarithmic axes, for a 512 × 512 table under three layouts of the same cells. Each cell reads its three neighbours and writes itself; the step is a run of cells along one anti-diagonal, which have no dependence on one another. stored by diagonals: 3.125 at a step of 1, 1.753 at a step of 2, 1.067 at a step of 4, 0.723 at a step of 8, 0.552 at a step of 16, 0.466 at a step of 32, 0.422 at a step of 64. by diagonals, each line-aligned: 3.123 at a step of 1, 1.749 at a step of 2, 1.001 at a step of 4, 0.626 at a step of 8, 0.502 at a step of 16, 0.440 at a step of 32, 0.408 at a step of 64. stored by rows: 2.250 at a step of 1, 1.751 at a step of 2, 1.501 at a step of 4, 1.377 at a step of 8, 1.314 at a step of 16, 1.283 at a step of 32, 1.268 at a step of 64.12481632641step width, cellscache lines a cellstored by diagonalsby diagonals, each line-alignedstored by rows512 × 512, lines of 8 cellslower is better; the floor is what a layout allows
Fig. 2 The same measurement divided by the cells in a step. Stored by diagonals: 3.125 lines a cell at a step of one, 1.067 at four, 0.723 at eight, 0.552 at sixteen, 0.422 at sixty-four. Stored by rows: 2.250, 1.501, 1.377, 1.314 and 1.268. Aligned diagonals: 3.123, 1.001, 0.626, 0.502 and 0.408.

Two things are visible and the second is the one that decides the design.

At one cell a step the row layout is the cheaper of the two — 2.25 lines against 3.125. A single cell reads (i1,j1)(i-1, j-1) and (i1,j)(i-1, j), which are adjacent in the row above, and (i,j1)(i, j-1), which is adjacent to the cell itself: two lines, sometimes three. On the diagonal layout the same three reads sit on two different diagonals at unrelated offsets: three lines, always. So a serial fill, judged one cell at a time, prefers rows.

Both slopes can be read off the layout rather than fitted, which is the test of whether the count is understood. On the diagonal layout a step’s accesses are three runs, not four: the reads (i1,j)(i-1, j) and (i,j1)(i, j-1) are adjacent cells of diagonal d1d-1, so across a step of ww they merge into one run of w+1w+1; the reads (i1,j1)(i-1, j-1) are a run of w1w-1 on diagonal d2d-2; and the writes are the step’s own run of ww on diagonal dd. Three runs, each costing one line plus w/8w/8 more, gives 3+3w/83 + 3w/8 — which is 6.0 at a step of eight against a measured 5.71, and 27 at sixty-four against 24.07.

On the row layout each cell of a step is in its own row and therefore its own line, and the cell above it is in another. Those upper cells are adjacent to each other, so consecutive step positions share the line above and the second line is amortised: about 1.11.1 lines a cell, which is 10.8 at a step of eight against a measured 10.87.

And the two curves have different floors. The row layout’s flattens at about 1.27 lines a cell and cannot go below one, because each cell of a step is in its own row and therefore its own line. The diagonal layout’s keeps falling — 0.42 at a step of sixty-four and still descending — because a step’s cells share lines with each other, so a wider step amortises each line over more of them. The row layout’s cost per cell is bounded below by one line; the diagonal layout’s is bounded below by four over the step.

That is the whole case for the layout, stated in a way the serial fill could not state it. The earlier page justified the diagonal layout by a miss rate — 8.7% against 31.1% — which is a statement about one cache and one table size. The per-cell floor is a statement about the layouts.

A step of eight cells touches 4.35 lines stored by diagonals and 9.88 stored by rows — not the three or four a run of eight adjacent cells would suggestDistinct cache lines one step touches, against the step's width, on logarithmic axes, for a 512 × 512 table under three layouts of the same cells. Each cell reads its three neighbours and writes itself; the step is a run of cells along one anti-diagonal, which have no dependence on one another. stored by diagonals: 3.06 at a step of 1, 3.25 at a step of 2, 3.62 at a step of 4, 4.35 at a step of 8, 5.78 at a step of 16, 8.52 at a step of 32, 13.50 at a step of 64. by diagonals, each line-aligned: 3.06 at a step of 1, 3.24 at a step of 2, 3.48 at a step of 4, 3.96 at a step of 8, 4.88 at a step of 16, 7.60 at a step of 32, 12.58 at a step of 64. stored by rows: 2.13 at a step of 1, 3.25 at a step of 2, 5.47 at a step of 4, 9.88 at a step of 8, 18.49 at a step of 16, 34.94 at a step of 32, 65.11 at a step of 64.124816326410step width, cellscache lines a stepstored by diagonalsby diagonals, each line-alignedstored by rows512 × 512, lines of 16 cellsone step of an anti-diagonal
Fig. 3 The same sweep with a line of sixteen cells rather than eight — a 64-byte line holding 32-bit values rather than 64-bit ones. A step of eight cells touches 4.35 lines stored by diagonals against 5.71 with the shorter line, and 9.88 stored by rows against 10.87. The diagonal layout gains from a wider line and the row layout barely does, because the row layout’s cost is one line a cell whatever a line holds.

The two plates together separate the two terms of the account. Halving the cells a line holds changes the diagonal layout’s step from 4.35 lines to 5.71 — almost exactly the 3w/83w/8 against 3w/163w/16 the three runs predict — and changes the row layout’s from 9.88 to 10.87, which is the constant and not the slope. A layout that reads runs is paid in line width; a layout that reads strides is not. The cliff where the data stops fitting measured the other boundary of the same model, where a working set crosses a cache; this is the boundary below it, where a run crosses a line.

The same at every size

A step of 8 cells reads the same handful of lines at every table size: stored by diagonals 5.74, by diagonals, each line-aligned 4.99, stored by rows 10.97Distinct cache lines one step of 8 cells reads and writes, against the side of a square table, on logarithmic axes, for three layouts of the same cells. stored by diagonals: 5.38 at 64, 5.57 at 128, 5.66 at 256, 5.71 at 512, 5.73 at 1024, 5.74 at 2048. by diagonals, each line-aligned: 4.59 at 64, 4.79 at 128, 4.89 at 256, 4.94 at 512, 4.97 at 1024, 4.99 at 2048. stored by rows: 10.01 at 64, 10.48 at 128, 10.73 at 256, 10.87 at 512, 10.93 at 1024, 10.97 at 2048.641282565121024204810table sidecache lines a step touchesstored by diagonalsby diagonals, each line-alignedstored by rowssteps of 8 cells, lines of 8the same cells, three addressings
Fig. 4 Distinct lines one step of eight cells touches, against the side of a square table, on logarithmic axes. Stored by diagonals: 5.38 lines at a table of 64, rising to 5.74 at 2,048. Aligned diagonals: 4.59 to 4.99. Stored by rows: 10.01 to 10.97. A factor of thirty-two in the table’s side moves every curve by under seven per cent.

The prediction that this would hold “whatever the table size” is the part of it that was right, and it is right for a reason worth stating. A step’s lines are decided by where four short runs of cells fall relative to line boundaries, and that is a property of the runs and the line, not of how many other cells exist. The slow rise from 5.38 to 5.74 is the short diagonals near the table’s two corners, which are a smaller share of a larger table.

This is what makes the quantity worth measuring at all. A miss rate depends on the cache, the table and the fill order together, so it has to be re-measured for every case; a step’s line count is a property of the layout and the step, and the plate above is what “a property of” looks like when it is measured rather than claimed.

The measurement that measures nothing

There is an obvious next plate and it is worth reporting that it is empty.

Running the whole fill through a cache and counting misses, at each step width, gives the same miss rate at every width: 9.40% through a cache of eight lines, 8.70% through sixty-four, 3.14% through 256 — identical to three decimal places from a step of one cell to a step of 256.

The reason is that grouping cells into steps does not reorder them. A step of eight cells computes the same eight cells in the same order as eight serial steps would; all that changes is that they are issued together. The cache sees one sequence of addresses either way, so it produces one answer. Anyone reaching for a cache simulation to price a vector width will get a flat line and should, and the flat line is not evidence that the width does not matter — it is evidence that the simulation was asked the wrong question.

What a step width changes is not the order of the accesses but the number of them that have to be outstanding at once. That is the quantity the plates above count, and it is the one with a limit in it.

How wide a step can be

One step of 64 cells has to hold 27 lines at once, and a step of 8 holds 6 — so the width at which a step cannot hold its own operands is far above any instruction'sThe largest number of distinct cache lines any one step touches, against the step's width in cells, on logarithmic axes. A step whose operands do not fit in the cache at once evicts its own data, and that is the limit on how wide a step is worth making. stored by diagonals: 4 lines at a step of 1, 6 lines at a step of 2, 6 lines at a step of 4, 6 lines at a step of 8, 9 lines at a step of 16, 15 lines at a step of 32, 27 lines at a step of 64. by diagonals, each line-aligned: 4 lines at a step of 1, 5 lines at a step of 2, 5 lines at a step of 4, 5 lines at a step of 8, 8 lines at a step of 16, 14 lines at a step of 32, 26 lines at a step of 64. stored by rows: 3 lines at a step of 1, 5 lines at a step of 2, 9 lines at a step of 4, 17 lines at a step of 8, 33 lines at a step of 16, 65 lines at a step of 32, 129 lines at a step of 64.124816326410100step width, cellslines one step must hold at oncestored by diagonalsby diagonals, each line-alignedstored by rows512 × 512 table, lines of 8the step's own working set
Fig. 5 The largest number of distinct lines any one step touches, against the step’s width, on logarithmic axes — what a step must hold at once rather than what it averages. Stored by diagonals: 6 lines at a step of eight, 9 at sixteen, 15 at thirty-two, 27 at sixty-four. Aligned: 5, 8, 14, 26. Stored by rows: 17 at a step of eight, 33 at sixteen, 65 at thirty-two, 129 at sixty-four.

The question the earlier page asked was where a wider step stops paying: at some width, its runs no longer fit in the cache at once, and then a wider instruction fetches more lines per cell than a narrower one.

On the diagonal layout that width is far beyond any instruction’s. A step of sixty-four cells needs twenty-seven lines resident, which is under two kilobytes — a fraction of any first-level cache. A vector register holds eight or sixteen 32-bit lanes on current machines and sixty-four at the widest; at sixty-four lanes the operands are twenty-seven lines, and the step’s own working set is not the binding constraint on anything.

On the row layout it binds, and early. A step of sixty-four cells needs 129 lines — over eight kilobytes on a 64-byte line, which is a quarter of a typical first-level cache used by one instruction’s operands. At a step of thirty-two it is sixty-five lines. So the row layout has the limit the prediction described, and the diagonal layout, which the prediction was about, does not.

That inversion is the page’s other finding. The question was posed about the layout that turned out not to have the problem, because the layout that does have it is the one whose lines per cell never fell in the first place — and the two facts are the same fact. A layout whose step costs one line per cell will always need as many lines as the step has cells.

What this adds to the layout’s case

Rounds and cache misses by fill order, 256 charactersAn edit-distance table for two strings of 256 characters — 66,049 cells — filled in 2 combinations of order and layout, each named on its bar. The upper bars are the rounds each order needs if every cell whose inputs are ready runs at once, which is the order's depth; the lower bars are the share of its reads that miss in fully associative · 32 lines × 8 elements · LRU. Anti-diagonal order, stored by rows: 513 rounds, 31.1% missed. Anti-diagonal order, stored by diagonals: 513 rounds, 8.7% missed.rounds, if every ready cell ran at onceanti-diagonal order, stored by rows513anti-diagonal order, stored by diagonals513reads that miss the cacheanti-diagonal order, stored by rows31.1%anti-diagonal order, stored by diagonals8.7%fully associative · 32 lines × 8 elements · LRU263,169 reads and writes per order
Fig. 6 The earlier page’s measurement, for the two combinations this page is about: anti-diagonal order on a table stored by rows and on a table stored by diagonals, both at 513 rounds on 256-character strings, missing 31.1% and 8.7% of their reads through a cache of 32 lines of 8 cells.

The serial measurement and the step measurement agree about which layout to use and disagree about why, and both reasons are worth having.

The serial one says: on this cache, filling this table this way, the diagonal layout misses a third as often. It is a strong number and it is contingent on three things, any of which a different problem changes.

The step one says: a step of ww cells costs about 3+3w/83 + 3w/8 lines on the diagonal layout and about 2+1.1w2 + 1.1w on the row layout, at every table size, for reasons that are arithmetic about where runs of cells fall. It is a weaker claim about any particular machine and a stronger one about the layouts.

And there is a third thing the step measurement says that the serial one cannot. A serial fill on the diagonal layout reads 3.125 lines per cell against the row layout’s 2.25 — the diagonal layout is worse one cell at a time. The layout is not an improvement that also happens to allow a wide step; it is a bet on the wide step, and it loses without one. The earlier page’s 8.7% against 31.1% comes from reuse across many cells, which the serial walk gets for free; the per-cell line count is what one step sees, and it points the other way.

What is not measured here

Lines, not instructions, and not time. A step of eight cells is one instruction on a machine with a vector unit and eight on a machine without, and nothing here counts either. The count is not the time applies with unusual force: the whole argument for a wide step is that the cells are independent, and independence is a property this model cannot see because it never charges for a dependence.

A step, not a schedule. Every step here runs to completion before the next begins, and the plates say nothing about how many could be in flight at once. A machine issues several instructions before the first has its operands, so the lines a sequence of steps needs resident is the quantity a real processor faces; where an algorithm looks is the argument that an access pattern is the thing to measure, and a pattern of overlapping steps is a different pattern from a sequence of separate ones.

Gathers are free here. A step on the row layout reads eight cells at a stride of 513, which a real machine does with a gather instruction that is several times slower than a contiguous load even when every line is resident. Counting lines charges the row layout for its lines and not for its gathers, so the factor of 1.9 at a step of eight is a lower bound on the difference.

One fully associative cache with LRU. The resident-set plate compares a step’s lines against a cache’s capacity and assumes a step’s operands can all be held. A set-associative cache can evict one of a step’s lines to make room for another, and a step whose runs land in the same set would thrash at a width far below the one counted here.

No edges. Every step is a full run of the stated width where the diagonal allows it and a short one where it does not, and the short steps at the table’s corners are counted as they fall. A real implementation would mask them, and masked lanes are fetched whether or not they are used.

Three cells read, one written. The table is an edit distance, so the recurrence has three inputs. A recurrence with more — an alignment with affine gaps, or an interval table whose cell reads every shorter diagonal, as a triangle stored in a square measured — has more runs a step must hold, and the counts here do not carry across to it.

One recurrence whose cells are independent along a diagonal. That independence is what a step is, and it is a property of the edit distance rather than of dynamic programming. The cost is the number of subproblems is the general statement of what a table costs; how many of those subproblems can be computed together is a separate question with a separate answer for every recurrence, and the anti-diagonal answer here is the easiest one there is.

Still open: the step that spans two diagonals

Every step on this page is a run along one anti-diagonal, which is the largest set of cells with no dependence between them. It is not the largest set a step could usefully hold. The cells of diagonal dd and the cells of d+1d+1 that depend only on cells already finished form a staircase two diagonals deep, and a step shaped like that would reuse the lines of diagonal dd immediately rather than after a whole diagonal’s worth of other work.

The measurement that follows builds a step of that shape — half of it on one diagonal and half on the next, offset by one — and asks whether the lines it holds at once fall below the 4+w/24 + w/2 a single-diagonal step of the same size needs, since the two halves share their reads. The prediction from this page is that they do, by close to a factor of two at small widths and by little at large ones, and that the cost is a step whose two halves must be issued in order — which is a dependence, and therefore exactly the thing the anti-diagonal order was chosen to remove.

Named alongside this one

Essays reaching for the same objects. Nobody chose these; they are what the concept index makes visible.

The objects this essay names

Each one links to every other essay that touches it.

AlignmentCache lineDepthDesign parameterDynamic programmingEdit distanceLocalityMemory layoutParallelismVectorisationWorking set