前阵子我发过一个关于闩锁与SPINLOCK的帖子,其中简单的提到了CPU的PAUSE指令对SPINLOCK的影响。当自旋锁无法获得SPINLOCK的时候,已经占用了CPU的线程没必要放弃CPU进入休眠,等待再次尝试获得SPINLOCK,因此会在该CPU上自旋。不过对于争用比较严重的系统,自旋多次后,有可能仍然无法获得SPINLOCK。因为自旋时该线程需要持有CPU的串行锁,因此,对于具有多个线程(比如至强芯片是双线程的)的CPU来说,这种长时间自旋而无法获得SPINLOCK的情况会导致另外一个CPU线程也被锁死,无法执行。为了解决这个问题,LINUX针对SPINLOCK的算法做了一些优化。
我们可以看到_raw_qspin_lock首先通过lock cmpxchg来获得spinlock,如果当前SPINLOCK无人持有,则直接获得SPINLOCK,就调用queued_spin_lock_slowpath来继续自旋。今天篇幅有限,我们不讨论详细的细节。直接看queued_spin_lock_slowpath的代码。
在这段代码中,就可以看到多出存在PAUSE指令的地方。通过PAUSE,该线程可以暂停几个时钟周期,让该CPU中的其他线程可以获得CPU的公共资源来执行其他任务。上回我也说过,美团团队的一篇论文中探讨了不同的X86 CPU因为PAUSE的时钟周期的不同对SPINLOCK性能会产生影响。发现这个问题的原因是他们的MYSQL数据库在更换了新的SKYLAKE服务器之后,QPS反而下降了15%。
正好今天我也在分析一个MYSQL数据库的问题,通过perf发现ut_delay的比例比较高。