E_best 共识下的随机 cycle phase BBR

bbr 的模型和实现是两回事。bbr 需考虑更多现实困扰,从论文看来,这些困扰均来自 tcp 的不完备,比如 delayed ack 对 cwnd gain 的影响。

理论上 pacing rate controller 就足够,但由于反馈环固有的滞后性,还是需要一个 cwnd controller 来兜底,对 cwnd 设置一个 limit。

另一个问题,前面提到过多次的 probe 同步问题。有两个方法可以解决它:

  • 随机化 cycle phase 将同步风险压缩在一轮 cycle 内;
  • 用加法(additive) gain 替代当前实现的乘法(multiplicative) gain。

这类问题只要找到线索,简化算法解决它们比增加新机制解决它们要更高尚。

简单两个修改,先看第一个,用 E_best = max(bw / delay) 替代 max(bw)。这个修改理论上很简单:

#define double ...

static const int bbr_cwnd_gain  = 0x1fffffff; 
 
struct E {
  double bw;
  double delay;
};

struct minmax_sample {
  u32     t;      /* time measurement was taken */
  u32     v;      /* time measurement was taken */
  struct  E v2;   /* value measured */
};

struct minmax {
  struct minmax_sample s[3];
};

// 用此替代 bbr_max_bw 获得最佳而不是最大带宽
static inline double max_E_getbw(const struct minmax *m)
{
  return m->s[0].v2.bw;
}

static inline double max_E_reset(struct minmax *m, u32 t, double meas)
{
  struct minmax_sample val = { .t = t, .v = meas, .v2 = {.bw = 0, .delay = 1} };

  m->s[2] = m->s[1] = m->s[0] = val;
  return m->s[0].v;
}

static double bbr_max_E(const struct sock *sk)
{
  struct bbr *bbr = inet_csk_ca(sk);
  return minmax_get(&bbr->bw);
}
  
/* bbr_update_bw */
  e = bw / rs->interval_us;
  if (!rs->is_app_limited || e >= bbr_max_E(sk)) {
    bbr->bw.s[0].v2.bw = bw;
    /* 初始化并 set new E instance sample:
     *{.t = bbr->rtt_cnt, .v = e, {.bw = bw, .delay = rs->rtt_us}}
     */
    minmax_running_max_E(&bbr->bw, bbr_bw_rtts, bbr->rtt_cnt, e, bw, rs->rtt_us);
  }

难点在 double 类型的处理,内核实现需要使用 BBR_UNIT 缩放,用户态实现直接用即可。

max(bw / delay) 替换 max(bw) 的效果在于它天然抑制了 bufferbloat,正交量 maxbw 和 minrtt 同时测不准的问题就解决了,因为一旦 buffer 膨胀,delay 就增加,bw / delay 不再最大。换句话说,用这个机制获得的 bw 最佳而不一定最大。

还有额外益处。当存在 aimd 流量进行持续(感知 rtt 增加超过一个 probertt 周期) capacity-seeking 行为导致基础 buffer 膨胀时,由于 bbr 取消了 cwnd 束缚,同时其 probe 可增加 bw / delay,就自适应了与 aimd 共存时 deep buffer 场景吃亏的问题,若 aimd 流退出,继续 probe 将导致 bw / delay 下降,bbr 随即停止主动 buffer 抢占,最多等到再下一个 probertt 回归常态。

为何要等再下一个 probertt?这问题在于 aimd 流的侵入本就不是 bbr 能控制的一部分,bbr buffer 膨胀由于 aimd 入侵后的随行 minrtt 导致,aimd 退出却导致 bbr 采集到新的 minrtt,这已经是 bbr 随行后的基础 minrtt,aimd 已退出,再没有外力促使 bbr 基础 queue 排空,只能再等到 probertt。

第二个修改,随机化 cycle phase。这个更容易,每一个 cycle 开始时,重置 bbr_pacing_gain 即可,也非常容易模拟,主要引入一个 reset_pacing_gain,每经过 CYCLE_LEN 个 round 调用一次:

#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <unistd.h>

#define BBR_UNIT 4
#define CYCLE_LEN 8
#define ARRAY_LENGTH(arr) (sizeof(arr) / sizeof(arr[0]))

static int bbr_pacing_gain[] = {
        BBR_UNIT * 5 / 4,        /* probe for more available bw */
        BBR_UNIT * 3 / 4,        /* drain queue and/or yield bw to other flows */
        BBR_UNIT, BBR_UNIT, BBR_UNIT,        /* cruise at 1.0*bw to utilize pipe, */
        BBR_UNIT, BBR_UNIT, BBR_UNIT        /* without creating excess queue... */
};

static void reset_pacing_gain()
{
    int idx = rand() % 8, i;
    int len = ARRAY_LENGTH(bbr_pacing_gain);
    for (i = 0; i < len; i++) {
        if (i != (idx + 1) % len)
            bbr_pacing_gain[i] = BBR_UNIT;
        if (i == idx) {
            bbr_pacing_gain[i] = BBR_UNIT * 5 / 4;
            bbr_pacing_gain[(i + 1) % len] = BBR_UNIT * 3 / 4;
        }
    }
}

static void print_pacing_gain()
{
    int len = ARRAY_LENGTH(bbr_pacing_gain), i;
    for (i = 0; i < len; i++) {
        printf("%d ", bbr_pacing_gain[i]);
    }
    printf("\n");
}

int main(int argc, char **argv)
{
    int i;
    for (i = 0; i < 64; i++) {
        if (i % CYCLE_LEN == 0) {
            srand(time(NULL));
            reset_pacing_gain();
            print_pacing_gain();
            sleep(1);
        }
    }
}

还有一个关于 probertt 的细节,不过没什么意思。

不尚贤,使民不争,不拔优,小经理不舔,不淘汰,则牛马不卷。

浙江温州皮鞋湿,下雨进水不会胖。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值