0%

提前 SENDME、cwnd、alpha=186 与发包速率关系分析

提前 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
2
cwnd <= inflight
→ allowed = 0 → 电路停止发包(stall)

这才是真正的流控点。


2. BDP + alpha 的构成(代码机制)

在 Tor 的 cc 中,cwnd 会被调整为接近:

1
cwnd_target ≈ bdp + alpha

其中 alpha 默认值为:

1
#define CC_PARAM_ALPHA 186

代码中你会看到类似逻辑(不同版本略有差异):

1
cwnd = bdp + alpha;

随着时间推移:

  • RTT 被更新;

  • throughput 推动 BDP 更新;

  • cwnd 会向该“目标值”收敛。

因此在稳定阶段(steady state),会出现:

1
2
3
cwnd ≈ bdp + 186
inflight ≈ bdp
→ cwnd - inflight ≈ 186

也就是说:

Tor 默认设计了一个“允许比 BDP 多塞 186 个 cell 的松弛窗口”。


3. sendme_inc=32 对窗口的影响被 alpha=186 放大稀释

每收到 32 个 DATA cell,对端会发回一个 SENDME(电路级):

sendme_process_circuit_level() 中:

1
2
inflight -= 32;   // 确认了 32 个 cell
cwnd += 1; // additive increase

于是WINDOW变化:

1
2
window_new = (cwnd + 1) - (inflight - 32)
≈ window_old + 33

但 steady state 下窗口大小是:

1
window ≈ 186

因此 SENDME 带来的 +33 相对于 +186 来说是:

1
33 << 186  (远远小得多)

这意味着:

一次 SENDME 对 window 的影响非常有限,不足以让电路频繁进入 window=0 的“强流控点”。

换句话说:

alpha=186cwnd - 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
2
3
inflight → 接近 cwnd
window → 经常变成 0
电路频繁 stall

要做到这一点:

(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
2
3
if (window == 0) {
log_info(LD_EXIT, "Circuit %p stalled by cwnd: cwnd=%d inflight=%d", ...);
}

再对比提前 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 调小。