CPU缓存伪共享
CPU缓存什么东西?当然这个问题很多人有可能觉得比较傻,CPU缓存什么,肯定是缓存数据(代码)啊,要不然还能缓存啥,这个确实没问题,但是CPU到底缓存什么样的数据呢?因为对CPU来说,无论是指令,还是数据,都是数据,他如果要缓存,缓存的单位是啥?要缓存的内容是啥呢?
接下来咱们一点点解析这部分的内容,首先看一个比较有意思的代码
#include <thread>
#include <iostream>
#include <sys/time.h>
//设置一个10亿的执行次数
#define MAX_NUM 1000000000
struct TestLine {
int x;
int y;
};
int GetTimeCost(const timeval &beg,const timeval &end) {
return (end.tv_sec-beg.tv_sec) * 1000 + (end.tv_usec-beg.tv_usec)/1000;
}
void T1Func(TestLine* tl){
for( int it = 0; it< MAX_NUM ; ++it) {
tl->x +=1;
}
return;
}
void T2Func(TestLine *tl) {
for ( int it = 0;it < MAX_NUM ; ++it) {
tl->y +=1;
}
return ;
}
int main(){
timeval beg,end;
gettimeofday(&beg,NULL);
TestLine tl = {0,0};
std::thread t1(T1Func,&tl);
std::thread t2(T2Func,&tl);
t1.join();
t2.join();
gettimeofday(&end,NULL);
std::cout << "cost = "<< GetTimeCost(beg,end) << "ms ,x="<< tl.x << ",y="<< tl.y << std::endl;
return 0;
}
这是一段很简单的程序,启动两个线程,分别对结构体中的x变量与y变量进行操作,循环10亿次,在大家看来,这段代码是两个线程分别对两个变量进行操作,其实是两者毫无关联的动作,我们执行一下这段代码,看下耗时

线程执行耗时
接下来咱们再看一下下面的代码
#include <thread>
#include <iostream>
#include <sys/time.h>
//设置一个10亿的执行次数
#define MAX_NUM 1000000000
struct TestLine {
int x;
long long buf[8]; // 新增一个8个long long 类型的数组
int y;
};
int GetTimeCost(const timeval &beg,const timeval &end) {
return (end.tv_sec-beg.tv_sec) * 1000 + (end.tv_usec-beg.tv_usec)/1000;
}
void T1Func(TestLine* tl){
for( int it = 0; it< MAX_NUM ; ++it) {
tl->x +=1;
}
return;
}
void T2Func(TestLine *tl) {
for ( int it = 0;it < MAX_NUM ; ++it) {
tl->y +=1;
}
return ;
}
int main(){
timeval beg,end;
gettimeofday(&beg,NULL);
TestLine tl = {0,0};
std::thread t1(T1Func,&tl);
std::thread t2(T2Func,&tl);
t1.join();
t2.join();
gettimeofday(&end,NULL);
std::cout << "cost = "<< GetTimeCost(beg,end) << "ms ,x="<< tl.x << ",y="<< tl.y << std::endl;
return 0;
}
这段代码与上面的那段代码最大的区别就是:在结构体中增加了一个8个long long类型的数组,接下来我们看下这段代码的执行情况:

注:以上代码是在C++11环境下进行编译运行,大家可以通过 g++ -lpthread -std=c++11 http://xxx.cc方式进行编译
大家可以看到,这段代码与上段代码最终x与y的输出是一致的,但是耗时上,这段代码要比第一段代码执行时间降低50%以上,为啥?
为什么我已经把数据缓存到本地了,通过增加一个数组,就可以提升程序的运行时间呢?
cache line(缓存行)
通过上面的一个小程序,大家是不是会存在很多疑惑,为啥只是增加了一个数组,整个程序的运行时间就大幅度提高了,下面我们来简单解释一下这里面的原因。
通过上文我们知道,CPU为了提升运行速度,是存在缓存的,但是CPU的缓存,到底缓存了啥呢?数据+指令
CPU的缓存单位是啥呢?cache line,cache line 到底是个啥东西呢?cache line 就是CPU执行时,从内存中读取内容的最小单位,那cache line大小是多少呢?一般是64个字节(当然不同的体系结构及厂商设定的cache line大小是不一样的),为啥是64个字节,不是其他值呢?哈哈,我也不知道,大概率就是测试出来64的性价比+性能是最优的吧。
典型的cache line结构如下:

