false sharing及使用vtune验证

softarts 2009-06-01 07:20:01
加精
多核开发中常见的一个问题是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
...全文
423 30 打赏 收藏 转发到动态 举报
写回复
用AI写文章
30 条回复
切换为时间正序
请发表友善的回复…
发表回复
zhangqun2206951 2010-02-22
  • 打赏
  • 举报
回复
经典太棒了,very good!
zhangqun2206951 2010-02-22
  • 打赏
  • 举报
回复
dell有几款四核的笔记本?
lynsan 2010-01-25
  • 打赏
  • 举报
回复
支持,谢谢
周睿 2010-01-23
  • 打赏
  • 举报
回复
mark
fuyong8904 2010-01-23
  • 打赏
  • 举报
回复
.............
sxthcj2000 2010-01-22
  • 打赏
  • 举报
回复
谢谢分享
hnjzjdd 2010-01-21
  • 打赏
  • 举报
回复
顶个!!!!!!1
chihongran 2010-01-21
  • 打赏
  • 举报
回复
taihnel
wudingfeng 2010-01-21
  • 打赏
  • 举报
回复
好啊
ij111223 2010-01-21
  • 打赏
  • 举报
回复
学习中
mikk2007 2010-01-21
  • 打赏
  • 举报
回复
受教了 非常感谢
power199 2010-01-21
  • 打赏
  • 举报
回复
是的。至于量是多少,你想以什么为单位来明确呢?
wangzai968 2010-01-20
  • 打赏
  • 举报
回复
受教了 非常感谢
paul_1982 2010-01-20
  • 打赏
  • 举报
回复
是的。至于量是多少,你想以什么为单位来明确呢?
intel_www 2009-07-07
  • 打赏
  • 举报
回复
[Quote=引用 11 楼 cls555 的回复:]
是不是说,只有达到一定的量级才适合用openmp
那么这个量是多少,能不能明确出来
[/Quote]

是的。至于量是多少,你想以什么为单位来明确呢?
cls555 2009-07-03
  • 打赏
  • 举报
回复
是不是说,只有达到一定的量级才适合用openmp
那么这个量是多少,能不能明确出来
cls555 2009-07-03
  • 打赏
  • 举报
回复
[Quote=引用 9 楼 intel_www 的回复:]
你这个性能测试程序计算量实在是太小了。我简单地测试了一下openmp和单线程版本,当元素数目达到5000万时整个copy的时间为:
openmp 2个线程:  0.307091l
单线程: 0.395266l

在用Intel Thread Profiler看了一下,真正的copy操作在很短的时间内就完成了,比线程带来的额外开销还要小。不过即使这样也没有出现你说的openmp版比顺序执行慢的情况。
[/Quote]

你好,我的测试量没有达到万级那么大,我只测过100 - 1000之间的值
线程带来的额外开销要远大于copy操作,实际测试的结果(Vopenmp <Vtbb <V顺),不知道是不是我哪里设置不太对,请指教
intel_www 2009-06-30
  • 打赏
  • 举报
回复
你这个性能测试程序计算量实在是太小了。我简单地测试了一下openmp和单线程版本,当元素数目达到5000万时整个copy的时间为:
openmp 2个线程: 0.307091l
单线程: 0.395266l

在用Intel Thread Profiler看了一下,真正的copy操作在很短的时间内就完成了,比线程带来的额外开销还要小。不过即使这样也没有出现你说的openmp版比顺序执行慢的情况。
cls555 2009-06-25
  • 打赏
  • 举报
回复
//初始化变量
size_t i=0, j=0;
size_t nsize = vecSrc.size(); //vecSrc里面存放的是字符串(类型是 vector<string>),例如"aaa","bbb"
vector<char> vecLine;
vector<size_t> vecDict;
vecLine.assign(nsize, 0);
vecDict.assign(nsize, 0);

//设置时间计数器
LARGE_INTEGER litmp;
LONGLONG QPart1,QPart2;
double dfMinus, dfFreq, dfTim;
QueryPerformanceFrequency(&litmp);
// 获得计数器的时钟频率
dfFreq = (double)litmp.QuadPart;
QueryPerformanceCounter(&litmp);
// 获得初始值
QPart1 = litmp.QuadPart;

#ifdef USE_PARALLEL
#ifdef USE_OMP
#pragma omp parallel for shared(vecLine, vecSrc)
for (int ai=0; ai<nsize; ai++)
{
vecLine[ai] = vecSrc[ai].at(0);
}
#else
VecInit tVI;
tVI.pVectLine = &vecLine;
tVI.pVectSrc = &vecSrc;
parallel_for(blocked_range<size_t>(0,nsize), tVI, auto_partitioner());
#endif
#else
for (int ai=0; ai<nsize; ai++)
{
vecLine[ai] = vecSrc[ai].at(0);
}
#endif

QueryPerformanceCounter(&litmp);
QPart2 = litmp.QuadPart;
dfMinus=(double)(QPart2-QPart1);
dfTim=dfMinus/dfFreq; //运行的时间结果
///////////////////////////////////////////////////////////////////////////////////

//VecInit的声明
class VecInit {
public:
void operator()( const blocked_range<size_t>& r ) const
{
for(size_t i=r.begin(); i!=r.end( ); ++i)
{
(*pVectLine)[i] = (*pVectSrc)[i].at(0);
}
}
public:
vector<char> *pVectLine;
const vector<string> *pVectSrc;
};
///////////////////////////////////////////////////////////////////////////////////




intel_www 2009-06-23
  • 打赏
  • 举报
回复
[Quote=引用 6 楼 cls555 的回复:]
我写了个小例子实测一下:
从vector里面复制到另一个vector
并行程序速度均比顺序程序要慢(Vopenmp <Vtbb <V顺),基本上 V顺=10*Vopenmp=1.5到3*Vtbb,随着vector的size的变大差值还有变大的趋势
如果不能很好的解决这个问题,多核编程意义不大,如程序需要并行处理,一般的线程库已足够使用
[/Quote]

能否把测试程序贴上来瞅瞅?
加载更多回复(6)

566

社区成员

发帖
与我相关
我的任务
社区描述
英特尔® 边缘计算,聚焦于边缘计算、AI、IoT等领域,为开发者提供丰富的开发资源、创新技术、解决方案与行业活动。
社区管理员
  • 英特尔技术社区
  • shere_lin
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