提前 SENDME、cwnd、alpha=186 与发包速率关系分析
1. Tor 新拥塞控制的核心公式与行为
在新拥塞控制中,电路的发送窗口由以下公式决定:
1 | window = cwnd - inflight |
调度器在发送 DATA cell 前会调用:
1 | allowed = congestion_control_get_package_window(circ, layer_hint); |
该函数本质上返回:
1 | allowed ≈ max(cwnd - inflight, 0); |
当:
1 | cwnd <= inflight |
这才是真正的流控点。
2. BDP + alpha 的构成(代码机制)
在 Tor 的 cc 中,cwnd 会被调整为接近:
1 | cwnd_target ≈ bdp + alpha |
其中 alpha 默认值为:
1 |
代码中你会看到类似逻辑(不同版本略有差异):
1 | cwnd = bdp + alpha; |
随着时间推移:
RTT 被更新;
throughput 推动 BDP 更新;
cwnd 会向该“目标值”收敛。
因此在稳定阶段(steady state),会出现:
1 | cwnd ≈ bdp + 186 |
也就是说:
Tor 默认设计了一个“允许比 BDP 多塞 186 个 cell 的松弛窗口”。
3. sendme_inc=32 对窗口的影响被 alpha=186 放大稀释
每收到 32 个 DATA cell,对端会发回一个 SENDME(电路级):
在 sendme_process_circuit_level() 中:
1 | inflight -= 32; // 确认了 32 个 cell |
于是WINDOW变化:
1 | window_new = (cwnd + 1) - (inflight - 32) |
但 steady state 下窗口大小是:
1 | window ≈ 186 |
因此 SENDME 带来的 +33 相对于 +186 来说是:
1 | 33 << 186 (远远小得多) |
这意味着:
一次 SENDME 对 window 的影响非常有限,不足以让电路频繁进入 window=0 的“强流控点”。
换句话说:
alpha=186 让 cwnd - inflight 在绝大多数时间里都远大于零,
电路不会因为 cwnd 耗尽而停住。
这也解释了你观察到的现象:
“发 DATA 时几乎不会出现 cwnd < inflight,电路几乎不会 stall。”
4. 这说明什么?——电路级拥塞控制在默认参数下是“软控”而非“硬控”
在这种配置下:
电路级窗口 几乎不会真正约束发送速率;
cwnd / inflight 的差值(window)始终保持在一个较大的余量;
流控更多依赖:
stream-level SENDME;
KIST 调度;
TCP 排队与瓶颈;
全局写队列限制;
也就是说:
电路级 CC 在 alpha=186 时不是阻断性约束,而是一个“方向性”调节机制。
它让 cwnd 跟随 BDP,但不强制限制发包速率。
5. 那为什么你仍能看到“每 100 cells 耗时”有明显起伏?
尽管电路级不会进入真正的 stall(window=0),但其它更高层的因素仍会造成周期性抖动:
(1) Stream-level 窗口耗尽
在 sendme_note_stream_data_packaged() 中可以看到:
1 | conn->package_window--; |
如果 stream SENDME 没及时回来,stream 会被暂停,导致 DATA 发不出去。
即使电路级窗口很大,单条 stream 也会 stall。
(2) TCP 层排队 / RTT 抖动
100 个 cell 跨越一个 RTT 抖动区间时,你统计的时延就自然起伏。
(3) KIST 调度器的分配时间片
Tor 的 KIST(Kernel-Informed Socket Transport)会为不同 ORConn 做调度:
某循环中不给这条电路发;
下一循环又重新允许发;
这会导致你看到的 100-cell 耗时类似“波浪形”。
因此:
100-cell 的曲线起伏并不等价于“cwnd 打满”,
而是多层调度/网络因素叠加导致的抖动。
6. 提前 SENDME 在这种参数下是否会提升速率?
答案:对“瞬时速率”影响很小,但会改变 cwnd/BDP 收敛曲线和微观 stall 时刻。
原因如下:
(1) 窗口永远不会打满 → 提前解锁 SENDME 无法显著减少 stall
因为:
1 | window ≈ 186 >> sendme_inc(32) |
你提前 SENDME 做的是:
inflight 更早减 32;
cwnd 更早加 1;
但:
window 本来就大于 150;
本来就不会因为 window=0 停止发包;
因此提前 SENDME :
✔ 会使 cwnd 收敛稍微更快
✘ 不会显著提高瞬时发包速率
(2) 平均速率改善有限
因为发包主要受:
TCP 带宽上限;
stream-level 流控;
ORConn 写队列;
CPU 和调度器时间片;
而不是 cwnd 限速。
在 window 足够大时,cwnd 不再是瓶颈。
7. 如何设计一个能明显观察到“提前 SENDME 提速”的实验?
要观察显著效果,你需要让 cwnd 真正成为瓶颈,即必须达到:
1 | inflight → 接近 cwnd |
要做到这一点:
(1) 把 alpha 调小
例如:
1 | alpha = 16 或 alpha = 32 |
此时 steady state:
1 | cwnd ≈ bdp + 16 |
sendme_inc=32 会更频繁使 window 打到 0。
(2) 强调单条电路高负载
必须保证:
inflight 逼近 cwnd;
Exit 发得足够快(你的私网环境已经满足)。
(3) 打 log:window=0 的时刻
在 congestion_control_get_package_window() 中加:
1 | if (window == 0) { |
再对比提前 SENDME 后:
stall 次数变少;
每 1000 cell 耗时减少;
cwnd 更快收敛;
你就能清楚看到提前 SENDME 的加速效果。
8. 最终总结
在默认参数 alpha=186 下:
cwnd 很少真正成为瓶颈;
window 始终远大于 0;
电路级流控是“软控”而非“硬控”;
sendme_inc=32 的影响被大 alpha 严重稀释;
提前 SENDME 对瞬时速率提升几乎看不到;
100-cell 起伏主要来自 TCP / stream-level / 调度抖动。
如果想实验“提前 SENDME 让发包显著变快”:
必须减小 alpha,使 cwnd 经常逼近 inflight;
让电路频繁在 window=0 附近运行;
然后提前 SENDME 才能明显减少 stall,并提升平均发送速率。
换句话说:
默认的 Tor CC 参数刻意避免让 cwnd 成为硬瓶颈,所以提前 SENDME 很难“立刻提速”。
如果你想让 cc 真正限制速率,就要把 alpha 调小。