Skip to content

v1.16.0

Choose a tag to compare

@github-actions github-actions released this 12 Oct 22:00
· 22 commits to main since this release
v1.16.0
3abe555

Warning

Superseded by v1.17.0 — upgrade.

This release shipped --readahead-subblock-dispatch on by default, and that default is
rolled back in v1.17.0. It costs wall clock on an index-then-stream reader — a handle that
reads scattered and then streams a large contiguous range, which is the common
metadata-then-payload shape. Measured after this release: median 0.798 throughput against
the flag off, below both brackets in 8 of 8 reps, with uncovered demand misses 62 → 81 and
bytes identical.

The flag itself is unchanged and still available. If you know your workload reads slices and
stops, --readahead-subblock-dispatch still gives the byte savings described below; the
correction is only that it is the wrong thing to turn on for everyone. See
#407 for the measurement.


Changed

  • --readahead-subblock-dispatch is on by default (#407).
    Pass =false to disable. This was pre-registered: wall clock neutral on both moving rows with
    faults flat meant the default moves on.

    GRIB HEMCO
    byte amplification 3.02× → 1.39× 1.79× → 1.48×
    wall, median B/D 1.005 (11 kept reps) 0.974 (5 kept reps)
    uncovered demand misses 69 in every arm 331–333 in every arm
    GETs 71–74 339–341

    Each flagged run was bracketed by two default runs, with a rep discarded when the brackets
    disagreed by more than 10% — necessary because GRIB's ~3 s wall drifts 15–24% between brackets,
    which is larger than the effect. The tester disclosed that the flagged arm looked faster in
    the first six reps and that the impression did not survive the rule: the high-drift reps were
    exactly the ones it discards.

    FITS, three control shapes, a whole-object dd and a scatter-then-stream handle are all
    unchanged
    , and the arm cannot fire where the evidence gate is off, so cross-region and
    region-unknown mounts are unaffected. The default reaches exactly the in-region regime the
    measurement covers.

Added

  • The sub-block arm's test now reports uncovered demand misses, which is the mechanism by
    which it could cost wall clock
    (#407). Bytes
    a reader never wanted are pure waste; bytes it did want and now has to fault for are a stall,
    and a byte count cannot tell those apart — which is the whole reason the default decision is
    open after a clean byte result.

    On the walk fixture the arm halves bytes and triples uncovered misses (4 → 12) while
    prefetch hits fall 6 → 1. Treated as a bytes-for-faults trade, break-even is around
    100 MB/s of effective bandwidth at a 30 ms first byte, and ~30 MB/s at 100 ms — so if that
    fault increase transferred to a real workload, the arm would be a wall loss at any bandwidth
    those instances actually reach. An external run measured the GET count flat (72 → 71) on the
    real shape
    , which argues it does not transfer. The two signals disagree, and only a
    wall-clock cell on the real workload settles it.

    Logged, not asserted. Any bound on this ratio would be a tuned constant on a
    fixture-specific quantity, which is how both #340's and #349's withdrawn defaults were placed.
    The figures are in the test output so a future change to this arm shows its effect on faults
    and not only on bytes.

Fixed

  • --readahead-subblock-dispatch's help and docs row carried fixture figures and a stale
    "measured inert" note
    (#407). Both now carry
    the external v1.15.0 measurement, two arms and two reps with the default arm reproducing the
    previous release exactly as the control:

    shape default with the flag GETs prefetch-range
    GRIB .idx sweep 3.02× 1.39× 72 → 71 0 → 5.05 MB
    HEMCO time-slice 1.79× 1.48× 340 → 339 0.26 → 4.98 MB
    FITS cutout 2.20× 2.20× 12 → 12 0

    All of GRIB's whole fills become prefetch-range and its used:issued goes 2:24 → 2:5, so the
    change is attributable rather than inferred. The GET count does not rise, which settles the
    #124 concern that a byte win would arrive as a
    GET explosion: the span replaces whole-block fetches rather than fanning out.

    Two shapes qualify, not one, and the docs now say so. I predicted only GRIB would move, on
    the premise that HEMCO handles never enter Random; a trace I had already quoted showed one of
    them at 129 consecutive random→random transitions. The trigger is a property of the access
    pattern
    , not of a file format — any handle that reads scattered and then reads a short
    contiguous stretch meets it. Recorded so the next reader does not inherit the wrong premise.

    And the pre-registration it broke was badly written. Its failure condition — "if FITS or
    HEMCO move at all, the mechanism is not as described" — conflated whether the trigger behaves
    as specified
    with which shapes meet it. Only the first is a claim about the code. A falsifier
    that depends on a guess about someone else's workload tests the guess.

    Still off by default, and the row now says what is missing: wall clock on the two rows that
    move. The arm bounds prefetch, so its risk is latency rather than bytes — fetching 0.46× the
    bytes could be faster, neutral or slower, and a byte count cannot separate those. #309's 11×
    regression came from a prefetch-depth change that looked clean on totals.

  • My bytes-for-faults model was a fixture artifact, and it is now pinned as one. A fixture at
    8 MiB field spacing showed the arm halving bytes while tripling uncovered demand misses,
    from which I published a break-even implying the arm would be a wall loss at any realistic
    bandwidth. The external cell then measured faults flat on both real shapes.

    The cause is the spacing: the default dispatches window + 1 blocks, which at that spacing
    overshoots into the next field, so the default's over-fetch was accidentally prefetching it
    and the arm's shorter span was not. At wider spacings the fault counts are identical and the
    byte saving is larger — and a scattered walk jumps farther than the window by definition. A
    test now measures both sides of that boundary, so the artifact cannot return disguised as a
    regression.

    It asserts the delta between arms across two spacings, not an absolute count: UncoveredMiss
    fires when a demand read finds its chunk neither cached nor in flight, so it races the prefetch
    goroutine and picks up extra misses under load. The first version asserted equality, passed
    alone, and failed twice in three package runs — the same mistake as the #313 gate test that
    asserted the gate halves peak pressure.