This commit is contained in:
by931
2022-09-06 22:30:37 +08:00
parent 66970f3e38
commit 3d6528675a
796 changed files with 3382 additions and 3382 deletions

View File

@@ -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 &gt; L2 &gt; L3容量方面 L1 &lt; L2 &lt; 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>