现在的位置: 首页 > 综合 > 正文

多核程序探秘(1) false sharing及使用vtune验证

2013年09月12日 ⁄ 综合 ⁄ 共 2148字 ⁄ 字号 评论关闭

多核开发中常见的一个问题是false sharing(失效共享),这个问题让我们用一个全新的角度来看待多核程序的编写,这个角度就是硬件的角度。

 

Intel Core 2 Duo处理器平台上, L2 cache是由两个core共享的,而L1 data cache是分开的,由两个core分别存取。cache line的大小是64 Bytes。当不同的线程同时读写不同的,看起来更不相关的2个变量时,由于这2个变量实际保存在同一条cache line上,从而会暗地里造成cache line的访问冲突而导致潜在的性能损失。例如这段代码:

unsigned char VectorA[10];
unsigned char VectorB[10];

 

UINT MyThreadProcA( LPVOID pParam )
{
     unsigned long long myCounter = 100000000;
     while(--myCounter)
     {
         for (int i=0; i<10; ++i)
         {
             ++VectorA[i];
         }
     }
    return 0;   // thread completed successfully
}

 

UINT MyThreadProcB( LPVOID pParam )
{
     unsigned long long myCounter = 100000000;
     while(--myCounter)
     {
         for (int i=0; i<10; ++i)
         {
             ++VectorB[i];
         }
     }
    return 0;   // thread completed successfully
}

 

尽管MyThreadProc[A/B] 是两个不同的线程,访问的也是两个不同的变量,但是,false sharing却真真实实的发生了。当MyThreadProcA更新VectorA[i]的时候,对应的Core A上的cache line同时被更新,变为modified状态,而这个cache line中又保存了VectorB[i]的一份copy,因此,另一个Core B中的cacheline就会变为失效状态(invalid),CPU会不得不通过cache protocol(cache的同步协议)去通知Core B上的cache line同时更新VectorB的数据,这样,尽管MyThreadProcA没有修改VectorB,却会导致MyThreadProcB线程访问VectorB时cache miss增加!而我们知道,cache的访问速度是普通内存的10倍,cache miss增大自然会造成明显的性能下降!

 

在Core2平台上,可以使用EXT_SNOOP.ALL_AGENTS.HITM 事件来评测false sharing的影响,它监测的是总线(内存总线)传输的情况,如果HITM事件发生,则表明总线上响应端的cache正处于修改状态,这恰恰反映了false sharing问题的根源。

vtune的手册对于EXT_SNOOP.ALL_AGENTS.HITM 的解释的:

This event counts the snoop responses to bus transactions. Responses can be counted separately by type and by bus agent. With the 'THIS_AGENT' mask the event counts snoop responses from this processor to bus transactions sent by this processor. With the 'ALL_AGENTS' mask the event counts all snoop responses seen on the bus.

 

先看看看看上面这段代码的测量结果吧!


 

采用sampling测量,EXT_SNOOP.ALL_AGENTS.HITM 发生次数1175次,CPU_CLK 为6373,INST_RETIRED为3796

 

false sharing的解决也很简单,只要把共享的数据放到不同的cache line中即可,例如,将代码改为:

 

unsigned char VectorA[100];
unsigned char VectorB[100];

 

这样,使用的仍然是VectorA[0~9]和VectorB[0~9],VectorA[10~99]则充当了一个pad占位符,把同一条cache line(64bytes)占满。

 

解决false sharing问题后的测量数据为:

 

 

EXT_SNOOP.ALL_AGENTS.HITM 显著降到179次,CPU_CLK 降为1847,由于指令个数没有太大变化,INST_RETIRED为3370。通过程序中内嵌计时函数的方法也能得到接近的结果。

 

总结,解决false sharing问题的方法:

1. 增大数组元素的间隔使得由不同线程存取的元素位于不同的cache line上
2. 在每个线程中创建全局数组各个元素的本地拷贝,然后结束后再写回全局数组

false sharing是多核程序开发的常见问题,需要引起程序员们的重视。

抱歉!评论已关闭.