刷 PT 的人聊起提速,翻来覆去就三样东西:机器、线路、BBR。

前两样花钱能解决。第三样最玄——同一台机器,换个内核模块,速度就是不一样,但没人说得清为什么。这篇写写这个"为什么",顺便记一下我自己这段时间的折腾:做了什么、失败了什么、最后琢磨出了什么。

先用一段话说清 BBR 在干嘛

传统的拥塞控制(比如 CUBIC)把"丢包"当成"前面堵车"的信号:丢一个包,减一次速。

BBR 不信这套。它一边发一边量:这条路的最大带宽是多少、最小往返时间是多少。两个数一乘,就是"这根管子能装多少数据",然后照着这个量发。丢包?丢包不一定是堵车,可能只是路上有块石头。

它跑起来分几个阶段:

  • 冲刺:用远高于 1 的倍率猛灌,直到发现带宽不再涨了
  • 排空:把刚才灌多的吐出来
  • 巡航:八轮一个循环,偶尔往上探一探有没有更多带宽

原版 BBR 的冲刺倍率是 2.885(这个数是 2/ln2,有推导的),巡航循环平均下来正好 1.0 倍。它是个好公民:目标是"跑满,但不给别人添堵"。

而刷 PT 的人要的,恰恰不是好公民。

第一章:暴力的起点

bbrx,jerry048 早年的作品。相对原版,关键处改了三下:

  • 冲刺倍率 2.885 → 6.004
  • 判定"带宽到顶"要连续 10 轮没涨(原版只要 3 轮)
  • 最小拥塞窗口 4 个包 → 200 个包

翻译成人话:冲得更猛、更晚认输、就算被打压也守着一个很高的底线。

效果是公认的猛。代价也很直白——废弃数据多。很多人的服务器是计流量的,浪费不是难看,是真金白银。

第二章:温和化的诅咒

接下来这些年,这个圈子发生了一件挺有意思的事:每一个想改进它的人,都把它改温和了。

  • bbrz(胡师傅写的):专治浪费。冲刺降到 3.9,10 轮改成 3 轮,底线窗口从 200 砍到 20,巡航均值 1.31 倍。省是真省了,刷力也是肉眼可见地弱了。
  • 新版 bbrx(jerry 沉寂几年后回归之作):巡航循环直接退回原版的 1.0 倍,只保留了暴力的冲刺。大家想着"新的总比旧的好",纷纷升级,结果很差。更要命的是旧版被他从仓库里删了——想退回去都没地方拿。
  • bbr+各种字母的变异体

第三章:它根本不是设计出来的

带着"它到底凭什么猛"这个问题,我把旧版 bbrx 的源码逐行对了一遍。然后看到了结构体里的这两行:

u32 pacing_gain:10,   /* 只有 10 位,最大存 1023 */
    full_bw_cnt:2,    /* 只有 2 位,最大存 3   */

第一处:冲刺倍率 6.004 在代码里是整数 1537。塞进一个 10 位的字段里,溢出了,截断成 513——除以 256,正好 2.004 倍

也就是说,它从来没有 6 倍冲刺过。

第二处:退出冲刺阶段的门槛是"连续 10 轮带宽没涨",而这个计数器最大只能数到 3。

也就是说,它永远数不到 10,永远退不出冲刺阶段。

两个 bug 叠在一起,产生了一个谁也没设计过的行为:从连接建立到结束,死死焊在 2.004 倍上,一根直线。没有循环,没有排空,没有起伏。

而源码里那个精心写好的、"永不排空"的巡航数组(均值 1.40 倍)——是一段从来没有被执行过的死代码

这个圈子传了好些年的"手感",是两个整数溢出。

后来我才想明白:这不是笑话,这是线索。

第四章:两次失败

我的想法很自然:既然 2 倍焊死这么好用,那我把它做"对"不就行了?

  • bbrmax:拿位宽正确的 bbrx 做骨架,巡航数组拉到均值 2.0、峰值 2.5。
  • bbrmax2:干脆跳过排空、焊死 2.5 永不回落,用来验证"是不是峰值决定胜负"。

连着跑了好几天,结论让人泄气:bbrmax、bbrmax2、bbrx_old,三个在实战里分辨不出差别。 小种子打平,大种子 bbrx_old 略占优,但噪声盖过了差距。

(中间还闹了个乌龙:有几台机器在编译内核模块的时候撞上了内核升级,重启后模块根本没装上,实际在裸奔 cubic。早期那批"打不过"的结论全部作废重跑。这种事真的会发生,切完记得核对一下 sysctl net.ipv4.tcp_congestion_control。)

但"没差别"这件事本身,是整轮折腾里最值钱的结论:

稳态倍率在 2.0~2.5 这个区间,根本不是胜负手。

我和所有前人一样,一直在拧一个不管用的旋钮。

第五章:真正的刹车

既然不是倍率,那是什么?

答案在一段所有变种都原封不动抄了好些年的代码里——policer 识别

