闲来check一下Linux TCP实现近期的patch,有一个即将进入4.15内核的让我比较感兴趣,TCP终于将传输队列和重传队列分离了开来。
曾经,这两个队列是重合的,就像下面这样:
然而,在4.15内核,却变成了下面的样子:
重传队列成了一棵单独的红黑树,只要是发送过而没有被ACK确认(即没有被UNA指针覆盖)的数据包,均从传输队列断开而加入一棵红黑树。我接下来简单说一下这个改变为什么是一件好事。
如果重传队列仅仅是一个链表,那么如果想在其中找到某一个数据包的话,除了遍历别无他法。而我们知道,遍历操作的时间复杂度是O(n)
O(n),是n非常大的时候,这将是一个比较耗时的操作。我们再来想一下什么时候需要遍历。很简单,就是在收到携带SACK选项的ACK段时,需要在重传队列中找到那些数据包是被SACK的,然后标记它们,如果重传队列非常长,那么遍历操作将会消耗很多的CPU时间。
如补丁说明中所述,以往,人们以各种各样的理由抵制对这个遍历操作进行优化,其归根结底无非就是优化带来的收益很小,但却增加了很大的实现复杂性,优化带来的收益很小的原因无非仅仅在于“一般而言,重传队列都很短!”。这么想是对的,但也只是曾经。因为曾经的网络带宽是很低的,同时对端的接收缓存也是很小的,这意味着TCP的拥塞窗口以及接收窗口都不会太大。如果说重传队列很短,当然不必去故弄玄虚将重传队列独立起来作为一个可以高效查找的数据结构存在了。
但,现在的情况和以前不一样了。目前的网络带宽动辄10G以上,机器内存动辄16G以上,TCP拥塞控制算法也迎来了BBR新时代,因此TCP的发送窗口自然而然也就剧增了,随之TCP的重传队列自然而然变得非常之长。人们一向不太在意的问题,此时便显得尤为重要,优化这个遍历数据结构迫在眉睫。因此将其从链表独立成一棵红黑树便是自然而然的事情了。
在去年的时候曾经想过这个问题,但当时由于几乎没有碰到过真实情况下10G以上的带宽,就觉得这是没有意义的,直到现在我也没碰到过哪怕超过1G的公网TCP传输带宽,所以这个优化依然显得没有必要,然而这个patch看起来是如此的有道理,而且也是自己曾经考虑过的,为它多写几笔是应该的。
除了高效的查找之外,在用红黑树替代链表之后,我觉得还有一个好处,那就是简化了整个tcp_sacktag_write_queue函数以及其调用函数的代码。曾经何时,这些函数让多少人头疼不已,用红黑树替代链表操作之后,相信代码会清晰很多,自然很多人就很容易看懂了。