False Sharing 伪共享 – 译

标签: false sharing 共享 | 发表时间:2013-11-20 22:59 | 作者:
出处:http://www.iteye.com

 

原文地址: http://coderbee.net/index.php/basis/20131110/566

翻译自: http://mechanical-sympathy.blogspot.com/2011/07/false-sharing.html

伪共享

内存在缓存系统里是以缓存行为单位存储的。缓存行是大小为2的整数幂的连续字节,典型是32-256字节。最普遍的缓存行大小是64字节。伪共享是 个术语,用于当多个线程不知不觉地相互影响对方的性能,在修改使用相同缓存行的依赖变量时。缓存行上的写冲突是并行执行线程在SMP(对称多处理器 )系统上获得伸缩性的单一最大制约因素。我已听到伪共享被描述为无声的性能杀手,因为在查看代码时,它非常不明显。

为了达到按线程数量的线性伸缩性,我们必须确保没有两个线程写同一个变量或同一个缓存行。两个线程写同一个变量可以在代码层面跟踪到。为了知道独立 变量是否共享同一个缓存行,我们需要知道内存布局,或者让工具告诉我们。 Intel VTune是这样的一个分析工具。在这篇文章,我将解释Java对象的内存布局和如何通过填充缓存行来避免伪共享。

false-sharing
上图展示了伪共享的问题。线程A允许在Core 1,想更新变量X,运行在Core 2的线程想更新变量Y。不幸的是这两个热门变量属于同一个缓存行。每个线程将竞争缓存行的所有权,这样它们可以更新它。如果Core 1获得所有权,缓存子系统需要作废Core 2对应的缓存行。当Core 2获得所有权并执行它的更新,Core 1 将被告诉去作废它的缓存行拷贝。这将来来回回往返让L3缓存极大影响性能。如果竞争的Core处于不同的sockets,那么需要额外的跨Socket互 连,这个问题将更加恶化。

Java 内存布局

对于HotSpot JVM,所有对象都有一个2-字(world)的头。第一个是“标记(mark)”字,有24比特位用于哈希码和8比特位用于标记,例如锁状态,或交换用 于锁对象。第二个字是指向对象所属类的引用。数组有额外的字用于数组的大小。每个对象对齐到8字节的粒度边界,为了性能。因而为了更高效填充,对象的字段 将被重排序,从声明的顺序到下面的基于字节大小的顺序:

  1. doubles (8) and longs (8)
  2. ints (4) and floats (4)
  3. shorts (2) and chars (2)
  4. booleans (1) and bytes (1)
  5. references (4/8)
  6. <repeat for sub-class fields>

(译注:上面小括号里的数字表示占用的字节数。)

有了这些知识,我们可以用7个long在任意字段之间填充缓存行。在Disruptor里,我们在RingBuffer游标和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
    }
}

结果

运行上面的代码,逐渐增加线程数量和添加/移除缓存行填充,我得到的结果如下图所示。这是衡量运行在我的4核 Nehalem 上的测试持续时间。
false-sharing-test-result

伪共享的影响是清晰可见的,通过完成测试需要的时间的增加。没有缓存行竞争,我们可以达到接近按线程线性的扩展。

这不是一个完美等测试,因为我们不能确定VolatileLongs将布局到内存的哪里。他们是独立的变量。然而,经验显示同时分配的对象倾向于同地协作(co-located)。

现在你知道,伪共享是无声的性能杀手了。

另一个测试例子

这是来自作者的另一篇文章: http://mechanical-sympathy.blogspot.com/2011/08/false-sharing-java-7.html,由于相关,就放一起了。

看起来Java 7变得更聪明了,会消除或重排序未使用的字段,这重新引入伪共享。作者认为下面的代码是最可靠的:

    import java.util.concurrent.atomic.AtomicLong;

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 PaddedAtomicLong[] longs = new PaddedAtomicLong[NUM_THREADS];
    static
    {
        for (int i = 0; i < longs.length; i++)
        {
            longs[i] = new PaddedAtomicLong();
        }
    }

    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].set(i);
        }
    }

    public static long sumPaddingToPreventOptimisation(final int index)
    {
        PaddedAtomicLong v = longs[index];
        return v.p1 + v.p2 + v.p3 + v.p4 + v.p5 + v.p6;
    }

    public static class PaddedAtomicLong extends AtomicLong
    {
        public volatile long p1, p2, p3, p4, p5, p6 = 7L;
    }
}


