Subj : sexyz ZMODEM sender throughput ~30x slower than lrzsz (output-thread r To : GitLab note in main/sbbs From : Rob Swindell Date : Fri Jul 24 2026 23:32:14 https://gitlab.synchro.net/main/sbbs/-/issues/1195#note_9704 ## Fixed — buffered streaming send path Resolved in **`34af5f34b5`** (stamps-40-wave), sexyz.c 3.4. The two output threads and the ring were never the bottleneck; **feeding the ring one byte at a time** was. `send_byte()` took the ring mutex twice per byte while `output_thread` hot-looped on it, so a 256 MB localhost send cost ~44 CPU-seconds and 1.46 million voluntary context switches and topped out near 11 MB/s. The fix accumulates bytes into a 4 KB buffer and hands whole spans to the ring. The two-thread architecture, flow control and error handling are otherwise unchanged — the producer just stops trickling: | | before | after | |---|--:|--:| | streaming 256 MB (localhost → lrz) | 11.5 MB/s | **~115 MB/s** | | sender CPU / 256 MB | 44 s | **0.85 s** | | voluntary context switches | 1,464,130 | **64,061** | | error-recovery gate | passes | still passes (verified 5×) | This is the same throughput a single-threaded rewrite would give, for a ~105-line diff instead of a rewrite, and with better-proven error recovery (the single-threaded prototype only reached 2/3 on the gate). ### Deliberately scoped to streaming Buffering is applied **only for full ZMODEM streaming** (`max_window_size == 0 && !no_streaming`). A transmit window (`-w`) or segmented mode (`-s`) depends on the receiver's ACKs flowing back as data is sent; bursty buffered output fills the window before any ACK returns and stalls the transfer. Those modes fall through to the original byte-at-a-time path and are byte-for-byte unchanged. ### Error recovery preserved A fast streaming sender fills the socket's auto-tuned (multi-megabyte) send buffer, so a single line error would cost a ZRPOS retransmission of everything in flight. `SO_SNDBUF` is bounded to the ring size (`OutbufSize`) for streaming to cap the in-flight backlog; `[sockopts]` can override for a high bandwidth-delay link. ### Not in scope - **`zmodem.c` is unchanged**, so SyncTERM (which already has a buffered send path) is untouched. - **Windowed throughput** is still far below lrzsz — it stays on the byte-at-a-time path by design. The related `-w` correctness fixes shipped separately: the `window < 4×block` SIGFPE and the `window ≤ block` stall (block clamped to window/4 like lsz/sz, **`aaf394a0d8`** hello-27-tabs), both tracked in #1197. Deuce's shared-engine `zmodem.c` optimizations (byte-classification table, slicing-by-4 CRC-32, buffered `fcrc32`) also landed under this issue. Full analysis and the benchmark harness are in `docs/zmodem_comparison.md` and `src/bench/zmodem/`. Closing. *Note by Claude (Anthropic).* --- SBBSecho 3.37-Linux * Origin: Vertrauen - [vert/cvs/bbs].synchro.net (1:103/705) .