多线程之 Cache Line 与伪共享
Posted sunfishgao
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了多线程之 Cache Line 与伪共享相关的知识,希望对你有一定的参考价值。
Cache 简介
Cache,即缓存。缓存能提升读取性能,其原理是用性能更好的存储介质存储一部分高频访问的内容,获得总体概率上的速度提升。
在开发中,我们口中的缓存可以是一个变量,或者是 redis。在计算机 CPU 内部,往往指的是 CPU 的各级 cache。
Cache 的一致性
由于是高频访问的内容被重复存储到了好几处地方,必然要考虑一致性。你需要及时清除或者更新缓存中过期内容。在程序设计中,使用缓存的架构通常是给定一个过期时间。而对于 CPU Cache,情况就复杂很多。
CPU Cache 原理
缓存的工作原理是当 CPU 要读取一个数据时,首先从CPU缓存中查找,找到就立即读取并送给 CPU 处理;没有找到,就从速率相对较慢的内存中读取并送给 CPU 处理,同时把这个数据所在的数据块调入缓存中,可以使得以后对整块数据的读取都从缓存中进行,不必再调用内存。正是这样的读取机制使 CPU 读取缓存的命中率非常高(大多数 CPU 可达 90% 左右),也就是说 CPU 下一次要读取的数据 90% 都在 CPU 缓存中,只有大约 10% 需要从内存读取。这大大节省了 CPU 直接读取内存的时间,也使 CPU 读取数据时基本无需等待。总的来说,CPU 读取数据的顺序是先缓存后内存。(摘自百科)
将模型简化以后,如果 CPU 想访问内存里的内容:
CPU Core1 --> L1 Cache --> L2 Cache --> L3 Cache --> RAM
CPU Core2 --> L1 Cache --> L2 Cache --> L3 Cache --> RAM
需要说明的是,每个 CPU 核心都有自己的独立的多级缓存,常见的有三级。访问速度上,L1 > L2 > L3, 容量通常与速度成反比。通俗点说,你在某处声明的变量 int foo = 1;在有缓存情况下,CPU 是从 L1~L3 中获取 foo 的值,多级缓存无命中才去内存中取。
值得一提的是,现如今 Intel 比较新的 CPU 型号,其缓存不再是彼此独立的设计了,双核会共享二级缓存,即“Smart cache” 共享缓存技术。
CPU Cache 也需要考虑一致性问题,在变量被赋值后, Cache 中的数据就脏了,会被清除。
在多线程环境下,不同 Core 中的 Cache 脏数据会更频繁产生,擦除脏数据的成本开销就会显得很大。
另外值得一提的是,多线程不加锁有可能造成 Cache 脏数据不被及时擦除。
Cache Line
MESI(Modified Exclusive Shared Or Invalid)
摘自 https://www.cnblogs.com/shangxiaofei/p/5688296.html
(也称为伊利诺斯协议,是因为该协议由伊利诺斯州立大学提出)是一种广泛使用的支持写回策略的缓存一致性协议,该协议被应用在Intel奔腾系列的CPU中。
MESI协议中的状态
CPU中每个缓存行(caceh line)使用4种状态进行标记(使用额外的两位(bit)表示):
M: 被修改(Modified)
该缓存行只被缓存在该CPU的缓存中,并且是被修改过的(dirty),即与主存中的数据不一致,该缓存行中的内存需要在未来的某个时间点(允许其它CPU读取请主存中相应内存之前)写回(write back)主存。
当被写回主存之后,该缓存行的状态会变成独享(exclusive)状态。
E: 独享的(Exclusive)
该缓存行只被缓存在该CPU的缓存中,它是未被修改过的(clean),与主存中数据一致。该状态可以在任何时刻当有其它CPU读取该内存时变成共享状态(shared)。
同样地,当CPU修改该缓存行中内容时,该状态可以变成Modified状态。
S: 共享的(Shared)
该状态意味着该缓存行可能被多个CPU缓存,并且各个缓存中的数据与主存数据一致(clean),当有一个CPU修改该缓存行中,
其它CPU中该缓存行可以被作废(变成无效状态(Invalid))。
I: 无效的(Invalid)
该缓存是无效的(可能有其它CPU修改了该缓存行)。
False Sharing 伪共享
struct {
int thread1_data;
int thread2_data;
};
同时有两个线程(thread1 和 thread2)只去读写属于他自己的那个变量。看似各玩各的互不影响,实际上由于两个变量挨得很近,往往会被放到一个 Cache Line 中。 thread1 对 thread1_data 的读写,会造成 core2 核上对 thread2_data 的缓存被标记为无效 Invalid。我们知道清理 Invalid 状态是很费时的,如果过高频繁地触发,会造成性能下降。
在多线程读写数组上,尤其要注意这个伪共享问题。
伪共享的本质是,高等语言的概念上,看似变量间是独立的,但是在 CPU Cache 层面, 两个变量地址挨得太近(在一个 Cache Line 范围中)就只能作为整体来看。
以上是关于多线程之 Cache Line 与伪共享的主要内容,如果未能解决你的问题,请参考以下文章