false-sharing原理浅析和测试
绪论
SMP(对称多处理)架构简单的说就是多个CPU核,共享同一个内存和总线。L1 cache也叫芯片缓存,一般是CPU Core私有的,即每个CPU核一个,L2 cache可能是私有的也可能是部分共享的,L3 cache则多数是共享的。false-sharing是在SMP的架构下常见的问题。
false-sharing产生背景及原因
CPU利用cache和内存之间交换数据的最小粒度不是字节,而是称为cache line的一块固定大小的区域,缓存行是内存交换的实际单位。缓存行是2的整数幂个连续字节,一般为32-256个字节,最常见的缓存行大小是64个字节。
在写多线程代码时,为了避免使用锁,通常会采用这样的数据结构:根据线程的数目,安排一个数组, 每个线程一个项,互相不冲突。从逻辑上看这样的设计无懈可击,但是实践的过程可能会发现有些场景下非但没提高执行速度,反而会性能很差,而且年轻司机通常很难定位问题所在。
问题在于cpu的cache line,当多线程修改互相独立的变量时,如果这些变量共享同一个缓存行,就会无意中影响彼此的性能,这就是伪共享,即false-sharing。
实际案例
在多处理器,多线程情况下,如果两个线程分别运行在不同的CPU上,而其中某个线程修改了cache line中的元素,由于cache一致性的原因,另一个线程的cache line被宣告无效,在下一次访问时会出现一次cache line miss,大量的cache line miss会导致性能的显著下降。究其原因,cache line miss是由于两个线程的Cache line有重合(非共享的变量实际上却共享的使用了同一个cacheline,导致竞争)引起的。
如在Intel Core 2 Duo处理器平台上,L2 cache是由两个core共享的,而L1 data cache是分开的,由两个core分别存取。cache line的大小是64 Bytes。假设有个全局共享结构体变量f由2个线程A和B共享读写,该结构体一共8个字节同时位于同一条cache line上。
struct foo {int x;int y; };
若此时两个线程一个读取f.x另一个读取f.y,即便两个线程的执行是在独立的cpu core上的,实际上结构体对象f被分別读入到两个CPUs的cache line中且该cache line 处于shared状态。此时在核心1上运行的线程A想更新变量X,同时核心2上的线程B想要更新变量Y。
如果核心1上线程A优先获得了所有权,线程A修改f.x会使该CPU core 1 上的这条cache line将变为modified状态,另一个CPU core 2上对应的cache line将变成invalid状态;此时若线程B马上读取f.y,为了确保cache一致性,B所在CPU核上的相应cache line的数据必须被更新;当核心2上线程B优先获得了所有权然后执行更新操作,核心1就要使自己对应的缓存行失效。这会来来回回的经过L3缓存,大大影响了性能。如果互相竞争的核心位于不同的插槽,就要额外横跨插槽连接,若读写的次数频繁,将增大cache miss的次数,严重影响系统性能。
虽然在memory的角度这两种的访问时隔离的,但是由于错误的紧凑地放在了一起,是的两个变量处于同一个缓存行中。每个线程都要去竞争缓存行的所有权来更新变量。可见,false sharing会导致多核处理器上对于缓存行cache line的写竞争,造成严重的系统性能下降,有人将伪共享描述成无声的性能杀手,因为从代码中很难看清楚是否会出现伪共享。
false-sharing避免方法
把每个项凑齐cache line的长度,即可实现隔离,虽然这不可避免的会浪费一些内存。
- 对于共享数组而言,增大数组元素的间隔使得由不同线程存取的数组元素位于不同的cache line上,使一个核上的Cache line修改不会影响其它核;或者在每个线程中创建全局数组的本地拷贝,然后执行结束后再写回全局数组,此方法比较粗暴不优雅。
- 对于共享结构体而言,使每个结构体成员变量按照Cache Line大小(一般64B)对齐。可能需要使用#pragma宏。
注意事项
单线程或单核多线程都不存在这个问题,因为只有一个CPU核也即只有一个L1 Cache,不存在缓存一致性的问题。
示例程序
注意程序中的LEVEL1_DCACHE_LINESIZE宏来自g++编译命令传入的,使用Shell命令getconf LEVEL1_DCACHE_LINESIZE能获取cpu cache line的大小。(有关getconf命令的使用可以自行google)
#include <stdio.h> #include <sys/time.h> #include <time.h> #include <pthread.h> #define PACK __attribute__ ((packed)) typedef int cache_line_int __attribute__((aligned(LEVEL1_DCACHE_LINESIZE)));#ifdef FS struct data {cache_line_int a;cache_line_int b; }; #endif #ifdef NONFS struct data {int a;int b; }; #endif#define MAX_NUM 500000000void* thread_func_1(void* param) {timeval start, end;gettimeofday(&start, NULL);data* d = (data*)param;for (int i=0; i<MAX_NUM; ++i){++d->a;}gettimeofday(&end, NULL);printf("thread 1, time=%d\n", (int)(end.tv_sec-start.tv_sec)*1000000+(int)(end.tv_usec-start.tv_usec));return NULL; }void* thread_func_2(void* param) {timeval start, end;gettimeofday(&start, NULL);data* d = (data*)param;for (int i=0; i<MAX_NUM; ++i){++d->b;}gettimeofday(&end, NULL);printf("thread 2, time=%d\n", (int)(end.tv_sec-start.tv_sec)*1000000+(int)(end.tv_usec-start.tv_usec));return NULL; }int main() {data d = {a:0, b:0};printf("sizeof(data) : %d\n", sizeof(data));pthread_t t1, t2;pthread_create(&t1, NULL, thread_func_1, &d);pthread_create(&t2, NULL, thread_func_2, &d);pthread_join(t1, NULL);pthread_join(t2, NULL);printf("end, a=%d,b=%d\n", d.a, d.b);return 0; }
编译、运行可以看到结果对比:
/*编译指令*/ g++ -o 1 1.cpp -g -Wall -lpthread -DLEVEL1_DCACHE_LINESIZE=`getconf LEVEL1_DCACHE_LINESIZE` -DFS g++ -o 1 1.cpp -g -Wall -lpthread -DLEVEL1_DCACHE_LINESIZE=`getconf LEVEL1_DCACHE_LINESIZE` -DNONFS/*输出结果:*/ thread 1, time=1607430 thread 2, time=1629508
我的腾讯云主机只有一个CPU核,所以运行的结果并没有差异,但是在多核CPU上执行大约相差2~3倍。
注:本文整理自多篇文章,参考文章列表后续补充。
转载于:https://www.cnblogs.com/blastbao/p/8290332.html
false-sharing原理浅析和测试相关推荐
- 从缓存行出发理解volatile变量、伪共享False sharing、disruptor
volatile关键字 当变量被某个线程A修改值之后,其它线程比如B若读取此变量的话,立刻可以看到原来线程A修改后的值 注:普通变量与volatile变量的区别是volatile的特殊规则保证了新值能 ...
- Spring Security入门到实践(一)HTTP Basic在Spring Security中的应用原理浅析
一.Spring Security简介 打开Spring Security的官网,从其首页的预览上就可以看见如下文字: Spring Security is a powerful and highly ...
- 【vue双向绑定原理浅析】
vue双向绑定原理浅析 1.什么是双向绑定? 所谓双向绑定,指的是vue实例中的data与其渲染的DOM元素的内容保持一致,无论谁被改变,另一方会相应的更新为相同的数据.(数据变化更新视图,视图变 ...
- 好理解的Java内存虚假共享(False Sharing)性能损耗以及解决方案
虚假共享(False Sharing)也有人翻译为伪共享 参考 https://en.wikipedia.org/wiki/False_sharing 在计算机科学中,虚假共享是一种性能降低的使用模式 ...
- Python标准库queue模块原理浅析
Python标准库queue模块原理浅析 本文环境python3.5.2 queue模块的实现思路 作为一个线程安全的队列模块,该模块提供了线程安全的一个队列,该队列底层的实现基于Python线程th ...
- Python标准库threading模块Condition原理浅析
Python标准库threading模块Condition原理浅析 本文环境python3.5.2 threading模块Condition的实现思路 在Python的多线程实现过程中,在Linux平 ...
- 伪共享(False Sharing)
原文地址:http://ifeve.com/false-sharing/ 作者:Martin Thompson 译者:丁一 缓存系统中是以缓存行(cache line)为单位存储的.缓存行是2的整数 ...
- 伪共享(false sharing),并发编程无声的性能杀手
在并发编程过程中,我们大部分的焦点都放在如何控制共享变量的访问控制上(代码层面),但是很少人会关注系统硬件及 JVM 底层相关的影响因素.前段时间学习了一个牛X的高性能异步处理框架 Disruptor ...
- .NET1.1中预编译ASP.NET页面实现原理浅析[1]自动预编译机制浅析
.NET1.1中预编译ASP.NET页面实现原理浅析[1]自动预编译机制浅析 .NET1.1中预编译ASP.NET页面实现原理浅析[1]自动预编译机制浅析 作者:&;nbsp来自:网络 htt ...
最新文章
- 阿里P7架构师的成长之路
- 机器学习拓展知识(数学/统计/算法)
- 怎样加速微软商店服务器,windows10系统如何加快应用商店打开速度【图文教程】...
- oracle 启动监听提示 :The listener supports no services
- (转)交换机攻击方法描述
- php能打开.shp文件吗,shp文件是什么格式的
- python-函数的使用 0222
- 剑指 Offer II 070. 排序数组中只出现一次的数字
- 小D课堂 - 零基础入门SpringBoot2.X到实战_第2节 SpringBoot接口Http协议开发实战_9、SpringBoot基础HTTP其他提交方法请求实战...
- macOS 下的 homebrew
- 海康威视摄像头密码重置方法
- Linux cd命令cd、 cd ~、cd /、cd../、cd /home讲解
- 阅读笔记:3D visual discomfort predictor based on subjective perceived-constraint sparse representation
- 2018-2019-2 网络对抗技术 20165322 Exp3 免杀原理与实践
- Spring Spring-data-redis 实现的消息队列
- .ldb文件到底派什么用场得?
- No JSON object could be decoded
- 字符串字符数组的赋值
- el-input 密码输入框 显示隐藏优化
- 在SQL server中设置时间格式
热门文章
- iOS Hacker 反注入和反反注入
- exe的dll加载过程
- Ring3下Inline Hook API
- 【Linux】目录组织结构、文件类型和文件权限
- RuntimeError 之 : CUDA error: device-side assert triggered
- c++primer12.3文本查询程序的一些问题的感悟和错误剖析
- windows7下安装centos7双系统(未验证)
- 7.5 obtaining database metadata
- Eclipse里web的依赖工程部署的简便方法
- Nmap Windows 版本时区显示乱码