diff --git a/spike/x86/COST.md b/spike/x86/COST.md index 1624c34..fd7c224 100644 --- a/spike/x86/COST.md +++ b/spike/x86/COST.md @@ -111,7 +111,7 @@ nothing *but* calls (`b1`) the whole backend is 2.8× LLVM `-O0`, and the guard widened, stored, reloaded, stored again, the limit goes to a third slot, and then `cmp`/`jb` — with the failure path, its `.rodata` location string and its length, inline at the branch target. On `b2-bounds`, `flan.main` is `0x4b1` with checks and `0x3c0` without: **241 bytes, a quarter of the function.** In time it is 112.6ms against -104.9ms over 20.5 million checked loads — **about 0.4ns, one cycle a check** — because the branch predicts +105.0ms over 20.5 million checked loads — **about 0.4ns, a cycle or two a check** — because the branch predicts perfectly and the loads were going to memory anyway. That contradicts the way the handoff's list reads. The three temporaries are a code-size item. They are not a speed item. @@ -149,17 +149,23 @@ Times are best-of-seven, in milliseconds. | `b1-calls` | 20M calls of a one-instruction function | 2.1 | 36.8 | 104.3 | 2.8× | 200 → 807 | | `b2-bounds` | 20.5M bounds-checked array loads | 4.7 | 27.0 | 112.6 | 4.2× | 407 → 1390 | | `b3-spill` | 5M iterations of a six-deep arithmetic tree | 22.2 | 29.0 | 137.3 | 4.7× | 248 → 1117 | + +`b3`'s ratio is the one to distrust slightly: its expression ends in a `%`, which is an `idiv`, and an `idiv` is +twenty-odd cycles on every side. That is most of LLVM's own 22.2ms and a good part of its 29.0ms, so the +denominator is largely a hardware latency this backend cannot do anything about. The absolute gap — 108ms over +5 million iterations, about 21ns of extra work each — is the honest reading of that row. | `b4-copy` | 2M copies of a 64-byte struct | 2.0 | 7.2 | 20.8 | 2.9× | 348 → 877 | `b2` with `--no-bounds-checks` on both sides: LLVM 4.5ms, x86 105.0ms — the 7.6ms the check costs over 20.5 million of them, and the 241 bytes it costs in `flan.main`, are the whole of it. -`b1-calls` and `b3-spill` are the pair to read together. `b1` is 20 million calls of a one-instruction function -and lands at 2.8× `-O0`; `b3` is no calls at all, six dependent arithmetic temporaries per iteration, and lands -at 4.7×. **The backend is worse at arithmetic than it is at calling**, which is the opposite of what the suspect -list implies, and it is because a call already costs enough that four extra instructions beside it disappear, -while an add that should be one instruction costs five. +`b1-calls` and `b3-spill` are the pair to read together, with the `idiv` caveat above in mind. `b1` is 20 million +calls of a one-instruction function and lands at 2.8× `-O0`; `b3` has no calls at all and pays 21ns an iteration +for six dependent arithmetic temporaries. **The backend is worse at arithmetic than it is at calling**, which is +the opposite of what the suspect list implies, and it is because a call already costs enough that four extra +instructions beside it disappear, while an add that should be one instruction costs five. `recur`, which is a +counting loop and nothing else, says the same thing on a corpus program: 6× LLVM `-O0`. The `-O2` column in `b1` and `b4` is 2ms — the loop is gone. That is a true fact about the toolchain Flan ships and a useless one about code generation, which is the whole reason the `-O0` column exists. @@ -193,7 +199,7 @@ Ranked by what the numbers actually support, and not by the order of the list in 1. **Keep values in registers across a single expression.** Not a register allocator — just not routing every constant and every subexpression through a frame slot, and not re-materialising a loop bound every iteration. - This is most of the 2× against `-O0`, most of `recur`'s 6×, and all of `b3`'s 4.7×. + This is most of the 2× against `-O0` and most of `recur`'s 6×, and it is what `b3`'s 21ns an iteration buys. 2. **Inline small aggregate copies** instead of `rep movsb`. One instruction, twenty cycles, on a copy that is four `movdqu` pairs. 3. **`flan_dev_reg_note` in a release build** (item 2 of the old handoff's list) is worth doing and is small.