• 《为什么在多核处理器下需要内存屏障(MenmoryBarrier)?》


    因为CPU的乱序执行技术虽然可以极大的提高流水线的工作效率,但是导致了实际运行次序和program的次序不一致,如果不做多余的防护措施,在逻辑次序上最后写入内存的数据未必最后写入。 也就是说,如果你期望最后写入一个标记数据表示前面的数据都已经准备好,然后在另外一个核心上依靠判断这个标记来判定一些数据就绪,这个策略并不可靠;

    比如以下代码:

    char* g_Data = nullptr;
    
    //processor 1 code:
    char* pTemp = new char[1000];
    g_Data = pTemp;
    
    //processor 2 code:
    if(g_Data != nullptr)
    {
        //do something...
    }

    这样的代码,在核心2上进行判断是不准确的,有可能核心1上的g_Data的值已经不是nullptr了,但是真正的1000个char还没有new出来,也就是说核心1上的g_Data保存着pTemp[0]的地址(标记位先被存到g_Data里面了),但是后面999个char还没真正开辟出来,这样,在核心2上进行访问就会造成程序crash。

    也就是说,在一个核心写入内存的数据未必真的最后写入,核与核之间作为一个整体来看的话,不能保证self-consitent。

    解决的办法是在标记为被写入前,强迫CPU串行化(也就是在上面demo中g_Data被赋值前强行CPU串行化):

    char* g_Data = nullptr;
    
    //processor 1 code:
    char* pTemp = new char[1000];
    MemoryBarrier();//add barrier, different platform has different API
    g_Data = pTemp;
    
    //processor 2 code:
    if(g_Data != nullptr)
    {
        //do something...
    }

    这样,在核心1上的g_Data被赋值前pTemp的内存就会是完整开辟出来的,就不会对核心2上的代码在访问g_Data的时候产生crash隐患。

    另:当CPU要访问的内存存在于cache的时候,像Interxxx(如InterLockedIncrement等)这样的原子操作命令是不会被发送到总线上的,取而代之会锁住cache。加锁,其实也是会锁住cpu的cache的,如果此时该核心又被让出去其他线程使用,那么这些原有的cache会被清掉,所以大量的锁也有这个副作用。

    PS:关于CPU的推测执行技术和乱序执行技术:

    推测执行技术:处理器为了提高性能,会去提前猜测接下去需要执行什么动作,然后提前执行,如果发现推测错误,则回滚至正常状态。通常应用的推测执行技术都能达到80%-90%的推测准确率。需要指出的是,推测执行技术是一个大类技术的统称,包括分支预测,预读取,推测性内存访问,缓存缺失的重叠/乱序处理(MESR)等等;

    乱序执行技术:当处理器遇到需要发生停顿的时间时(例如需要装在的数据发生了混存确实,需要去高延迟的内存中查找),处理器可以越过这个停顿事件,继续超前执行指令。同样,如果超前执行中发生了错误,也需要回滚至正常状态。

    乱序执行的微结构框图:

  • 相关阅读:
    【BZOJ2599】[IOI2011]Race 树的点分治
    【BZOJ1787】[Ahoi2008]Meet 紧急集合 LCA
    【BZOJ1834】[ZJOI2010]network 网络扩容 最大流+最小费用流
    【BZOJ3012】[Usaco2012 Dec]First! Trie树+拓补排序
    【BZOJ2743】[HEOI2012]采花 离线+树状数组
    【BZOJ2946】[Poi2000]公共串 后缀数组+二分
    【BZOJ2157】旅游 树链剖分+线段树
    【BZOJ2661】[BeiJing wc2012]连连看 最大费用流
    【BZOJ1801】[Ahoi2009]chess 中国象棋 DP
    【BZOJ4236】JOIOJI STL
  • 原文地址:https://www.cnblogs.com/DeanWang/p/7087959.html
Copyright © 2020-2023  润新知