绪论

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的长度,即可实现隔离,虽然这不可避免的会浪费一些内存。

  1. 对于共享数组而言,增大数组元素的间隔使得由不同线程存取的数组元素位于不同的cache line上,使一个核上的Cache line修改不会影响其它核;或者在每个线程中创建全局数组的本地拷贝,然后执行结束后再写回全局数组,此方法比较粗暴不优雅。
  2. 对于共享结构体而言,使每个结构体成员变量按照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原理浅析和测试相关推荐

  1. 从缓存行出发理解volatile变量、伪共享False sharing、disruptor

    volatile关键字 当变量被某个线程A修改值之后,其它线程比如B若读取此变量的话,立刻可以看到原来线程A修改后的值 注:普通变量与volatile变量的区别是volatile的特殊规则保证了新值能 ...

  2. Spring Security入门到实践(一)HTTP Basic在Spring Security中的应用原理浅析

    一.Spring Security简介 打开Spring Security的官网,从其首页的预览上就可以看见如下文字: Spring Security is a powerful and highly ...

  3. 【vue双向绑定原理浅析】

    vue双向绑定原理浅析 1.什么是双向绑定? ​ 所谓双向绑定,指的是vue实例中的data与其渲染的DOM元素的内容保持一致,无论谁被改变,另一方会相应的更新为相同的数据.(数据变化更新视图,视图变 ...

  4. 好理解的Java内存虚假共享(False Sharing)性能损耗以及解决方案

    虚假共享(False Sharing)也有人翻译为伪共享 参考 https://en.wikipedia.org/wiki/False_sharing 在计算机科学中,虚假共享是一种性能降低的使用模式 ...

  5. Python标准库queue模块原理浅析

    Python标准库queue模块原理浅析 本文环境python3.5.2 queue模块的实现思路 作为一个线程安全的队列模块,该模块提供了线程安全的一个队列,该队列底层的实现基于Python线程th ...

  6. Python标准库threading模块Condition原理浅析

    Python标准库threading模块Condition原理浅析 本文环境python3.5.2 threading模块Condition的实现思路 在Python的多线程实现过程中,在Linux平 ...

  7. 伪共享(False Sharing)

    原文地址:http://ifeve.com/false-sharing/ 作者:Martin Thompson  译者:丁一 缓存系统中是以缓存行(cache line)为单位存储的.缓存行是2的整数 ...

  8. 伪共享(false sharing),并发编程无声的性能杀手

    在并发编程过程中,我们大部分的焦点都放在如何控制共享变量的访问控制上(代码层面),但是很少人会关注系统硬件及 JVM 底层相关的影响因素.前段时间学习了一个牛X的高性能异步处理框架 Disruptor ...

  9. .NET1.1中预编译ASP.NET页面实现原理浅析[1]自动预编译机制浅析

    .NET1.1中预编译ASP.NET页面实现原理浅析[1]自动预编译机制浅析 .NET1.1中预编译ASP.NET页面实现原理浅析[1]自动预编译机制浅析 作者:&;nbsp来自:网络 htt ...

最新文章

  1. 阿里P7架构师的成长之路
  2. 机器学习拓展知识(数学/统计/算法)
  3. 怎样加速微软商店服务器,windows10系统如何加快应用商店打开速度【图文教程】...
  4. oracle 启动监听提示 :The listener supports no services
  5. (转)交换机攻击方法描述
  6. php能打开.shp文件吗,shp文件是什么格式的
  7. python-函数的使用 0222
  8. 剑指 Offer II 070. 排序数组中只出现一次的数字
  9. 小D课堂 - 零基础入门SpringBoot2.X到实战_第2节 SpringBoot接口Http协议开发实战_9、SpringBoot基础HTTP其他提交方法请求实战...
  10. macOS 下的 homebrew
  11. 海康威视摄像头密码重置方法
  12. Linux cd命令cd、 cd ~、cd /、cd../、cd /home讲解
  13. 阅读笔记:3D visual discomfort predictor based on subjective perceived-constraint sparse representation
  14. 2018-2019-2 网络对抗技术 20165322 Exp3 免杀原理与实践
  15. Spring Spring-data-redis 实现的消息队列
  16. .ldb文件到底派什么用场得?
  17. No JSON object could be decoded
  18. 字符串字符数组的赋值
  19. el-input 密码输入框 显示隐藏优化
  20. 在SQL server中设置时间格式

热门文章

  1. iOS Hacker 反注入和反反注入
  2. exe的dll加载过程
  3. Ring3下Inline Hook API
  4. 【Linux】目录组织结构、文件类型和文件权限
  5. RuntimeError 之 : CUDA error: device-side assert triggered
  6. c++primer12.3文本查询程序的一些问题的感悟和错误剖析
  7. windows7下安装centos7双系统(未验证)
  8. 7.5 obtaining database metadata
  9. Eclipse里web的依赖工程部署的简便方法
  10. Nmap Windows 版本时区显示乱码