这篇文章给出了对于老古董技术Slow Start的诸多现有性能问题评测分析,
并最后提出一种叫Hybird Slow Start (Hystart)的技术来尝试解决分析的一些问题。
这篇文章是理解最新的slow start技术的关键文章。
现有Slow-Start的性能问题
长肥网络[1]中的TCP性能问题是现代TCP设计的一个重要领域。这不,连那么naive的slow start都在长肥网络中遇到的诸多性能问题。
a. 标准的Slow Start算法可能引起严重的网络丢包
在ssthreshold值设置的过高时,慢启动一段时间后,cwnd的指数增长会显得过快。
有可能在上一个RTT,cwnd刚好等于BDP;下一个RTT,cwnd就等于2BDP了。
这样就可能导致多出的一个BDP的数据包被丢弃。这类丢包属于burst packet losses
b. Slow start可能导致系统负载过高,尤其是在使用SACK机制时
关于SACK选项可能引起CPU utilization过高的问题已经有了普遍的认识[2]
这篇论文给出的一个数据倒是令我比较感兴趣:
The problems consistently occur in all three dominant operating systems,
Linux, Windows XP and FreeBSD during more than 40% of slow start runs
in large BDP networks.
解决Slow Start问题两个明显的思路:
1. 继续优化SACK的处理机制,提高遍历重传队列的效率
-- 这也是目前较热的一个研究和工程趋势
2. 解决slow start可能带来的burst losses,进而也能缓解SACK性能问题
-- 这是这篇论文所主要关心的思路
吐个小槽:这篇论文的数据来看,Window XP被黑的不轻啊,╮(╯▽╰)╭
Hybird-Slow-Start
本文提出一种叫做Hybird Slow Start的算法来解决上述问题,论文中有不少精妙的
理论分析,这里就不再赘述。
主要想解释一下packet train是个什么样的概念。
举个栗子,下图是我理解的一个packet train的样子,[Packet1]这些是表示一个具体
的数据包,’-‘用于表示两个数据包之间的发送时间间隔。
那么这样一个packet train能用来干嘛呢?计算带宽利用率!
具体来说,图中四个数据包的数据总量可以求和得到,然后总的时间间隔也可以得到
(timestamp4 - timestamp1)。
[Packet1] -- [Packet2] ------ [Packet3] ---- [Packet4]
那这篇论文就利用的类似的思路,通过ACK train信息判断一个Safe Exit Point
for Slow Start. 同时为了避免计算带宽,通过一个巧妙的转换(具体建议看论文),只要判断时间差
是否超过Forward one-way delay(Dmin)来决定是否退出慢启动。
总的来说,Hystart算法可以总结如下
Hystart算法通过以下两个技术来判断是否应该退出慢启动
1. 一个窗口内的数据包的总传输间隔是否超过Dmin
2. 数据包的RTT sample是否出现较大幅度的增长
论文给出的伪代码如下图所示
内核源码分析
从上面的hystart算法伪代码可以看出,hystart算法主要包括两个方面:
a. 在每个RTT区间开始时,重置hystart算法相关变量
b. 对于每个收到的ACK,根据ack train算法和RTT波动来判断是否应该退出slow start
在Linux 3.10内核中,hybird slow start算法被集成在了默认的CUBIC算法中。
在tcp_cubic.c文件中有两个函数分别对应以上两个步骤,
分别是bictcp_hystart_reset()和hystart_update()。
这两个函数几乎是严格按照上述伪代码来实现的,所以也就没有接着展开解释的必要。
下面主要从整体的处理逻辑方面来分析源码
// cubic算法的关键数据结构
static struct tcp_congestion_ops cubictcp = {
.init = bictcp_init, // 在TCP流建立之初调用
.ssthresh = bictcp_recalc_ssthresh,
.cong_avoid = bictcp_cong_avoid,
.set_state = bictcp_state,
.undo_cwnd = bictcp_undo_cwnd,
.pkts_acked = bictcp_acked, // 每次收到ACK数据包后调用
.owner = THIS_MODULE,
.name = "cubic",
};
上面是cubic算法的关键结构体的初始化部分,bictcp_init()负责TCP流建立时的初始化动作,
bictcp_acked()负责在每次收到ACK包后,更新相关信息。所有hystart算法上面的两个
函数也分别是在cubic的这两个函数中。下面依次看cubic中这两个函数的实现
static void bictcp_init(struct sock *sk)
{
bictcp_reset(inet_csk_ca(sk));
if (hystart) // 如果开启(默认开启),则进行hystart相关变量的初始化
bictcp_hystart_reset(sk);
if (!hystart && initial_ssthresh)
tcp_sk(sk)->snd_ssthresh = initial_ssthresh;
}
static void bictcp_acked(struct sock *sk, u32 cnt, s32 rtt_us)
{
...
/* first time call or link delay decreases */
if (ca->delay_min == 0 || ca->delay_min > delay)
ca->delay_min = delay;
/* hystart triggers when cwnd is larger than some threshold */
if (hystart && tp->snd_cwnd <= tp->snd_ssthresh && tp->snd_cwnd >= hystart_low_window)
hystart_update(sk, delay);
}