FalseSharing
发布日期:2021-06-29 18:38:30 浏览次数:2 分类:技术文章

本文共 2407 字,大约阅读时间需要 8 分钟。

文章目录

FalseSharing

CPU缓存架构

  • CPU 和主内存间有好几级缓存,
    • 直接访问主内存很慢
  • 若对一块数据做相同的运算多次,执行运算时把它加载到离 CPU 很近的地方
  • 如循环计数,不想每次循环都跑到主内存去取这个数据来增长它吧。

在这里插入图片描述

  • L1 很小很快,紧靠着在使用它的 CPU 内核。
  • L2 大些,慢些,只能被一个单独的核使用。
  • L3 在现代多核机器中更普遍,更大,更慢,且被单个插槽上的所有CPU核共享。
  • 主存保存着程序运行的所有数据,更大,更慢,
    • 由全部插槽上的所有 CPU 核共享。
  • CPU运算时,先去L1找,再去L2,然后L3,
    • 最后如果这些缓存中都没有,所需的数据就要去主内存拿。
    • 走得越远,运算耗时就越长。
  • so 若进行很频繁的运算,要确保数据在 L1 缓存。

CPU缓存行

  • 缓存由缓存行组成,常64字节(旧的处理器缓存行32)

    • 它有效地引用主内存中的一块地址。
  • 一个缓存行中可存 8 个 java的long

    在这里插入图片描述

  • 程序运行时,缓存每次更新都从主内存中加载连续64

  • 设想有个long型a,是单独的变量,另个long 型变量 b 挨着它,

    • 当加载a时将免费加载b。
  • 若核的线程在对 a 改,另一个核的线程却对 b 读。

  • 当前者改a 时,会把a和b同时加载到前者核心的缓存行中,更新完a后其它所有包含a的缓存行都将失效

    • 因为其它缓存中的a不是最新值了。
      在这里插入图片描述
  • 这就出现问题,

  • b 和 a 不相干,每次却要因为 a 的更新需要从主内存重新读取,它被缓存未命中给拖慢了。

  • 此即传说中的伪共享。

伪共享

  • 当多线程修改互相独立的变量时,若这些变量共享同一个缓存行,就会无意中影响彼此的性能,
    • 即伪共享。
  • 下面例子说明了伪共享是咋肥事。
public class FalseSharingTest {
public static void main(String[] args) throws InterruptedException {
testPointer(new Pointer()); } private static void testPointer(Pointer pointer) throws InterruptedException {
long start = System.currentTimeMillis(); Thread t1 = new Thread(() -> {
for (int i = 0; i < 100000000; i++) {
pointer.x++; } }); Thread t2 = new Thread(() -> {
for (int i = 0; i < 100000000; i++) {
pointer.y++; } }); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(System.currentTimeMillis() - start); System.out.println(pointer); }}class Pointer {
volatile long x; volatile long y;}
  • 声明Pointer类,含x和y (必须声明为volatile,保证可见性,关于内存屏障后面再讲),一个线程对 x 自增1亿,一个线程对 y 自增1亿。
  • x 和 y 没关系,但更新 x 时会把其它包含 x 的缓存行失效,同时也就失效了 y,运行这段程序输出的时间为3890ms。

避免伪共享

  • 一个缓存行64 字节,
  • long 8 字节,
  • 避免伪共享很简单

在两个 long 类型的变量之间再加 7 个 long 类型

  • 把Pointer改成下面
class Pointer {    volatile long x;    long p1, p2, p3, p4, p5, p6, p7;    volatile long y;}
  • 运行程序,时间为695

重新创建自己的 long 类型,而不是 java 自带的 long

  • 修改Pointer
class Pointer {
MyLong x = new MyLong(); MyLong y = new MyLong();}class MyLong {
volatile long value; long p1, p2, p3, p4, p5, p6, p7;}
  • pointer.x++; 改pointer.x.value++;,pointer.y++; 改为pointer.y.value++;,
  • 间724

使用 @sun.misc.Contended 注解(java8)

  • 修改 MyLong 如下:
@sun.misc.Contendedclass MyLong {
volatile long value;}
  • 默认使用这个注解是无效的,需要在JVM启动参数加上-XX:-RestrictContended才生效,,
  • 再次运行程序718ms。
  • 以上三种方式中的前两种是通过加字段的形式实现的,加的字段没有地方使用,
    • 可能会被jvm优化掉,建议用第三种。

参考链接

  • https://www.jianshu.com/p/7758bb277985

转载地址:https://cyj666.blog.csdn.net/article/details/103244660 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:C++中 volatile
下一篇:ping命令

发表评论

最新留言

不错!
[***.144.177.141]2024年04月22日 21时16分59秒