多线程false sharing带来的影响和一些优化.
来源:互联网 发布:fft算法c语言 编辑:程序博客网 时间:2024/06/04 23:35
最近在线项目中测试一个无锁队列的性能的时候发现,在一个线程push另一个线程pop整型数据的时候,吞吐量竟然和std::queue+spinlock类似甚至更差,这样完全体现不出lockfree的优势, 决定找找原因.
这个无锁队列是通过一个头指针来push数据,一个尾指针来pop数据来实现的.
template<typename T>class LockFreeQueue { struct Node { T value; Node *next; } ... bool Push(const T & data) { //只操作tail成员 ... } bool Pop(T &data) { //只操作head成员 ... } ...private: Node * head; Node * tail;}
使用上一个线程只调用队列的push方法, 另一个线程调用队列的pop方法.
push方法只访问tail成员实现入队,pop方法只方法head成员实现出队.
注意到head和tail指针有极大的可能在同一个L1缓存行上的, 这就会造成false sharing,这里体现在push线程操作tail指针的时候将缓存行刷新掉了,cpu会通知读线程所在的cpu将该相同的缓存行也刷新掉,以达到多处理器指针的cache coherence, 读线程修改head指针的时候也会做相同的事情, 在系统高压的情况下这种访存方式会对性能造成很大的伤害,具体通过下面一个例子来看.
例子程序P1
#include <pthread.h>#include <stdio.h>#define ITERATIONS 1e9int A;int B;static void * thread_func(void *){ for (int i = 0; i < ITERATIONS; i++){ if (A == 1) { A = 0; } else { A = 1; } }}int main() { pthread_t tid; pthread_create(&tid, NULL, thread_func, NULL); for (int i = 0; i < ITERATIONS; i++){ if (B == 1) { B = 0; } else { B = 1; } } return 0;}
这个简单程序展示了false sharing带来的性能损失,两个线程访问不同的地址,但是却在同一个缓存行上.这里通过linux下的perf工具来统计程序的运行指标。
>> perf stat -e instructions -e cache-references -e cache-misses -e L1-dcache-loads -e L1-dcache-load-misses -e L1-dcache-stores -e L1-dcache-store-misses -e LLC-loads -e LLC-load-misses -e LLC-prefetches -e cycles -e cs ./cacheline_unaligned Performance counter stats for './cacheline_unaligned': 19,006,842,755 instructions # 注意这行 0.97 insns per cycle [36.43%] 78,160,551 cache-references [45.54%] 24,661 cache-misses # 0.032 % of all cache refs [45.54%] 7,990,941,413 L1-dcache-loads [45.54%] 78,220,759 L1-dcache-load-misses # 注意这行 0.98% of all L1-dcache hits [45.54%] 4,009,177,234 L1-dcache-stores [36.31%] 69,339,246 L1-dcache-store-misses #注意这行 [36.54%] 8,499,780 LLC-loads [36.49%] 5,684 LLC-load-misses # 0.07% of all LL-cache hits [36.45%] 76,310,160 LLC-prefetches [18.20%] 19,623,169,444 cycles [27.27%] 612 cs 3.438158407 seconds time elapsed
对代码做一个简单的修改得到例子程序P2,对变量之间加以padding, 使得变量A和B存在于独立的缓存行上,这里机器的cacheline大小为64个字节, linux下可以通过getconf LEVEL1_DCACHE_LINESIZE
来得到这个大小.
#include <pthread.h>#include <stdio.h>#define ITERATIONS 1e9int A;int32_t __padding__[16]; //padding, 使A,B独立存在一个缓存行上int B;static void * thread_func(void *){ for (int i = 0; i < ITERATIONS; i++){ if (A == 1) { A = 0; } else { A = 1; } }}int main() { pthread_t tid; pthread_create(&tid, NULL, thread_func, NULL); for (int i = 0; i < ITERATIONS; i++){ if (B == 1) { B = 0; } else { B = 1; } } return 0;}
接着看修改后的结果.
>> perf stat -e instructions -e cache-references -e cache-misses -e L1-dcache-loads -e L1-dcache-load-misses -e L1-dcache-stores -e L1-dcache-store-misses -e LLC-loads -e LLC-load-misses -e LLC-prefetches -e cycles -e cs ./cacheline_aligned Performance counter stats for './cacheline_aligned': 18,128,002,993 instructions # 注意这行 1.58 insns per cycle [36.49%] 180,503 cache-references [45.61%] 17,691 cache-misses # 9.801 % of all cache refs [45.61%] 7,640,076,865 L1-dcache-loads [45.61%] 208,122 L1-dcache-load-misses # 注意这行 0.00% of all L1-dcache hits [45.61%] 3,841,727,742 L1-dcache-stores [36.27%] 16,237 L1-dcache-store-misses # 注意这行 [36.65%] 53,850 LLC-loads [36.58%] 3,879 LLC-load-misses # 7.20% of all LL-cache hits [36.51%] 4,254 LLC-prefetches [18.22%] 11,479,745,606 cycles [27.27%] 379 cs 2.030437774 seconds time elapsed
看到修改后L1-dcache-store-misses数量约为未修改版本的0.26%, ipc指标从0.97提升到了1.58. 这带来的好处是相当的明显的,因为L1缓存的miss使得cpu流水线的暂停去访问下一级缓存获取数据,减少了ipc(instructions per cycle)这个重要的指标.
上述的无锁队列的情况恰好和这种访存方式相同, 所以很自然可以做这样的一个优化.
template<typename T>class LockFreeQueue { struct Node { T value; Node *next; } ... bool Push(const T & data) { //只操作tail成员 ... } bool Pop(T &data) { //只操作head成员 ... } ...private: Node * head; char __padding__[CACHELINE_SIZE - sizeof(Node *)]; Node * tail;}
经过测试之后, 相比之前的队列实现,效率约有5~60%的提升, 还是比较满意的!
通过这个简单的例子可以得出在多线程环境下要尽量避免Flase sharing的发生,我自己总结了方法.即将一个线程独享的变量通过padding放置在同一个缓存行上.
- 多线程false sharing带来的影响和一些优化.
- 多线程之false sharing问题
- 多线程 False Sharing问题-ok
- 什么是多线程环境下的伪共享(false sharing)?
- 介绍false sharing的文章
- 多线程伪共享(false sharing)问题分析
- 多线程伪共享(false sharing)问题分析
- 多线程伪共享(false sharing)问题分析
- 高性能多线程程序中的false sharing和CPU cache效应
- false sharing
- False Sharing
- false sharing
- SMP中多线程程序的性能衰退现象之False Sharing
- 计算模型给多线程编程带来的影响
- 编译器优化选项带来的影响逻辑的问题
- 网站优化中404页面所带来的影响
- 多核技术给软件正确性带来的一些影响
- 影响关键词优化的一些外在因素
- HDOJ 4276 The Ghost Blows Light 树上分组背包
- 哲学家就餐问题
- cocos spine 骨骼动画.水平翻转
- Creating a Simple 1D Noise
- 数据库
- 多线程false sharing带来的影响和一些优化.
- VS2010 C++ 学习笔记(七)virtual 虚函数
- Java语言编程基础及应用(3)
- github上传
- HDOJ 4762 Cut the Cake(概率+Java)
- tomcat内存配置
- [iOS]细小的知识点
- Lecture1-1Course Introduction
- C++ <使用友元函数的类对象加法>