diff options
| author | Luke Hoersten <[email protected]> | 2026-06-20 20:36:54 -0500 |
|---|---|---|
| committer | Luke Hoersten <[email protected]> | 2026-06-20 20:36:54 -0500 |
| commit | 5b3b3d3f74fffdcf4023d3f0f3fe9729fadfb8e2 (patch) | |
| tree | 2c7d72e47cbecfc7ec13987d0822fabc1cca6e59 /sdkconfig.defaults | |
| parent | 3b0ab735d8764e536bf085ec07a95e427d60a6c1 (diff) | |
firmware: keep TCP recv window at IDF defaults (5760) — bigger window regressed g2g
Step-2 experiment from the iterative tuning plan: bumped
CONFIG_LWIP_TCP_WND_DEFAULT 5760 → 65535 (and matching SND_BUF +
RECVMBOX 6 → 16) under the hypothesis that the 4×MSS window
throttled receive throughput.
Instrumentation showed the window WAS the per-call throttle
(recv_chunk_max was exactly 5760 before, grew to 33-60KB after).
But end-to-end got dramatically worse:
- g2g exploded from ~100ms steady to 17 SECONDS growing unbounded
(1964 → 6010 → 10632 → 14725 → 17889ms over consecutive windows).
- painted dropped from ~24fps to 15-20fps.
- recv_avg rose from 32ms steady to 50-90ms with frequent
230-500ms outliers (the retransmit / RTO signature).
- chunk_min fell to 61 bytes, chunk_avg dropped from 3415 to ~2200
— fragmentation as lwIP RX struggled with the bigger bursts.
Root cause: the single recv→decode→paint task serializes; the
kernel buffer fills against ANY window during the ~6ms
decode+paint, but with 65535 bytes available the sender pumps
~45 segments before stopping vs ~4 before. That overruns the
lwIP RX path (RECVMBOX=16 mailboxes, default pbuf pool) → drops
→ retransmits → multi-frame stalls. And Scrypted has no way to
skip-oldest on the kernel queue, so frames just pile up on the
wire / in the ESP32 kernel buffer → g2g grows linearly with time.
Lesson: window size isn't the right knob until the receiver can
drain at line rate independent of decode+paint. Next iteration:
split recv into its own FreeRTOS task with a 1-deep latest-frame
slot to the decode/paint task — only then revisit window.
Diffstat (limited to 'sdkconfig.defaults')
| -rw-r--r-- | sdkconfig.defaults | 22 |
1 files changed, 11 insertions, 11 deletions
diff --git a/sdkconfig.defaults b/sdkconfig.defaults index a32a88e..2042e24 100644 --- a/sdkconfig.defaults +++ b/sdkconfig.defaults @@ -29,17 +29,17 @@ CONFIG_LWIP_IPV4=y CONFIG_LWIP_IPV6=y CONFIG_MDNS_MAX_INTERFACES=1 -# TCP receive-window + buffer tuning. Earlier attempt under HTTP -# regressed because every /frame opened a fresh socket and we ate -# the slow-start cost repeatedly. Under the streaming pivot the -# socket is long-lived: slow-start happens once, then we run with -# the full window for the rest of the session — which is exactly -# the workload these knobs are designed for. Larger WND lets the -# sender keep more bytes in flight per ACK and pushes our recv -# throughput ceiling up from ~5.3 MB/s. -CONFIG_LWIP_TCP_WND_DEFAULT=65535 -CONFIG_LWIP_TCP_SND_BUF_DEFAULT=65535 -CONFIG_LWIP_TCP_RECVMBOX_SIZE=16 +# TCP receive-window kept at IDF defaults (4 × MSS = 5760). An +# attempt to raise WND to 65535 regressed g2g from ~100ms to 17s +# growing unbounded — the bigger window let Scrypted bury the ESP32 +# in queued bytes faster than the single recv→decode→paint task +# can drain them, with no way to skip-oldest on the kernel queue. +# The real bottleneck is task serialization, not window size; raise +# this only once the recv path is on its own FreeRTOS task with a +# 1-deep latest-frame slot to the decode/paint task. +CONFIG_LWIP_TCP_WND_DEFAULT=5760 +CONFIG_LWIP_TCP_SND_BUF_DEFAULT=5760 +CONFIG_LWIP_TCP_RECVMBOX_SIZE=6 CONFIG_LWIP_TCP_SACK_OUT=y # HTTP server |