原版 BBR 有这么一套逻辑:如果连续两个采样区间丢包率超过 20%、而且带宽估计稳定,它就判定"我被运营商的限速器掐住了",然后做两件事:把带宽估计换成更低的那个,并且把巡航倍率直接摁成 1.0。想翻身,得在巡航状态下连续熬 48 个往返

问题来了:一条 2 倍超发的流,丢包率天然就在 20% 这条线上下晃。

所以它会反复触发:猛一阵 → 被摁成 1.0 → 熬 48 轮 → 再猛一阵 → 又被摁下去。

那 bbrx_old 为什么没事?

因为这段检查写在巡航分支里。而 bbrx_old 因为那个 bug 永远停在冲刺阶段,压根走不到这段代码。

它不是跑得比别人快。它是唯一一个没踩到刹车的。

这一下,很多零碎的现象全串起来了:为什么同一个变种有时猛有时一般(看丢包时序有没有撞上阈值)、为什么小种子大家打平(几十秒的传输来不及凑满两个采样区间)、为什么大种子 bbrx_old 略占优(跑几个小时,反复触发)。

第六章:bbrx_pro

方向清楚了:别再调倍率了,去拆那些没人拆过的"礼貌机制"。

以位宽正确的 bbrx 为底,动了五处:

  1. 废掉 policer 识别——判定成立也不降速、不降带宽估计。这比那个 bug 还彻底,bug 至少还吃了带宽降级这一刀。
  2. 拆掉丢包刹车——丢包不再削减窗口、不做保守的包守恒、进恢复期不把窗口砍到在途量。这三样从 jerry 到胡师傅,一直原封不动留着,没人动过。
  3. 冲刺撞顶后跳过排空,直接进巡航。
  4. 巡航数组 [2.5, 2.0 × 7]——地板就是 2.0,永不回落,每八轮往上探一次 2.5。
  5. 拥塞窗口增益 4 → 5 倍

第 5 条有个反直觉的地方值得单独说:bbrx_old 的窗口增益和发送倍率是 1:1,看起来很"克制"——但那其实是被 bug 憋小的,是更弱,不是更猛。想照着它抄的人,很容易在这里抄反方向。

换上去当天,几台机器像脱缰的野马。

  • 某音乐站最新的 9 个种子,8 个第一、1 个第二。这个站正是我折腾这一整套东西的起点,以前这是难以想象的。
  • 某影视站的一颗种,所有人下载量都一样,我上传 249.23 GB、分享率 28.3;第二名 134.29 GB、15.3。领先 1.86 倍。

有一点要说清楚:249 GB 是 tracker 记账的有效上传,重传是不算数的。所以这个数字本身就回答了"你是不是只是在疯狂重传"这个质疑——是真抢到的。

第七章:代价,以及又要马儿跑又要马儿不吃草

后来我在一台机器上量了一次:开机四天半,重传率 17.5%;网卡实际发出去 803 GiB,而客户端记账的有效上传是 554 GiB。

稳态的浪费其实有个很粗糙但够用的估算:浪费 ≈ 1 − 1/倍率

变种 巡航均值 大致浪费
bbrz 1.31 ~24%
bbrx_lite 1.56 ~36%
bbrx_old 2.00 ~50%
bbrx_pro 2.06 ~51%

我自己不太在乎总量(竞速嘛),但不是所有人的机器都不计流量。于是有了省流版 bbrx_lite

做它的时候有个原则我觉得挺有意思:

冲刺阶段的凶悍很便宜,巡航阶段的凶悍很贵。

冲刺每条连接就那么几轮,却决定了小种子和短连接的胜负;巡航是按小时持续烧的。

所以 lite 的取舍是——6 倍冲刺原样保留、废 policer 识别原样保留、跳过排空原样保留(这三样收益高、几乎不额外烧流量),然后把主旋钮巡航倍率从 2.06 降到 1.56、窗口增益收回 4、再把"丢包削窗"这道温和刹车装回去。

浪费从约 50% 降到约 36%。还是明显比 bbrz 费,但这是竞速选手能接受的价位。

最后,几句诚实话

一、bbrx_pro 一次改了五处,我没法断定哪一条是主因。 policer 那条是我的主假设,也最能解释 bbrx_old 的历史现象,但严格说只是"最可能",不是"已证明"。真要拆开验证,得一次只改一处重跑。

二、激进档要看带宽。 带宽越低,缓冲区的富余越薄,超发越容易撞上"大规模超时重传、吞吐不升反降"那条红线。我自己的经验是:10G 独服随便上;千兆最多用温和版;2.5G 这档最多到 bbrz。

三、上行已经跑满的机器,激进是亏的。 瓶颈在自己这边的时候,重传占用的正是那条已经跑满的线,每一个浪费字节都直接挤掉一个有效上传字节。这种情况下,省流量的版本才是赚的。


写到这儿其实有点感慨。

一个被用了好些年、被无数人称道"手感好"的东西,真相是两个整数溢出;而这些年里每一个试图改进它的人——包括我自己前两次——都在拧同一个不管用的旋钮。

真正的答案往往不在"调得更极端",而在"看看有什么东西是所有人都没动过的"。