今天通过酷壳一篇推荐了解了一下 Cache 的“伪共享”(False Sharing). 写了小程序做了个简单的测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | //! false_sharing.cpp #include <iostream> #include <pthread.h> #include <sys/time.h> using namespace std; template <size_t NPAD, size_t NTH = 4, size_t NLOOP = (1<<30)> class FalseSharing { public: void run() { struct timeval b, e; gettimeofday(&b, NULL); pthread_t threads[NTH]; for (size_t i = 0; i < NTH; ++i) { pthread_create(&threads[i], NULL, hook, reinterpret_cast<void*> (i)); } for (size_t i = 0; i < NTH; ++i) { pthread_join(threads[i], NULL); } gettimeofday(&e, NULL); cout<<"padding bytes: "<<NPAD<<endl;; cout<<"thread count: "<<NTH<<endl; cout<<"loop count: "<<NLOOP<<endl; cout<<"elapsed(ms): "<< ((e.tv_sec-b.tv_sec)*1000000 + (e.tv_usec-b.tv_usec)) / 1000 <<endl; cout<<endl; } private: static void* hook(void *args) { size_t ith = reinterpret_cast<size_t> (args); for (size_t i = 0; i < NLOOP; ++i) { ++s[ith].n; } return NULL; } private: struct S { size_t n; char padding[NPAD]; }; static S s[NTH]; }; template <size_t NPAD, size_t NTH, size_t NLOOP> typename FalseSharing<NPAD, NTH, NLOOP>::S FalseSharing<NPAD, NTH, NLOOP>::s[NTH]; int main() { FalseSharing<0> test1; test1.run(); FalseSharing<56> test2; test2.run(); return 0; }; |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | $ cat /proc/cpuinfo | egrep 'model name|cache_alignment' model name : Intel(R) Core(TM) i7-3520M CPU @ 2.90GHz cache_alignment : 64 model name : Intel(R) Core(TM) i7-3520M CPU @ 2.90GHz cache_alignment : 64 model name : Intel(R) Core(TM) i7-3520M CPU @ 2.90GHz cache_alignment : 64 model name : Intel(R) Core(TM) i7-3520M CPU @ 2.90GHz cache_alignment : 64 $ g++ false_sharing.cpp -pthread $ time ./a.out padding bytes : 0 thread count : 4 loop count : 1073741824 elapsed(ms) : 12638 padding bytes : 56 thread count : 4 loop count : 1073741824 elapsed(ms) : 3888 real 0m16.530s user 1m3.688s sys 0m0.044s |
PS. 上面结果是在一台2核4线程的i7-3520笔记本上面测得的,在一台4核16线程的E5520(2.27GHz)机器上,结果更加触目惊心:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | padding bytes: 0 thread count: 4 loop count: 1073741824 elapsed(ms): 13646 padding bytes: 56 thread count: 4 loop count: 1073741824 elapsed(ms): 3548 padding bytes: 0 thread count: 16 loop count: 1073741824 elapsed(ms): 43848 padding bytes: 56 thread count: 16 loop count: 1073741824 elapsed(ms): 5051 |
UPDATE
有意思的是,根据Felix同学的测试性能差距并没有这么大,详情请移步到这里。
你好!除了代码,此处没有多少原创之物,皆为本人搜集、整理、总结之记录与心得,欢迎转载分享!转载时请尽量注明出处,将不胜感激。祝你健康、快乐!
赞测试结果,差别还真是明显啊。CPU娘果然还是得好好爱抚,否则会傲娇的。。。
请看16线程时候的测试结果。。。
Orz 大坑啊。
在Xcode下用felix021在《Yet Another False-Sharing Test》中所示的代码做了测试,发现几个问题(那边没找到留言功能,我就在这里留言啦),如有错不吝赐教:
1,计算时所用的数据是int,所以创建buffer的时候应该是size_t size = npad + sizeof(int);而不是用sizeof(long)。
2,测试不同npad大小时,每次的步进值应该用sizeof(int)而不是固定的8。(在我这台Mac上通过sudo sysctl -a | grep cachelinesize获得的chaeline大小是64)。
3,–(*nloop)会比(*nloop)–更高效,使得结果差距更大。
4,我这边的测试结果是,npad为60时最快。但npad从0到64和从64到0的结果会有所不同,第一笔数据总是偏大。
5,不知道为什么60和64之间的差异没那么明显……
6,下面是测试结果,第一组是从64到0,第二组是从0到64。每一组的第一笔数据总是偏大。
nloop = 134217728, nthread = 16
padding: 64, time usage: 733120
padding: 60, time usage: 658281
padding: 56, time usage: 686168
padding: 52, time usage: 685958
padding: 48, time usage: 703739
padding: 44, time usage: 711737
padding: 40, time usage: 724637
padding: 36, time usage: 727409
padding: 32, time usage: 748383
padding: 28, time usage: 763139
padding: 24, time usage: 807617
padding: 20, time usage: 854493
padding: 16, time usage: 901531
padding: 12, time usage: 918722
padding: 8, time usage: 1047953
padding: 4, time usage: 1137501
padding: 0, time usage: 1397541
padding: 60, time usage: 658281 is the best
nloop = 134217728, nthread = 16
padding: 0, time usage: 1428508
padding: 4, time usage: 1143791
padding: 8, time usage: 1006690
padding: 12, time usage: 918160
padding: 16, time usage: 908541
padding: 20, time usage: 876474
padding: 24, time usage: 814162
padding: 28, time usage: 778620
padding: 32, time usage: 738507
padding: 36, time usage: 733821
padding: 40, time usage: 717367
padding: 44, time usage: 702876
padding: 48, time usage: 699436
padding: 52, time usage: 694278
padding: 56, time usage: 680956
padding: 60, time usage: 657892
padding: 64, time usage: 659143
padding: 60, time usage: 657892 is the best
唔,我的确是没有注意到那里用了int,这是个笔误,不过我觉得那不是问题,换成long也没太差差别。步长用了8是因为我的测试机器都是x86_64的,4个字节的padding(对于我原计划的long来说)可能还会带来问题(不是8字节对齐)。你改成sizeof(int) + padding的话,当然就是60字节(及以上)效率最高了,因为这时候正好padding到一个cache line。60和64之间没什么差距也是可以理解的,因为二者都能避免伪共享带来的开销。
另外我博客要注册才能留言,因为垃圾留言太多了。。。
再测试了一下dutor的代码,确实还是56最快,测试结果如下:
padding bytes: 0
thread count: 4
loop count: 1073741824
elapsed(ms): 7398
padding bytes: 56
thread count: 4
loop count: 1073741824
elapsed(ms): 2815
padding bytes: 60
thread count: 4
loop count: 1073741824
elapsed(ms): 3174
padding bytes: 64
thread count: 4
loop count: 1073741824
elapsed(ms): 3160
于是我增大felix021代码中的测试量,并且只测试48到64的范围,结果如下:
nloop = 1073741824, nthread = 8
padding: 64, time usage: 2672284
padding: 60, time usage: 2786169
padding: 56, time usage: 2744741
padding: 52, time usage: 3297609
padding: 48, time usage: 3249077
padding: 64, time usage: 2672284 is the best
nloop = 1073741824, nthread = 8
padding: 48, time usage: 3304030
padding: 52, time usage: 3334961
padding: 56, time usage: 2600259
padding: 60, time usage: 2595829
padding: 64, time usage: 2609459
padding: 60, time usage: 2595829 is the best
这不科学……
考虑机器字长了吗?
不懂,求解~我以为处理器字长跟Cache Line大小是一样的……
基本上这个可以认为是误差了吧? 结果没落在小于56的区间(而且差距还挺明显),就能说明这个padding的确是有效果的。
如果说“线程读写空间>=Cache line”才能避免False Sharing的话,为什么“56+4(int的长度)”也有效果呢?它还没满64呢,后面会跟着下一个线程读写的空间。
size_t在x86_64下是8个字节吧。