Skip to content

v0.7.0 — the movement was the cost, not the arithmetic

Choose a tag to compare

@tannevaled tannevaled released this 26 Sep 15:51
· 27 commits to main since this release
28520d1

Two passes folded away, bit for bit

Synthesize2D_97_WithDims is 48% of the heaviest page in the corpus, and the
arithmetic is the small part of it:

1.12 s flat in Synthesize2D_97_WithDims the gather and scatter
0.45 s liftstep97_c_f64_neon the lifting, NEON assembly
0.40 s BaseInterleave_fallback_Float64 the interleave, pure Go
0.30 s runtime.memmove the two copies
0.23 s scaleslice_c_f64_neon the scaling, NEON assembly

1.82 s of moving data against 0.68 s of computing it. Two passes carry no
arithmetic of their own and are now folded into the passes either side: the
scaling into the read-out of each subband (a copy then *= K is the same
single multiply as = data[i] * K), and the interleave, in the vertical pass,
into the scatter.

Measured

min-of-5, two interleaved rounds, on the three heaviest JPEG 2000 pages, against
a ±1% noise floor:

document before after
b31820797.pdf 786.3 ms 721.2 ms −7.7%
bulletinchicagov48nchicg.pdf 657.7 ms 636.7 ms −4.4%
irrigationrequir1340fort.pdf 404.7 ms 388.5 ms −3.4%

Unchanged output

All 3 215 corpus documents render byte-identical. TestFewerPassesIsBitIdentical
compares the new synthesis against the shape it had before in exact float64
bits, over twelve geometries including odd sizes and both parities — and it runs
on all six architectures, so the fold is exact on big-endian s390x and on 32-bit
too, where the lifting has no assembly path at all.

Two things measured and NOT done

  • A fused kernel in scalar Go (one call over six passes instead of seven calls
    over nine) is 1.5× to 1.8× slower. The library's lifting is NEON assembly
    with FMA and losing it costs more than the passes save. The same FMA makes that
    path unreproducible in Go: the identical expressions differ by 1 to 16 ulp.
  • BaseInterleave runs the pure-Go fallback on arm64 — the generated NEON
    variant exists and is never assigned. Benchmarked head to head, it makes no
    difference: both run at 25–28 GB/s, which is memory bandwidth. An interleave is
    pure movement; the answer is to remove the pass, not vectorise it.

Also takes go-highway v0.0.12, eight versions newer than what this package was
pinned to. Nothing was updating it.