伪共享( false sharing )

一、相关概念

      (1)CPU缓存介绍:

           以近代CPU的视角来说,它们的作用都是作为CPU主内存之间的高速数据缓冲区L1最靠近CPU核心;L2其次;L3再次

                                                       

     (2)  任务管理器可以看到其中的大小

                                           

     (3)L1 L2 L3详细介绍:

                    L11级)高速缓存是计算机系统中存在的最快内存。在访问优先级方面,L1缓存具有CPU在完成特定任务时最可能需要的数据L1缓存通常也有两种分割方式,分为指令缓存数据缓存。指令高速缓存处理有关CPU必须执行的操作的信息,而数据高速缓存保存要在其上执行操作的数据

                   L2(级别2)缓存比L1缓存慢,在大多数现代CPU中,L1L2高速缓存存在于CPU内核本身,每个内核都有自己的高速缓存

                   L3Level 3)缓存是最大的缓存单元,也是最慢的缓存单元。它的范围在4MB50MB之间。现代CPUCPU裸片上 有专用空间用于L3缓存,占用了大量空间

        (4) 缓存命中或错过和延迟

数据从RAM流到L3缓存,然后是L2,最后是L1

当处理器正在寻找执行操作的数据时,它首先尝试在L1高速缓存中找到它。如果CPU能够找到它,则该条件称为缓存命中

然后它继续在L2找到它,然后在L3中找到它

如果找不到数据,它会尝试从主存储器访问它。这称为缓存未命中。

        (4)高性能异步处理框架 Disruptor,它被誉为“最快的消息框架”,其 LMAX 架构能够在一个线程里每秒处理 6百万 订单!在讲到 Disruptor 为什么这么快时,接触到了一个概念————  伪共享( false sharing )

        其中提到:缓存行上的写竞争是运行在 SMP(多对称系统) 系统中并行线程实现可伸缩性最重要的限制因素。由于从代码中很难看出是否会出现伪共享,有人将其描述成无声的性能杀手。

        伪共享的非标准定义为:缓存系统中是以缓存行(cache line)为单位存储的,当多线程修改互相独立的变量时,如果这些变量共享同一个缓存行,就会无意中影响彼此的性能,这就是伪共享。

        缓存行:缓存行是2的整数幂个连续字节,一般为32-256个字节。最常见的缓存行大小是64个字节。

       

        我们可以通过缓存行填充来消除伪共享,上图是两个long类型的x变量和y变量。他们是连续存储的,变量x,y同时被放到了CPU的一级和二级缓存当线程1使用CPU1对变量x进行更新时候

首先会修改cpu1的一级缓存变量x所在缓存行,这时候缓存一致性协议会导致cpu2中变量x对应的缓存行失效那么线程2写入变量x的时候就只能去二级缓存去查找,这就破坏了一级缓存,而一级缓存比二级缓存更快。

更坏的情况下如果cpu只有一级缓存,那么会导致频繁的直接访问主内存。

 

二、为什么会产生伪共享

        伪共享的产生是因为多个变量被放入了一个缓存行,并且多个线程同时去写入缓存行中不同变量。那么为何多个变量会被放入一个缓存行那。其实是因为Cache与内存交换数据的单位就是Cache,当CPU要访问的

变量没有在Cache命中时候,根据程序运行的局部性原理会把该变量在内存中大小为Cache行的内存放如缓存行。

         long a;    long b;    long c;    long d;声明了四个long变量,假设cache行的大小为32个字节,那么当cpu访问变量a时候发现该变量没有在cache命中,那么就会去主内存把变量a以及内存地址附近的b,c,d放入缓存行。

也就是地址连续的多个变量才有可能会被放到一个缓存行中,当创建数组时候,数组里面的多个元素就会被放入到同一个缓存行。

产生原因:

数据XYZ被加载到同一Cache Line

线程ACore1修改X线程BCore2上修改Y

根据MESI,假设Core1是第一个发起操作的CPU核,Core1上的L1 Cache LineS(共享)状态变成M(修改,脏数据)状态,然后告知其他的CPU核,图例则是Core2引用同一地址的Cache Line已经无效了

Core2发起写操作时,首先导致Core1X写回主存Cache Line状态由M变为I(无效),而后才是Core2从主存重新读取该地址内容Cache Line状态由I变成E(独占),最后进行修改Y操作Cache LineE变成M。可见多个线程操作在同一Cache Line上的不同数据,相互竞争同一Cache Line导致线程彼此牵制影响,变成了串行程序,降低了并发性

解决方法:

此时我们则需要将共享在多线程间的数据进行隔离,使他们不在同一个Cache Line上,从而提升多线程的性能。即 缓存行的填充

附图:MESI协议

                       

三、如何消伪共享

        (1)JDK8之前一般都是通过字节填充的方式来避免,也就是创建一个变量的时候使用填充字段填充该变量所在的缓存行,这样就避免了多个变量存在同一个缓存行。

        public final static class FilledLong {
            public volatile long value = 0L;
            public long p1, p2, p3, p4, p5, p6;    
        }

        假如Cache行为64个字节,那么我们在FilledLong类里面填充了6个long类型变量,每个long类型占用8个字节,加上value变量的8个字节总共56个字节,另外这里FilledLong是一个类对象,

而类对象的字节码的对象头占用了8个字节,所以当new一个FilledLong对象时候实际会占用64个字节的内存,这个正好可以放入Cache的一个行。

         (2)在JDK8中提供了一个sun.misc.Contended注解,用来解决伪共享问题,上面代码可以修改为如下:

        

         @sun.misc.Contended 
         public final static class FilledLong {
            public volatile long value = 0L;
        }

        (3)上面是修饰类的,当然也可以修饰变量,比如Thread类中的使用:

        @sun.misc.Contended("tlr")
        long threadLocalRandomSeed;


        @sun.misc.Contended("tlr")
        int threadLocalRandomProbe;


        @sun.misc.Contended("tlr")
        int threadLocalRandomSecondarySeed;

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
伪共享False Sharing)是指多个线程同时访问不同但位于同一缓存行的变量时,由于缓存一致性协议的限制,会导致缓存行多次在不同的 CPU 之间进行传递,从而降低了程序的性能。 举一个简单的例子:假设两个线程同时访问同一缓存行中的不同变量,如下所示: ``` struct CacheLine { int x; int y; } cacheLine; // 线程 1 void thread1() { while (true) { cacheLine.x++; } } // 线程 2 void thread2() { while (true) { cacheLine.y++; } } ``` 在这个例子中,由于 `x` 和 `y` 位于同一缓存行中,所以在多个线程同时访问它们时,会导致缓存行多次在不同的 CPU 之间进行传递,从而降低了程序的性能。 为了避免伪共享,可以采用以下两种方法: 1. 添加填充:为了让不同的变量位于不同的缓存行中,可以在变量之间添加一些填充,从而让它们位于不同的缓存行中,如下所示: ``` struct CacheLine { int x; char padding[60]; // 填充 int y; } cacheLine; ``` 2. 使用 `std::atomic`:使用 `std::atomic` 可以确保多线程对变量的访问是原子的,从而避免了伪共享的问题,如下所示: ``` struct CacheLine { std::atomic<int> x; std::atomic<int> y; } cacheLine; ``` 以上两种方法都可以有效地避免伪共享的问题,提高程序的性能。但是,需要注意的是,添加填充会增加内存的消耗,而使用 `std::atomic` 会增加程序的开销。因此,需要根据具体的场景选择合适的方法。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

qq_270490096

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值