zoukankan      html  css  js  c++  java
  • 为什么SpinLock的实现中应该加上PAUSE指令?

    当spinlock执行lock()获得锁失败后会进行busy loop(参考这段代码),不断检测锁状态,尝试获得锁。这么做有一个缺陷:频繁的检测会让流水线上充满了读操作。另外一个线程往流水线上丢入一个锁变量写操作的时候,必须对流水线进行重排,因为CPU必须保证所有读操作读到正确的值。流水线重排十分耗时,影响lock()的性能。

            inline int rdlock()
            {
              int ret = common::OB_SUCCESS;
              int64_t tmp = 0;
              while (true)
              {
                tmp = ref_cnt_;
                if (0 > tmp || 0 < wait_write_)
                {
                  // 写优先
                  continue;
                }
                else
                {
                  int64_t nv = tmp + 1;
                  if (tmp == (int64_t)atomic_compare_exchange((uint64_t*)&ref_cnt_, nv, tmp))
                  {
                    break;
                  }
                }
              }
              return ret;
            };


    为了解决这个问题,intel发明了pause指令。这个指令的本质功能:让加锁失败时cpu睡眠30个(about)clock,从而使得读操作的频率低很多。流水线重排的代价也会小很多。


    参考这位同学的博客,讲得很好很清晰:

    http://kb.cnblogs.com/page/105657/

    4, Pause指令解释(from intel):

    Description

    Improves the performance of spin-wait loops. When executing a “spin-wait loop,” a Pentium 4 or Intel Xeon processor suffers a severe performance penalty when exiting the loop because it detects a possible memory order violation. The PAUSE instruction provides a hint to the processor that the code sequence is a spin-wait loop. The processor uses this hint to avoid the memory order violation in most situations, which greatly improves processor performance. For this reason, it is recommended that a PAUSE instruction be placed in all spin-wait loops.

    提升spin-wait-loop的性能,当执行spin-wait循环的时候,笨死和小强处理器会因为在退出循环的时候检测到memory order violation而导致严重的性能损失,pause指令就相当于提示处理器哥目前处于spin-wait中。在绝大多数情况下,处理器根据这个提示来避免violation,藉此大幅提高性能,由于这个原因,我们建议在spin-wait中加上一个pause指令。

    名词解释(以下为本人猜想):memory order violation,直译为-内存访问顺序冲突,当处理器在(out of order)乱序执行的流水线上去内存load某个内存地址的值(此处是lock)的时候,发现这个值正在被store,而且store本身就在load之前,对于处理器来说,这就是一个hazard,流水流不起来。

    在本文中,具体是指当一个获得锁的工作线程W从临界区退出,在调用unlock释放锁的时候,有若干个等待线程S都在自旋检测锁是否可用,此时W线程会产生一个store指令,若干个S线程会产生很多load指令,在store之后的load指令要等待store在流水线上执行完毕才能执行,由于处理器是乱序执行,在没有store指令之前,处理器对多个没有依赖的load是可以随机乱序执行的,当有了store指令之后,需要reorder重新排序执行,此时会严重影响处理器性能,按照intel的说法,会带来25倍的性能损失。Pause指令的作用就是减少并行load的数量,从而减少reorder时所耗时间。



    晓楚 (17:40:19):
    这篇文章很好,基本解释清楚了spin_lock中为什么要加pause。中文的,大家都看得懂。
    http://kb.cnblogs.com/page/105657/

      (17:49:28):
    对失败的加锁行为进行惩罚(failure penalty),让等待时间和失败次数成正比,即失败次数越多等待时间越长、执行的pause指令越多。
    这个我们也可以用吗?

    到底最大pause次数是多少需要试验来支撑,目前是4次;个人感觉pause的处理器实现粒度还是比较粗的,应该是intel的一个经验值,接下来的试验可以用nop来代替pause,这样得出来的数据应该会更为平滑一些,控制也更为细腻。
    nop是什么指令?

    晓楚 (17:51:21):
    我觉得没必要,这个做法太复杂了。用pause就足够了。
    nop是让cpu 空转1个clock的指令。什么都不做。

      (17:51:59):
    其实可以测试。
    临界区大小,我们一般是多少代码?

      (17:59:29):
    看他的实验结果,提升并不明显,5%的样子,还是在冲突特别大的情况下,好像不值得。
    要优化spin lock,主要还是避免大量的线程在同一个cache line里spin。我以前测过,要是以浪费内存为代价,可以很轻松提升40%的样子。同样的思路去优化spin_rwlock的读锁(以降低写锁性能为代价),让不同的读者修改自己线程局部的引用计数,读锁性能可以提升几倍。不过现在性能还不在这里。

      (18:02:28):
    恩,支持。现在性能还不在这里。我们保持关注,先从收益大的地方入手。
  • 相关阅读:
    P1131 [ZJOI2007]时态同步(树形dp)
    P2831 愤怒的小鸟(状压dp)
    bzoj2456 / P2397 yyy loves Maths VI (mode)
    P1879 [USACO06NOV]玉米田Corn Fields(状压dp)
    P1026 统计单词个数
    P2679 子串
    数据库合并工具 esql
    在游戏中实现语音聊天和语音转化成文字
    在游戏中实现语音聊天和语音转化成文字
    类型强转的那些坑
  • 原文地址:https://www.cnblogs.com/xinyuyuanm/p/2998585.html
Copyright © 2011-2022 走看看