关于多核 CPU 自旋锁 (spinlock) 的优化

原文链接: https://yq.aliyun.com/articles/698642

CPU的总线为铜薄膜,虽然摩尔定律使单位面积晶体管的密度不断增加,但是对于连接导线的电阻却没有明显的下降,导线的RC延迟几乎决定现有CPU性能,因此我们会看到传输数据在CPU的角度来看是个极为沉重的负担。我们看到intel 为了引入更多的CPU核心,从Skylake开始芯片总线由上一代的 ring-bus 转变为 2D-mesh, 虽然2D-mesh为数据提供了更多的迁移路径减少了数据堵塞,但也同样为数据一致性带来更多问题,例如过去ring-bus 结构下对于存在于某个CPU私用缓存的数据争抢请求只有两个方向(左和右), 但是在2D-mesh环境下会来自于4个方向(上,下,左,右),同时大家不久会看到更多CPU socket的服务器将会出现,为了优化现有的和将来会出现的自旋锁问题,我们开展了自旋锁的优化工作,在代码中具体包含了以下优化方法:

  1. MCS spinlock
    MCS 优化原理( 最新的Linux Kernel也采用了MCS)能够减少锁在不同CPU核心之间的迁移

具体参照:http://www.cise.ufl.edu/tr/DOC/REP-1992-71.pdf

  1. Critical Section Integration (CSI)
    本质上自旋锁产生的效果就是一个CPU core 按顺序逐一执行关键区域的代码,所以在我们的优化代码中将关键区域的代码以函数的形式表现出来,当线程抢锁的时候,如果发现有冲突,那么就将自己的函数挂在锁拥有者的队列上,然后使用MCS进入spinning 状态,而锁拥有者在执行完自己的关键区域之后,会检测是否还有其他锁的请求,如果有那么依次执行并且通知申请者,然后返回。可以看到通过这个方法所有的共享数据更新都是在CPU私用缓存内完成,能够大幅度减少共享数据的迁移,由于减少了迁移时间,那么加快了关键区域运行时间最终也减少了冲突可能性。

具体参照:https://users.ece.cmu.edu/~omutlu/pub/acs_asplos09.pdf

  1. NUMA Aware Spinlock (NAS)
    当前服务器多CPU socket (2/4/8) 给我们提供了更好的性能/功耗比值,但由于CPU片外的数据传输非常昂贵(慢),也引入了更加复杂的一致性需求。 所以我们引入了分布式一致性机制来减少锁的缓存行在不同CPU socket 之间的迁移,最终加速性能。

具体可以参照:https://www.usenix.org/system/files/conference/atc17/atc17-kashyap.pdf

  1. Mutex Schedule per core
    当线程数大于CPU核数的时候, 在自旋锁的场景下由于操作系统的CFS调度,那些没有抢到锁的线程

或者刚刚开始抢锁的线程会不断争抢锁的拥有者和已经处于spinning 状态线程的CPU资源。为了解决
这个问题我们为每一个CPU core 引入mutex, 这样在任何一个阶段只有一个线程可以进入spinning 状
态或者成为锁的拥有者。由于mutex 锁总是存在CPU core 的私用缓存,另外第一个获取mutex锁的线
程数据不会引入任何的系统调用,最终解决了OS带来的问题,数据表明该方法大大提升了整体性能。

  1. Optimization when NUMA is ON or OFF
    虽然NUMA ON可以减少程序访问内存的延迟,但是如果有些程序需要更大的内存带宽,NUMA OFF也是很好的选择。因此在本次优化中我们的代码同时考虑了 NUMA ON 和 NUMA OFF

以下是具体提升数据(与GLIBC pthread_spinlock 对比):
关于多核 CPU 自旋锁 (spinlock) 的优化_第1张图片

使用同样的测试代码,由上图2个CPU socket 变为4个CPU socket(即使是NUMA off), 我们看到了又再次动态的提升了一倍的比例(由96核心的15倍变为192核心的30倍),说明我们的优化持续有效减少了数据和锁的迁移,最终提升性能。
关于多核 CPU 自旋锁 (spinlock) 的优化_第2张图片

最新代码:http://gitlab.alipay-inc.com/eff/numa-spinlock

在进行软件优化的同时,我们看到仍然存在不必要的数据迁移,而这些问题必须有对应CPU硬件指令,为了彻底解决自旋锁问题,我们已经提出了CPU优化方案, 通过该方案可以达到自旋锁的理论值。

虽然性能得到提升,但是我们希望着重说明,如果能够避免使用这个技术,即避免产生由于同步导致的数据传输,才是最佳方案。举例说明:如果一个任务可以并行化并且我们有64个CPU核心, 但他们之间只有1%的串行化代码(如即使性能达到理论值的Spinlock操作), 那么根据 阿姆达尔法则,我们的吞吐量提升度是: T/(0.99T/64 + 0.01T) = 1/(0.99/64 + 0.01) = 39.26, 也就是说64个核心只给了我们40个核心的吞吐量,因此spinlock会严重影响吞吐量。越来越多的经验提醒我们,我们学习技术的目的也许是为了理解事物的规律,从而避免使用这些技术,例如如果使用好的设计从而避免产生同步,不使用spinlock的吞吐量一定是最大的,对么?

你可能感兴趣的:(关于多核 CPU 自旋锁 (spinlock) 的优化)