tag用于标识这个缓存行,data字段用于存储实际的内容数据(这就是我们所说的64字节大小的部分),flag用于标记这个缓存行的状态
如何获取到系统的缓存行大小信息呢?
getconf -a | grep CACHE
上述可以看到,计算机有三层缓存,并且每层缓存中的cache line都是64字节。
现在我们来解释一下上两段代码的差异吧。我们知道CPU缓存数据是以cache line为单位,并且每个cache line的大小大概是64个字节,我们就可以看出,在第一段代码中,结构体中的x成员与y成员,大概率会在同一个cache line 中,如果这两个线程分别对这个cache line进行操作,那么很有可能会造成读写这段cache line临界区的程序变成串行,因为两个线程同时操作一个cache line,肯定会存在覆盖写的问题,为了解决覆盖写,所以这段数据只能是串行(你写完之后,我在读取,然后我在写)这种模式
那再来分析一下第二段代码,第二段代码在两个变量中间增加了long long类型的数组,数组大小为8,为啥是这个数字内,因为上文中简单介绍了,一般cache line的大小是64个字节,当然大家也可以增加一个64个char型的数组,效果其实是一样的。当我们增加了这么第一段看似没有用的数据之后,我们就可以猜测出来,x与y,大概率不会再同一个cache line中了,所以这两个线程操作的是两个不同的cache line,完全可以实现线程的并行执行(因为已经不存在临界区的东西了),所以第二段代码的执行时间也是第一段代码的50%(可解释)
所以通过分析可以得出,CPU虽然把数据进行了缓存,但是这些缓存有时候并不能完全做到数据共享,而是有部分数据发生变化之后,其余CPU的数据也必须跟着发生变化,这就是所谓的CPU缓存的“伪共享”问题。
CPU缓存伪共享的更多相关文章
- JUC源码学习笔记4——原子类,CAS,Volatile内存屏障,缓存伪共享与UnSafe相关方法
JUC源码学习笔记4--原子类,CAS,Volatile内存屏障,缓存伪共享与UnSafe相关方法 volatile的原理和内存屏障参考<Java并发编程的艺术> 原子类源码基于JDK8 ...
- java高并发核心要点|系列5|CPU内存伪共享
上节提到的:伪共享,今天我们来说说. 那什么是伪共享呢? 这得从CPU的缓存结构说起.以下如图,CPU一般来说是有三级缓存,1 级,2级,3级,越上面的,越靠近CPU的,速度越快,成本也越高.也就是说 ...
- cache line 伪共享
https://blog.csdn.net/qq_27680317/article/details/78486220认识CPU Cache CPU Cache概述 随着CPU的频率不断提升,而内存的访 ...
- 什么是CPU缓存
一.什么是CPU缓存 1. CPU缓存的来历 众所周知,CPU是计算机的大脑,它负责执行程序的指令,而内存负责存数据, 包括程序自身的数据.在很多年前,CPU的频率与内存总线的频率在同一层面上.内存的 ...
- CPU缓存是位于CPU与内存之间的临时数据交换器,它的容量比内存小的多但是交换速度却比内存要快得多。CPU缓存一般直接跟CPU芯片集成或位于主板总线互连的独立芯片上
一.什么是CPU缓存 1. CPU缓存的来历 众所周知,CPU是计算机的大脑,它负责执行程序的指令,而内存负责存数据, 包括程序自身的数据.在很多年前,CPU的频率与内存总线的频率在同一层面上.内存的 ...
- 从Java视角理解CPU缓存和伪共享
转载自:http://ifeve.com/from-javaeye-cpu-cache/ http://ifeve.com/from-javaeye-false-shari ...
- 从缓存行出发理解volatile变量、伪共享False sharing、disruptor
volatilekeyword 当变量被某个线程A改动值之后.其他线程比方B若读取此变量的话,立马能够看到原来线程A改动后的值 注:普通变量与volatile变量的差别是volatile的特殊规则保证 ...
- 伪共享(False Sharing)和缓存行(Cache Line)
转载:https://www.jianshu.com/p/a9b1d32403ea https://www.toutiao.com/a6644375612146319886/ 前言 在上篇介绍Long ...
- 简述伪共享和缓存一致性MESI
什么是伪共享 计算机系统中为了解决主内存与CPU运行速度的差距,在CPU与主内存之间添加了一级或者多级高速缓冲存储器(Cache),这个Cache一般是集成到CPU内部的,所以也叫 CPU Cache ...
- Java8的伪共享和缓存行填充--@Contended注释
在我的前一篇文章<伪共享和缓存行填充,从Java 6, Java 7 到Java 8>中, 我们演示了在Java 8中,可以采用@Contended在类级别上的注释,来进行缓存行填充.这样 ...
随机推荐
- 解决cnpm syscall: ‘rename‘
1.删了cnpm npm uninstall -g cnpm 2.指定版本下载cnpm npm install cnpm@7.1.0 -g
- CMake学习(一)
CMake学习(一) 1.简介 CMake是一个强大的软件构建系统,可以用简单的语句来描述所有平台的安装(编译过程) 可以编译源代码.制作程序库.产生适配器(wrapper).还可以用任意的顺序建构执 ...
- python multipart/form-data post接口请求
python multipart/form-data post接口请求 def WebKit_format(data, boundary="----WebKitFormBoundary*** ...
- python global将结果存储起来给另外一个文件对象使用
python global将结果存储起来给另外一个文件对象使用 使用场景: 在aaa.py文件里面操作数据生成结果C 然后再在bbb.py文件里面使用C 下面是aaa.py代码: #!/usr/bin ...
- 【Scala】07 集合
分三大类: 序列 Seq 集 Set 映射 Map 所有集合类型都扩展自Iterable特质(可迭代的) 所有集合类型都提供[可变]和[不可变]的版本 归纳在下面两个包中 scala.collecti ...
- 【Vue】Re09 Webpack 第一部分(介绍、安装、配置)
一.Webpack的用途 webpack要解决的是统一网页资源的问题 前端工程化出现了很多问题,就是兼容性,浏览器所不能解析 所以需要一个打包,转换等方式处理 二.安装描述介绍 下载安装NodeJS, ...
- 如何使用工具下载B站非会员视频(下载B站免费web视频)
最近准备从B站上下载几个web页面上的视频,但是B站的视频又没有提供相关的下载工具,于是找到了一款下载B站视频的工具( you-get ), 该工具不能下载会员版的视频,不能下载收费的视频,不过对于免 ...
- Python使用pynvml查看GPU信息
参考: https://blog.csdn.net/TracelessLe/article/details/107405544 ==================================== ...
- 生态兼容性进一步提升!白鲸开源 WhaleStudio 与火山引擎ByteHouse完成产品互认
数据作为新型生产要素,已快速融入生产.分配.流通.消费和社会服务管理等各环节,深刻改变着生产方式.生活方式和治理方式.越来越多企业也在尝试充分利用数据要素,开辟全新发展路径,进一步实现业务价值提升. ...
- SMU Spring 2023 Trial Contest Round 11
A. The Text Splitting 题意:给出字符串长度,给出p和q两种切割方式,任选其中一种,把字符串分割输出结果. 题解:先进行判断,p和q是否能整个的分割n,利用p和q的函数关系判断(见 ...