关闭

内存伪共享(False Sharing)

标签: 内存多线程java并发优化
16517人阅读 评论(0) 收藏 举报
分类:

博主注:在考虑优化多线程并发的内存使用场景时, 由于CPU缓存机制不尽相同, 建议至少确保有128字节距离, 一般通过设置不使用哑元(dummy)或者跨区分配来避免命中同一缓存行, 以减少不同处理器由于缓存行相同造成的缓存行频繁载入和剔除的性能消耗.

 

缓存系统中是以缓存行(cache line)为单位存储的。缓存行是2的整数幂个连续字节,一般为32-256个字节。最常见的缓存行大小是64个字节。当多线程修改互相独立的变量时,如果这些变量共享同一个缓存行,就会无意中影响彼此的性能,这就是伪共享。缓存行上的写竞争是运行在SMP系统中并行线程实现可伸缩性最重要的限制因素。有人将伪共享描述成无声的性能杀手,因为从代码中很难看清楚是否会出现伪共享。

为了让可伸缩性与线程数呈线性关系,就必须确保不会有两个线程往同一个变量或缓存行中写。两个线程写同一个变量可以在代码中发现。为了确定互相独立的变量是否共享了同一个缓存行,就需要了解内存布局,或找个工具告诉我们。Intel VTune就是这样一个性能分析工具。本文中我将解释Java对象的内存布局以及我们该如何填充缓存行以避免伪共享。

 

图1说明了伪共享的问题。在核心1上运行的线程想更新变量X,同时核心2上的线程想要更新变量Y。不幸的是,这两个变量在同一个缓存行中。每个线程都要去竞争缓存行的所有权来更新变量。如果核心1获得了所有权,缓存子系统将会使核心2中对应的缓存行失效。当核心2获得了所有权然后执行更新操作,核心1就要使自己对应的缓存行失效。这会来来回回的经过L3缓存,大大影响了性能。如果互相竞争的核心位于不同的插槽,就要额外横跨插槽连接,问题可能更加严重。

Java内存布局(Java Memory Layout)

对于HotSpot JVM,所有对象都有两个字长的对象头。第一个字是由24位哈希码和8位标志位(如锁的状态或作为锁对象)组成的Mark Word。第二个字是对象所属类的引用。如果是数组对象还需要一个额外的字来存储数组的长度。每个对象的起始地址都对齐于8字节以提高性能。因此当封装对象的时候为了高效率,对象字段声明的顺序会被重排序成下列基于字节大小的顺序:

  1. doubles (8) 和 longs (8)
  2. ints (4) 和 floats (4)
  3. shorts (2) 和 chars (2)
  4. booleans (1) 和 bytes (1)
  5. references (4/8)
  6. <子类字段重复上述顺序>

