“长肥管道”(带宽大、延迟高)为什么常常跑不满?答案在带宽时延积:窗口小于 BDP,管道里就有空洞。这篇用 netlab 造一条 100 Mbit/s、RTT 100 ms 的链路,把接收缓冲区和发送缓冲区从小到大扫一遍,量吞吐,也量缓冲过大造成的排队延迟。
BDP = 12,500,000 字节/秒 × 0.1 秒 = 1,250,000 字节(约 1.19 MiB):要让管道始终是满的,发送方必须有这么多字节在路上,接收方的窗口至少要这么大 接收窗口 64 KiB → 吞吐上限 = 窗口 ÷ RTT = 0.7 MB/s( 5.2 Mbit/s);链路上限 12.5 MB/s;取小者 0.7 MB/s(计算) 接收窗口 256 KiB → 吞吐上限 = 窗口 ÷ RTT = 2.6 MB/s( 21.0 Mbit/s);链路上限 12.5 MB/s;取小者 2.6 MB/s(计算) 接收窗口 512 KiB → 吞吐上限 = 窗口 ÷ RTT = 5.2 MB/s( 41.9 Mbit/s);链路上限 12.5 MB/s;取小者 5.2 MB/s(计算) 接收窗口 1024 KiB → 吞吐上限 = 窗口 ÷ RTT = 10.5 MB/s( 83.9 Mbit/s);链路上限 12.5 MB/s;取小者 10.5 MB/s(计算) 接收窗口 2048 KiB → 吞吐上限 = 窗口 ÷ RTT = 21.0 MB/s( 167.8 Mbit/s);链路上限 12.5 MB/s;取小者 12.5 MB/s(计算) 接收窗口 4096 KiB → 吞吐上限 = 窗口 ÷ RTT = 41.9 MB/s( 335.5 Mbit/s);链路上限 12.5 MB/s;取小者 12.5 MB/s(计算)
BDP = 带宽 × RTT,是让管道始终满的在途字节数。窗口 ÷ RTT 给出窗口受限时的吞吐上限。
SO_RCVBUF 设置值 读回值 吞吐 计算上限 srtt cwnd 对端窗口 snd_wnd
64 KiB 64 KiB 2.52 MB/s 0.66 MB/s 142 ms 511 KiB 303 KiB
256 KiB 256 KiB 2.53 MB/s 2.62 MB/s 142 ms 511 KiB 310 KiB
512 KiB 512 KiB 4.98 MB/s 5.24 MB/s 104 ms 511 KiB 511 KiB
1024 KiB 1024 KiB 9.91 MB/s 10.49 MB/s 105 ms 1023 KiB 1023 KiB
2048 KiB 2048 KiB 12.46 MB/s 12.50 MB/s 168 ms 2047 KiB 2047 KiB
4096 KiB 4096 KiB 12.46 MB/s 12.50 MB/s 197 ms 2420 KiB 4095 KiB
不设置(自动调整) 128 KiB 10.44 MB/s 1.31 MB/s 177 ms 2200 KiB 4095 KiB
链路带宽上限 12.5 MB/s;BDP 1221 KiB。接收缓冲区小于 BDP 时吞吐被窗口卡住;够大以后吞吐封顶在带宽上接收缓冲区小于 BDP 时,实测吞吐和窗口 ÷ RTT 吻合;大于 BDP 后封顶在带宽上。小于约 256 KiB 的 SO_RCVBUF 在 macOS 上不被照办。
无限速:SO_RCVBUF 256 KiB → 吞吐 2.60 MB/s,srtt 102 ms,cwnd 511 KiB 无限速:SO_RCVBUF 4096 KiB → 吞吐 41.54 MB/s,srtt 101 ms,cwnd 4095 KiB
窗口超过 BDP 的部分不会提高吞吐,只会在瓶颈队列里排队,srtt 从 105 ms 涨到 197 ms。没有带宽瓶颈时不排队。
SO_SNDBUF 设置值 读回值 吞吐
64 KiB 64 KiB 0.63 MB/s (srtt 104 ms)
256 KiB 256 KiB 2.51 MB/s (srtt 104 ms)
1024 KiB 1024 KiB 9.97 MB/s (srtt 105 ms)
4096 KiB 4096 KiB 12.46 MB/s (srtt 198 ms)SO_SNDBUF 同样限制吞吐,小到 64 KiB 也照办(0.63 MB/s)。两端都要有至少 BDP 的缓冲。