DeanWang

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

因为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)等等;

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

乱序执行的微结构框图:

 

posted on 2017-06-28 01:40  DeanWang  阅读(756)  评论(0编辑  收藏  举报