我对eBPF的偏见

台风天气,适合饮酒作文。

我并不反对eBPF技术本身,相反,我很喜欢eBPF并且是它的粉丝,我反对的是对eBPF的滥用。

上周听了一个关于eBPF技术扩展spinlock的讨论,基于以下这个paper:
https://www.sigops.org/s/conferences/hotos/2021/papers/hotos21-s08-park.pdf

大致说的是,可以使用eBPF代码控制一个spinlock争锁者将自己排在什么位置。

先说结论,在spinlock上应用eBPF,我是持不同看法的。

跟同事讨论了这个事情,用eBPF扩展spinlock的排队策略,有点本末倒置,没有get到事情的本质。

说说我的观点。

首先,spinlock保护的临界区代码一定很短,如果你把这些代码写的冗长,那就是对spinlock的误用。基于这个假设,如果争锁者很少,任何策略都没必要,稍微等下就好。另一方面,如果争锁者很多,那必然是大量CPU在spin,这意味着你的代码在多核并行环境中出现了串行路径,这时应该做的是重构你的代码,把锁粒度拆细,而不是去插队,即便你的进程优先级很高。

其次,公平性问题得不到保证,没有仲裁者,甚至任何有注入eBPF代码权限的用户都可以注入自己的策略。当然,这是一个技术问题,可以通过控制eBPF代码的注入逻辑来保证只能有一个代码被注入,然而这意味着事情将进一步复杂化,这是典型的一个问题引出了另一个问题。关于这个公平性问题,还有一个case,后面说。

最后,spinlock本身承受不住复杂的逻辑。spinlock的目标很简单,就是保护短代码临界区,短代码就是指令很少,如果spinlock本身的加锁指令超过甚至大大超过了临界区代码的指令本身,是不是尴尬了呢?显然,增加的这部分指令在整机性能分析中,是无用的指令,这无疑会损耗性能。一个极端点的例子,一个CPU已经执行完临界区代码并且unlock,另一个CPU还在eBPF代码里从map里load数据。

服务员永远都不能比顾客多,如果服务员比顾客多了,那一定是店面的运营出现了问题。

这是我一直都强调的观点,管理开销一定做减法。还是那两个老掉牙的例子,3000米的摩天大楼建不成是因为电梯的开销太大,另一个例子,一个64位进程内存如果是稀疏的,它的页表项将消耗大量物理内存。

另一个例子,来自我的同事宋牧春,说的是同一类问题,降低管理开销:
https://mp.weixin.qq.com/s/H4CWwG1qURTIyoqEi7Bp7w

这篇paper初看起来是一个很有意思的技术点,给人眼前一亮的感觉,但稍微离远一点,而不是仅仅从eBPF技术角度看,这种做加法的设计明显有问题,当然了,我说的是对spinlock不该引入这种复杂性,但是对于mutex,其本身的管理开销就很大,加入eBPF扩展逻辑并没有引入更多的复杂性,相对比较make sense。

单纯从eBPF技术看,它对spinlock做了加法,显示了自己的能力,然而整体上,这个加法是有损的(eBPF的粉们不太乐意看到这句话)。但凡做加法的,都会提升故障的概率而不是降低。

就这个问题再站远一点,来看看eBPF和内核的关系。

eBPF受到关注,很大的因素来自于它是做加法的好手,我曾经将它看作是瑞士军刀,但后来发现并不应景,我觉得狗皮膏药更合适。

如果你手里有一把瑞士军刀,你的关注点就是这把瑞士军刀可以解决的问题,比如开瓶盖,拧螺丝,用完即收好。但如果你是卖狗皮膏药的,光把膏药卖出去还不行,狗皮膏药必须贴在某个部位才算完。

高效意味着简单,eBPF做加法的本质让系统无法保持简单。当然了,你可能会反驳我的观点,说通用内核本身就不是为了高效的,但就事论事的话,本文这个case,在spinlock中引入eBPF,就是不对。

把轿车造的坚固一些可以在发生交通事故的时候降低伤亡率,因此车子会变得越来越坚固吗?很多低端车商确实朝这个方向走,他们明显是颠倒了目的和手段。目的是降低伤亡率,而不是把车造坚固。需要做的事情是从整体考虑安全性,比如碰撞发生后,车要怎么做才能最大限度保护乘客,但如果你是个卖轻固材料的或者你是车厂材料部门的经理,你眼里的关注点就只有加固。

造一辆少出事故的车是目标,造一辆出了事故撞不坏的车不是目标。

eBPF提升内核的能效是目标,它要发挥的是瑞士军刀的作用,如果只是为了eBPF而eBPF,那它就是狗皮膏药,你只是为了把它贴到身体的某个地方而已,并且你也会一直找这样的地方,找准机会就贴上去。另一句意思相同的话,大概是如果你有个锤子,那么眼里什么都是钉子。这就是很难有整体视角的原因。

最后,有点跑题但又有些关联,来看看公平性问题。和QUIC的拥塞控制有关。

有人问过我,QUIC的实现是一个用户态的库,是不是每个人都可以写自己的拥塞控制算法了,比如固定大窗口猛发包,或者更加粗暴的,每个数据包发三遍。我说是的。

那为什么内核态实现的TCP拥塞控制就没有这个问题呢?事实上也有,但问题并不严重,因为大家觉得写内核模块有一点门槛,所以很多人望而却步了。以至于国内搞TCP拥塞控制优化的也就这几个人(我也是其中之一)。事实上,无论是内核还是用户态,写一个自己的拥塞控制算法非常简单,编写内核模块也没有什么门槛。

这就和用eBPF扩展策略是一样的,没有仲裁,因此每个人都可以定制自己的策略。很难将全网统一成同一个拥塞控制算法,因此这里面就会有很多博弈,如果保证拥塞控制的公平性,也一直是被研究的课题,但遗憾的是,没有什么好的方法。


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

上一篇:【C# 线程】.NET 中的轻量级线程安全


下一篇:【无标题】