(译注:更多HotSpot虚拟机对象结构相关内容:http://www.infoq.com/cn/articles/jvm-hotspot

了解这些之后就可以在任意字段间用7个long来填充缓存行。在Disruptor里我们对RingBuffer的cursor和BatchEventProcessor的序列进行了缓存行填充。

为了展示其性能影响,我们启动几个线程,每个都更新它自己独立的计数器。计数器是volatile long类型的,所以其它线程能看到它们的进展。

 

public final class FalseSharing
    implements Runnable
{
    public final static int NUM_THREADS = 4; // change
    public final static long ITERATIONS = 500L * 1000L * 1000L;
    private final int arrayIndex;

    private static VolatileLong[] longs = new VolatileLong[NUM_THREADS];
    static
    {
        for (int i = 0; i < longs.length; i++)
        {
            longs[i] = new VolatileLong();
        }
    }

    public FalseSharing(final int arrayIndex)
    {
        this.arrayIndex = arrayIndex;
    }

    public static void main(final String[] args) throws Exception
    {
        final long start = System.nanoTime();
        runTest();
        System.out.println("duration = " + (System.nanoTime() - start));
    }

    private static void runTest() throws InterruptedException
    {
        Thread[] threads = new Thread[NUM_THREADS];

        for (int i = 0; i < threads.length; i++)
        {
            threads[i] = new Thread(new FalseSharing(i));
        }

        for (Thread t : threads)
        {
            t.start();
        }

        for (Thread t : threads)
        {
            t.join();
        }
    }

    public void run()
    {
        long i = ITERATIONS + 1;
        while (0 != --i)
        {
            longs[arrayIndex].value = i;
        }
    }

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

 

结果(Results)

运行上面的代码,增加线程数以及添加/移除缓存行的填充,下面的图2描述了我得到的结果。这是在我4核Nehalem上测得的运行时间。

 

图 2.

 

从不断上升的测试所需时间中能够明显看出伪共享的影响。没有缓存行竞争时,我们几近达到了随着线程数的线性扩展。

这并不是个完美的测试,因为我们不能确定这些VolatileLong会布局在内存的什么位置。它们是独立的对象。但是经验告诉我们同一时间分配的对象趋向集中于一块。

所以你也看到了,伪共享可能是无声的性能杀手(False sharing can be a silent performance killer.)。

 

原文地址:http://mechanical-sympathy.blogspot.com/2011/07/false-sharing.html

原文作者:Martin Thompson  中文译者:丁一  选文有删改

1
0
查看评论

cpu伪共享问题

CPU内部也会有自己的缓存,内部的缓存单位是行,叫做缓存行。在多核环境下会出现CPU之间的内存同步问题(比如一个核加载了一份缓存,另外一个核也要用到同一份数据),如果每个核每次需要时都往内存中存取,这会带来比较大的性能损耗,这个问题一般是通过MESI协议来解决的。     &#...
  • e5945
  • e5945
  • 2012-09-23 21:53
  • 3252

False Sharing问题

在多处理器,多线程情况下,如果两个线程分别运行在不同的CPU上,而其中某个线程修改了cache line中的元素,由于cache一致性的原因,另一个线程的cache line被宣告无效,在下一次访问时会出现一次cache line miss,哪怕该线程根本无效改动的这个元素,因此出现了False S...
  • pennyliang
  • pennyliang
  • 2010-07-26 15:16
  • 10099

由一道淘宝面试题到False sharing问题

今天在看淘宝之前的一道面试题目,内容是 在高性能服务器的代码中经常会看到类似这样的代码: typedef union { erts_smp_rwmtx_t rwmtx; byte cache_line_align_[ERTS_ALC_CACHE_LINE_ALIGN_SIZE(sizeof(...
  • wdzxl198
  • wdzxl198
  • 2013-09-06 15:01
  • 5076

多线程之false sharing问题

在多核快速发展的现在,利用多线程技术提高CPU设备的利用率已经成为一种趋势。然而多核计算机体系架构和单核有了很大的变化,在多线程编程中会碰到一些意想不到的问题,比如多核中非常典型的false sharing问题。下文会非常详细的揭示false sharing产生的根源,以及何如避免来提高程序的性能。...
  • zhanglei8893
  • zhanglei8893
  • 2011-11-12 19:34
  • 2262

多线程伪共享(false sharing)问题分析

在多核的CPU架构中,每一个核心core都会有自己的缓存空间,因此如果一个变量如果同时存在不同的核心缓存空间时,就会出现伪共享(false sharing)的问题。 此时如果一个核心修改了该变量,该修改需要同步到其它核心的缓存。 在linux执行cat /proc/cpuinfo 来查看...
  • realxie
  • realxie
  • 2012-02-07 23:13
  • 7512

多线程false sharing带来的影响和一些优化.

最近在线项目中测试一个无锁队列的性能的时候发现,在一个线程push另一个线程pop整型数据的时候,吞吐量竟然和std::queue+spinlock类似甚至更差,这样完全体现不出lockfree的优势, 决定找找原因. 这个无锁队列是通过一个头指针来push数据,一个尾指针来pop数据来实现的.t...
  • zxjcarrot
  • zxjcarrot
  • 2015-09-09 22:08
  • 449

伪共享(false share)简介

现代计算机都带有很多缓存用于解决CPU极快的计算速度和主存duxie
  • WinWill2012
  • WinWill2012
  • 2014-10-17 20:49
  • 1060

linux编程的108种奇淫巧计-1(FALSE SHARING)【续】

该文有很多网友回复,比较集中的看法是CPU字节对齐,巧合的是有一个朋友用这个代码做了测试,发现对齐和不对齐的代码执行的速度是一样的,原因是他的笔记本安装的linux操作系统,而笔记本是单核的,所以就出现了这个状况,如果和CPU字节对齐,在单核的情况下怎么会速度一样呢?另外如果是CPU字节对齐,把线程...
  • pennyliang
  • pennyliang
  • 2010-10-26 12:40
  • 11385

Java Cache Line 伪共享及解决方案

伪共享问题本质:多个变量被cpu加载在同一个缓存行中,当在多线程环境下,多个变量被不同的cpu执行,导致缓存行失效而引起的大量的缓存命中率降低。缓存一致性协议MESI协议中,每个CPU的Cache控制器不仅知道自己的读写操作,而且也监听其它CPU的读写操作。每个Cache line所处的状态根据本核...
  • xx_yTm
  • xx_yTm
  • 2016-11-15 17:42
  • 944

内存伪共享(False Sharing)

博主注:在考虑优化多线程并发的内存使用场景时, 由于CPU缓存机制不尽相同, 建议至少确保有128字节距离, 一般通过设置不使用哑元(dummy)或者跨区分配来避免命中同一缓存行, 以减少不同处理器由于缓存行相同造成的缓存行频繁载入和剔除的性能消耗.   缓存系统中是以缓存行(cach...
  • rrrfff
  • rrrfff
  • 2015-04-11 10:24
  • 16517
    联系作者
    通过QQ与我联系(全天候7*24小时基本不在线)
    最新评论
    免责声明
    如果转载的文章侵犯了您的版权,请务必告知,我将立刻删除;
    博客所有文章允许转载,原创类不要求注明出处,随意就好;
    如果是转载的文章,建议直接转载原始来源,因为原作者极可能有更新