多核开发中常见的一个问题是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是多核程序开发的常见问题,需要引起程序员们的重视。
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/softarts/archive/2009/06/01/4232467.aspx