已有 0 人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐



相关 [false sharing 共享] 推荐:

False Sharing 伪共享 – 译

- - ITeye博客
原文地址: http://coderbee.net/index.php/basis/20131110/566. 内存在缓存系统里是以缓存行为单位存储的. 缓存行是大小为2的整数幂的连续字节,典型是32-256字节. 伪共享是 个术语,用于当多个线程不知不觉地相互影响对方的性能,在修改使用相同缓存行的依赖变量时.

从Java视角理解伪共享(False Sharing)

- - 淘宝网通用产品团队博客
从Java视角理解系统结构连载, 关注我的微博( 链接)了解最新动态从我的 前一篇博文中, 我们知道了CPU缓存及缓存行的概念, 同时用一个例子说明了编写单线程Java代码时应该注意的问题. 下面我们讨论更为复杂, 而且更符合现实情况的多核编程时将会碰到的问题. 这些问题更容易犯, 连j.u.c包作者Doug Lea大师的JDK代码里也存在这些问题.

生命即为分享 Life is for sharing

- 珍珠 - 腾讯CDC
  我们若想改变弱势群体的现状,捐款不是唯一方法. 如同经济学家E.F.舒马赫在其1973年出版的经典著作《小的是美好的》所指出的:“每个人都在他有限的领域里积累经验. 能提供的最佳援助是知识上的援助. 知识的礼物效果也维持得长些,也和发展更相关一些. 就如同谚语所说的,授人以鱼,不如授人以渔. 现在我们把这个说法提到更高层次:提供给他捕鱼工具,就算成效甚佳,这个人日后还要你提供替换零件.

cosplaygirl: All sizes | 阿宅反抗軍電台 | Flickr - Photo Sharing!

- 貝殼 - ero.tumblr.photo
All sizes | 阿宅反抗軍電台 | Flickr - Photo Sharing!.

[转]java 混合模式 ,解释模式 (java.vm.info=mixed mode, sharing)

- - 小鸥的博客
 设置zip/jar资源或者类(.class文件)存放目录路径.  追加zip/jar资源或者类(.class文件)存放目录路径.  预先加载zip/jar资源或者类(.class文件)存放目录路径.  设置JVM初始化堆内存大小.  设置JVM最大的堆内存大小.  执行严格的代码检查,预测可能出现的情况.

共享成本

- wooden - 不许联想
如果再谈论什么音乐版权,主张听唱片不要听MP3,就会显得有些傻逼了. 之前我只要在博客上说,就会有一帮共享主义的捍卫者跳出来跟我理论. 甚至,IT界的人认为版权的概念该改改了——你直接说不该有版权不就完了,改个毛啊. 怎么改它都是一种权利,除非你消灭这种权利. 但我现在在思考这样一个问题,它是一个哲学问题,不是版权问题.

实践:管理共享库

- jyf1987 - IBM developerWorks 中国 : 文档库
许多 Linux 应用使用共享库以及程序只在运行时才会进行链接的可执行代码. 这种动态链接减少了软件包的大小和内存要求. Linux 提供了使用共享库的工具,而了解该工具是管理员技能的重要组成部分. 本文中的练习和解决方案使您能够对管理共享库进行实践.

tomcat集群(共享session)

- - 研发管理 - ITeye博客
其实就是上述这样的一个架构,下面是原理. 1)  Apache装有一个模块,这个模块叫mod_jk. 2)  Apache通过80端口负责解析任何静态web内容. 3)  任何不能解析的内容,用表达式告诉mod_jk,让mod_jk派发给相关的app server去解释. 通过上述的文字描述我们可以得知:.

集群session共享机制

- - 企业架构 - ITeye博客
        现在集群中使用的Session共享机制有两种,分别是session复制和session粘性.        该种方式下,负载均衡器会根据各个node的状态,把每个request进行分发,使用这样的测试,必须在多个node之间复制用户的session,实时保持整个集群中用户的状态同步.

共享一些加固方案

- 逆风迎上 - 冷漠 &#39; Blog
共享一些项目中的加固方案给大家,希望对帮助有需要的朋友···. 加固方案包括:windows 、Linux 、Hp-unix 、aix、solaris 等系统··. 之前一直想将项目中的一些文档共享出来,可是因为很多涉及到客户的机密以及其他公司的利益,所以一直没放出来··以后会逐步放些不是太重要的东西共享给大家··希望能够对大家有用··.