mirror of
https://github.com/zhwei820/learn.lianglianglee.com.git
synced 2025-11-19 23:53:48 +08:00
fix img
This commit is contained in:
@@ -286,11 +286,11 @@ long p10, p11, p12, p13, p14, p15, p16, p17;
|
||||
</code></pre>
|
||||
<p>可以看出,MpscXxxPad 类中使用了大量 long 类型的变量,其命名没有什么特殊的含义,只是起到填充的作用。如果你也读过 Disruptor 的源码,会发现 Disruptor 也使用了类似的填充方法。Mpsc Queue 和 Disruptor 之所以填充这些无意义的变量,是为了解决伪共享(false sharing)问题。</p>
|
||||
<p>什么是伪共享呢?我们有必要补充这方面的基础知识。在计算机组成中,CPU 的运算速度比内存高出几个数量级,为了 CPU 能够更高效地与内存进行交互,在 CPU 和内存之间设计了多层缓存机制,如下图所示。</p>
|
||||
<p><img src="assets/CgqCHl_sPgiAL4s1AANDqZ3wy8M196.png" alt="Drawing 3.png" /></p>
|
||||
<p><img src="assets/CgqCHl_sPgiAL4s1AANDqZ3wy8M196.png" alt="png" /></p>
|
||||
<p>一般来说,CPU 会分为三级缓存,分别为<strong>L1 一级缓存</strong>、<strong>L2 二级缓存</strong>和<strong>L3 三级缓存</strong>。越靠近 CPU 的缓存,速度越快,但是缓存的容量也越小。所以从性能上来说,L1 > L2 > L3,容量方面 L1 < L2 < L3。CPU 读取数据时,首先会从 L1 查找,如果未命中则继续查找 L2,如果还未能命中则继续查找 L3,最后还没命中的话只能从内存中查找,读取完成后再将数据逐级放入缓存中。此外,多线程之间共享一份数据的时候,需要其中一个线程将数据写回主存,其他线程访问主存数据。</p>
|
||||
<p>由此可见,引入多级缓存是为了能够让 CPU 利用率最大化。如果你在做频繁的 CPU 运算时,需要尽可能将数据保持在缓存中。那么 CPU 从内存中加载数据的时候,是如何提高缓存的利用率的呢?这就涉及缓存行(Cache Line)的概念,Cache Line 是 CPU 缓存可操作的最小单位,CPU 缓存由若干个 Cache Line 组成。Cache Line 的大小与 CPU 架构有关,在目前主流的 64 位架构下,Cache Line 的大小通常为 64 Byte。Java 中一个 long 类型是 8 Byte,所以一个 Cache Line 可以存储 8 个 long 类型变量。CPU 在加载内存数据时,会将相邻的数据一同读取到 Cache Line 中,因为相邻的数据未来被访问的可能性最大,这样就可以避免 CPU 频繁与内存进行交互了。</p>
|
||||
<p>伪共享问题是如何发生的呢?它又会造成什么影响呢?我们使用下面这幅图进行讲解。
|
||||
<img src="assets/Ciqc1F_sPh-AQ8DnAARFoCjQlXQ789.png" alt="Drawing 4.png" /></p>
|
||||
<img src="assets/Ciqc1F_sPh-AQ8DnAARFoCjQlXQ789.png" alt="png" /></p>
|
||||
<p>假设变量 A、B、C、D 被加载到同一个 Cache Line,它们会被高频地修改。当线程 1 在 CPU Core1 中中对变量 A 进行修改,修改完成后 CPU Core1 会通知其他 CPU Core 该缓存行已经失效。然后线程 2 在 CPU Core2 中对变量 C 进行修改时,发现 Cache line 已经失效,此时 CPU Core1 会将数据重新写回内存,CPU Core2 再从内存中读取数据加载到当前 Cache line 中。</p>
|
||||
<p>由此可见,如果同一个 Cache line 被越多的线程修改,那么造成的写竞争就会越激烈,数据会频繁写入内存,导致性能浪费。题外话,多核处理器中,每个核的缓存行内容是如何保证一致的呢?有兴趣的同学可以深入学习下缓存一致性协议 MESI,具体可以参考 <a href="https://zh.wikipedia.org/wiki/MESI协议">https://zh.wikipedia.org/wiki/MESI%E5%8D%8F%E8%AE%AE</a>。</p>
|
||||
<p>对于伪共享问题,我们应该如何解决呢?Disruptor 和 Mpsc Queue 都采取了空间换时间的策略,让不同线程共享的对象加载到不同的缓存行即可。下面我们通过一个简单的例子进行说明。</p>
|
||||
|
||||
Reference in New Issue
Block a user