• docs/zmodem_comparison.md src/bench/zmodem/README.md

    From Rob Swindell (on Debian Linux)@1:103/705 to Git commit to main/sbbs/master on Fri Jul 24 11:06:27 2026
    https://gitlab.synchro.net/main/sbbs/-/commit/9627d6504cecd3c53ff66c47
    Modified Files:
    docs/zmodem_comparison.md src/bench/zmodem/README.md
    Log Message:
    docs/zmodem, bench: re-measure after Deuce's send-path work; root-cause sexyz

    Every throughput figure is re-measured in one interleaved batch (256 MB,
    three passes, spread under 2%) so the tables are internally consistent
    instead of splicing runs from different days: lrzsz 203.9, zmodem.c
    rev 2.3 buffered floor 115.8, sexyz receiving 113.3, Forsberg 96.9,
    zmodem.c rev 2.2 buffered floor 91.9, sexyz sending 11.5 MB/s.

    Deuce's 2026-07-24 work on the shared send path (class-table byte
    classifier, slicing-by-4 CRC-32, hoisted escape mask with noinline cold
    paths, buffered fcrc32) moved the buffered floor 91.9 -> 115.8, +26%,
    which SyncTERM inherits. The doc's old "second, shared lever, pending"
    framing is replaced with what actually shipped.

    Root-cause the sexyz sender properly rather than asserting it.
    send_byte() takes the ring mutex twice per byte while output_thread
    hot-loops on the same mutex: 44 CPU-seconds per 256 MB against lsz's
    0.95, 1,464,130 voluntary context switches against 1,402, one futex per
    17 bytes with 37% contended, and ~94-byte socket writes. A full sweep
    of OutbufSize, OutbufHighwaterMark and OutbufDrainTimeout is flat inside
    noise, so this is not a tuning problem and should not be chased as one.

    Record the prototype ladder and what each one proved. Buffering the
    producer removes the entire sexyz-specific penalty -- 115.8 MB/s at 1.0 CPU-second, exactly the engine ceiling -- but six variants now fail the
    3x error-injection gate that the shipped per-byte sender passes. Two
    results are worth keeping: rate-capping the batched sender to the
    shipped sender's own 11 MB/s still fails while the shipped sender passes
    3/3, so it is not an artifact of the higher data rate; and going single-threaded with a buffered send_byte holds full speed, drops
    context switches to lsz's level, and reaches 2/3, leaving blocking
    sendbuf() as the one missing piece. The recommendation is now what lrz actually is: single-threaded, buffered, non-blocking output with select
    on both directions.

    Also note the failure modes that cost time to rediscover (a void flush
    callback swallowing a failed write, a sticky error latch spinning on zmodem_send_raw ERROR, position accounting desync) and that a
    single-stream localhost benchmark cannot see per-byte CPU wins above
    ~100 MB/s -- two of Deuce's optimizations measure zero here because his
    six interleaved 1 GiB transfers are CPU-saturated and this is not.

    No code changes: all prototypes were measured out-of-tree and discarded.
    Refs #1195, #1196.

    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
    --- SBBSecho 3.37-Linux
    * Origin: Vertrauen - [vert/cvs/bbs].synchro.net (1:103/705)
  • From Rob Swindell (on Debian Linux)@1:103/705 to Git commit to main/sbbs/master on Mon Aug 24 22:26:12 2026
    https://gitlab.synchro.net/main/sbbs/-/commit/8d8af7d5d090c3077ef615d5
    Modified Files:
    docs/zmodem_comparison.md src/bench/zmodem/README.md
    Log Message:
    docs/zmodem: re-baseline the throughput tables on a receiver that can
    keep up

    Every sender figure this document ever published was measured against
    `lrz`. Section 3.5 then measured `lrz` itself and found it receives 256
    MB for 1.28 CPU-seconds, with receiver CPU equal to the wall clock --
    more expensive than any sender it was scoring. So the sender table was reporting `lrz`'s ceiling four times over and reading it as a four-way
    tie ("every sender built on zmodem.c now runs level with lrzsz").

    Deuce spotted it from the outside: switch the baseline receiver, lrz
    can't handle the load. He is right about the premise. `zmrx` is the
    obvious replacement at 0.94 CPU-s, but as of zmodem.c 2.7 it is no
    longer the cheapest receiver -- the span work put sexyz itself at
    0.55 s. So the baseline receiver is now sexyz.

    Re-measured (256 MB, five passes, interleaved, medians), the senders
    spread over 1.74x where they had sat inside 1.6 %:

    zmtx 487.2 MB/s 0.47 CPU-s
    ztx_buf 385.4 0.70
    sexyz 335.3 1.00
    lsz 279.9 0.96

    sexyz sends 20 % faster than lsz, which the old baseline could not show.
    The document says plainly that this is concurrency rather than
    efficiency -- sexyz spends more CPU than lsz and wins on wall clock with
    two threads -- and that the engine's efficiency claim belongs to
    ztx_buf, single-threaded and cheaper than lsz on both axes.

    The receive table of 3.5 had the same defect in the other direction: its
    fixed `lsz` sender costs 0.96 CPU-s, so after the span fix the three
    fastest receivers all read ~280 MB/s and the fix looked like it had
    merely drawn level with `zmrx`. Driven by `zmtx` (0.47 s) instead, sexyz receives at 486.5 MB/s for 0.55 s -- 1.70x `zmrx`, the fastest receiver measured, and 4.2x its own pre-fix 117.2 at 24 % of the CPU. The
    pre-fix rows were never affected, being the slow end either way, so the
    deficit that section diagnoses stands as measured.

    New section 3.0.2 states the rule both halves of this document have now
    broken: the fixed end of a comparison must be cheaper in CPU than
    everything at the varied end, and that must be shown rather than
    assumed. The per-endpoint `wait4` CPU columns added on 2026-08-24 make
    it checkable -- if the fixed end's CPU equals the wall clock, the run
    measured the fixed end. The bench README carries the same lesson, and
    its recipes no longer default to `lrz`.

    Also recorded: the span receiver's cost depends on the sender's write granularity. Behind zmtx's 8 KB subpackets it receives for 0.55 CPU-s;
    behind Forsberg's 1 KB blocks the same receiver spends 1.34 s and 256 k
    context switches for the same bytes.
    --- SBBSecho 3.37-Linux
    * Origin: Vertrauen - [vert/cvs/bbs].synchro.net (1:103/705)