`
jimmee
  • 浏览: 528539 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

3. Java 7与伪共享的新仇旧恨[转载]

阅读更多

原文:False Shareing && Java 7 (依然是马丁的博客)  译者:杨帆 校对:方腾飞

在我前一篇有关伪共享的博文中,我提到了可以加入闲置的long字段来填充缓存行来避免伪共享。但是看起来Java 7变得更加智慧了,它淘汰或者是重新排列了无用的字段,这样我们之前的办法在Java 7下就不奏效了,但是伪共享依然会发生。我在不同的平台上实验了一些列不同的方案,并且最终发现下面的代码是最可靠的。(译者注:下面的是最终版本,马丁在大家的帮助下修改了几次代码)

01 import java.util.concurrent.atomic.AtomicLong;
02  
03 public final class FalseSharing
04     implements Runnable
05 {
06     public final static int NUM_THREADS = 4// change
07     public final static long ITERATIONS = 500L * 1000L * 1000L;
08     private final int arrayIndex;
09  
10     private static PaddedAtomicLong[] longs = newPaddedAtomicLong[NUM_THREADS];
11     static
12     {
13         for (int i = 0; i < longs.length; i++)
14         {
15             longs[i] = new PaddedAtomicLong();
16         }
17     }
18  
19     public FalseSharing(final int arrayIndex)
20     {
21         this.arrayIndex = arrayIndex;
22     }
23  
24     public static void main(final String[] args) throws Exception
25     {
26         final long start = System.nanoTime();
27         runTest();
28         System.out.println("duration = " + (System.nanoTime() - start));
29     }
30  
31     private static void runTest() throws InterruptedException
32     {
33         Thread[] threads = new Thread[NUM_THREADS];
34  
35         for (int i = 0; i < threads.length; i++)
36         {
37             threads[i] = new Thread(new FalseSharing(i));
38         }
39  
40         for (Thread t : threads)
41         {
42             t.start();
43         }
44  
45         for (Thread t : threads)
46         {
47             t.join();
48         }
49     }
50  
51     public void run()
52     {
53         long i = ITERATIONS + 1;
54         while (0 != --i)
55         {
56             longs[arrayIndex].set(i);
57         }
58     }
59  
60     // 这段代码的来历可以看4楼的回复
61     public static long sumPaddingToPreventOptimisation(final int index)
62     {
63         PaddedAtomicLong v = longs[index];
64         return v.p1 + v.p2 + v.p3 + v.p4 + v.p5 + v.p6;
65     }
66  
67     public static class PaddedAtomicLong extends AtomicLong
68     {
69         public volatile long p1, p2, p3, p4, p5, p6 = 7L;
70     }
71 }

用以上这种办法我获得了和上一篇博客里提到的相近的性能,读者可以把PaddedAtomicLong里面那行填充物注释掉再跑测试看看效果。

我想我们大家都有权去跟Oracle投诉,让他们在JDK里默认加入缓存行对齐的函数或者是被填充好的原子类型,这和其他一些底层改变会让Java成为一门真真正正的并发编程语言。我们一直以来不断的在听到他们讲多核时代正在到来,但是我要说的是在这方面Java需要快点赶上来。

———————————————–

(译者注:博文后面的评论和交流也很精彩,也讲述了这段示例代码的进化过程,一起翻译出来:)

1楼:Ashwin Jayaprakash

在前一篇博文中你创建了一个数组来放VolatileLong,这次你又用一个数组放AtomicLongArray(译者注:此处我觉得他可能是写错了,应该是说AtomicLong吧)。
但是如何能保证AtomicLongArray或VolatileLong会被紧挨着分配在内存中?
那么,就算你在一个循环中创建他们,并且很幸运的,他们获得了连续的内存空间,但是依然无法保证这四个实例会在堆空间里紧挨着。如果他们被分布在JVM的旧生代堆里并且没有被压实的话,直到一次主要GC压实旧生代之前,重新分配填充是没必要的,因为他们在堆中是分散的。
所以你最好对读者说明,我们无法控制JVM如何在堆中对这些实例分配内存。(译者注:没办法,要精确控制内存来保证性能的话就不要用Java了,要不直接用C好了)

———————————————–
2楼:马丁

你大体上说的是对的,Ashwin,我们无法保证如何在堆空间中放置Java对象,这是伪共享问题发生的根源。如果你有一些跨线程的指针或者计数器,那么确保他们在不同的缓存行中是非常重要的,否则的话程序就无法按CPU的核数扩展。填充的根本意义在于保护这些跨线程的指针和计数器,以确保他们在不同的缓存行中。
这个是有意义的吧?

———————————————–
3楼:Ashwin Jayaprakash

嗯,有道理。那你可不可以创建一个大的AtomicLongArray,然后让不同的线程去更新第8,16,32个元素呢?(译者注:也算是消除竞争的一个办法,但是既然完全没有竞争还要多线程做什么?)而不是搞四个AtomicLongArray,而每个线程都去竞争访问同一个数组元素。
谢谢马丁花时间写了这么多。
———————————————–
4楼:马丁

如果我可以提前知道更多的业务逻辑那么你说的方式是可行的。但通常情况下在设计一个大型系统的时候,我们无法提前知道很多事情,或者我们要为其他的应用创造一个通用的类库。
我很难为很多不同的上下文场景写一个足够小巧简单的示例,而我上面写的示例是为了说明当伪共享发生的时候有多糟糕。如果你在你的数据结构中做了填充,那么你就不必担心他们在内存中如何分配。我们用一个更好的方案来替代AtomicLong,并且你可以使用AtomicLong的所有常规方法:

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

我是多希望Java委员会可以认识到这个问题的严重性,并且在JDK里加入对缓存行对齐和填充的基础方法。这是在Disruptor中有关性能BUG的最大根源。
我也根据以上的反馈更新了文章。

———————————————–

5楼:Gil Tene
马丁,我很同意你的观点,如果我们有一种办法可以指定某个字段占有独自的缓存行,并且让JVM自动处理如何在对象布局上的正确填充,那这个世界会和谐的多。你搞的这个人造填充将会是很美好的一个事情,但是你也知道,实际上的对象布局情况要取决于JVM的特定实现。
我是一个偏执狂,我给你的填充方案里加了一些东西,使那些个用于填充的字段很难被JVM优化掉。一个耍小聪明的JVM还是会把你用于填充的P1-P7的字段优化掉,原理是这样滴:PaddedAtomicLong类如果只对final的FalseSharing类可见(就是说PaddedAtomicLong不能再被继承了)。这样一来编译器就会“知道”它正在审视的是所有可以看到这个填充字段的代码,这样就可以证明没有行为依赖于p1到p7这些字段。那么“聪明”的JVM会把上面这些丝毫不占地方的字段统统优化掉。
那么针对这样的情况,你可以巧妙的让PaddedAtomicLong类在FalseSharing类之外可见,比如直接加一个依赖于p1到p7的公开的访问函数,并且这个函数在理论上可以被外界访问到。

———————————————–
6楼:马丁
我根据Gil的反馈做了修改。

———————————————–
7楼:Stanimir Simeonoff
直接用一个数组并且把元素放在中间的位置上(或者直接用bytebuffer,而你却为了这个写了这么一大篇),Java是不会重排他们的,我就是这样来消除伪共享的。

———————————————–
8楼:马丁
我以前经常像你这么干,比如搞一个长度是15的数组,把元素放在正中间,但是,如果我们需要volatile这个语意就行不通了。对于你的情况来说,你只需要用AtomicLongArray或者类似的。根据我的测量,在一个算法中,这种间接引用(译者注:原词是indirection,我理解也许是指间接引用,即不是直接使用数组,而是使用AtomicLongArray这种包装过的数组)和边界检查的消耗是显著的。
据我所知,一些人建议加入@Contened注解来标记一个字段,让这个被标记的字段拥有独立的缓存行,我希望这个快点到来。

8.1楼:John
你好,马丁,我看到在Disruptor当前的版本中Sequence类用的是unsafe.compareAndSwapLong(..)来更新第七个下标的long。
为什么不数组的长度不是15或者是其他的数值?如果长度是15的话会把2级缓存的缓存行也填充掉么?
谢谢。

8.2楼:马丁

因为用7个下标保证了会有56个字节填充在数值的任何一边,56字节的填充+8字节的long数值正好装进一行64字节的缓存行。
———————————————–
9楼:Stanimir Simeonoff

是的,马丁,我指的就是AtomicLongArray,如果你不想为间接引用和边界检查买单,Unsafe 是一个选项(甚至总是这样)。
———————————————–
10楼:Mohan Radhakrishnan

哪里有一些简单硬件说明书是讲述缓存行的关键概念么?我想找一些插图什么的来理解核心和缓存直接如何交互造成了伪共享。
———————————————–
11楼:马丁
你可以参照下面这个PDF的第3和第4章:
http://img.delivery.net/cm50content/intel/ProductLibrary/100412_Parallel_Programming_02.pdf
———————————————–
12楼:ying
你的博客太NB了,多谢马丁,我关于填充有两个疑问:
1.long占8字节,对象引用占16字节,但是这个实现是

1 public final static class VolatileLong // 16byte</pre>
2 {
3 public volatile long value = 0L; // 8 byte
4 public long p1, p2, p3, p4, p5, p6; // 6*8 = 48byte
5 }

看起来好像是72个字节啊。
2.你是发现这个问题的?是去查汇编代码吗?

12.1楼:马丁
我不希望在缓存行中的标记字在取出锁或者垃圾回收器在老化对象的时候被修改。
就算默认启用64位模式的压缩指针,它还是会包含类指针在对象的头部。
https://wikis.oracle.com/display/HotSpotInternals/CompressedOops
这个伪共享的问题我是在多年前发现的,但是我为一个应用做性能测试,发现性能时高时低,追查原因下去发现是伪共享问题。

12.2楼:ying
那你是如何缩小问题的范围最后发现问题的呢?需要深入分析汇编代码么?

11.3楼:马丁
汇编代码是不会显示出问题的,你需要去追查为什么CPU的2级缓存总是不命中,追查下去就知道了。
———————————————–

13楼:Joachim

关于@Contended注解的提案在这里:
http://mail.openjdk.java.net/pipermail/hotspot-dev/2012-November/007309.html
牛文啊,赞!

分享到:
评论

相关推荐

    axis2.jar 解决 org.apache.axis2.util.JavaUtils.callStackToString问题

    &lt;Call Stack = DEBUG_FRAME = org.apache.axis2.util.JavaUtils.callStackToString(JavaUtils.java:564) DEBUG_FRAME = org.apache.axis2.description.ParameterIncludeImpl.debugParameterAdd(ParameterIncludeImpl...

    Java开发技术大全(500个源代码).

    commSource.java 一个共享资源的类 demoSynchrony.java 演示线程的同步 setDataThread.java 设置数据的线程类 readDataThread.java 读取数据的线程类 demoEnhanceThread.java 使用自己定义的线程类示例 ...

    Java中的伪共享详解及解决方案.docx

    Java中的伪共享详解及解决方案.docx

    DiskLruCache.java 完整源码

    Android DiskLruCache的源码

    JAVA API官方中文版手册chm文件文档

    java.applet java.awt java.awt.color java.awt.datatransfer java.awt.dnd java.awt.event java.awt.font java.awt.geom java.awt.im java.awt.im.spi java.awt.image java.awt.image.renderable java....

    Learning.Reactive.Programming.With.Java.8

    The book starts with an explanation of what reactive programming is, why it is so appealing, and how we can integrate it in to Java. It continues by introducing the new Java 8 syntax features, such as...

    全国计算机二级java题库

    二级java 公共基础知识部分30分 专业语言部分 70分 Java语言程序设计 基本要求: 1. 掌握Java语言的特点,实现机制和体系结构。 2. 掌握Java语言中面向对象的特性。 3. 掌握Java语言提供的数据类型和结构。 4. 掌握...

    java控制扫描仪控件(附:程序源代码)

    本源代码包括:TestJTwain.java,ScanTwice.java,DemoFrame.java,DemoADF.java,DemoFrame.java,DemoGetCapabilities.java,DemoHiddenUI.java,ImageDisplayer.java,DemoSaveJPEG.java等等一系列扫描仪功能,...

    30个java工具类

    [工具类] CookieCounter .java.txt [工具类] 验证码img .jsp.txt [工具类] Java中计算任意两个日期之间的工作天数 .java.txt [工具类] java抓取网页 .java.txt [工具类] MD5 .java.txt [工具类] MD5强化版 .java.txt...

    基于java swing 的学生信息管理系统

    maniframe.java是主窗口类 studentmanageframe.java是学生信息主窗口类 studentinfo.java是学生查询 添加 删除类 DBconnection.java是连接数据库公用类 addframe.java是添加学生信息类 studentcou.java 学生...

    Java高手真经(编程基础卷)光盘全部源码 免积分

    看到那些要积分的很不酸,发布免费版本。...javareflection.zip 26.Java反射机制与动态代理 javageneric.zip 27.Java泛型编程 javaannotation.zip 28.Java注释符编程 javafeature.zip 29.Java5.0语言新特性

    Md5Util.java

    Md5Util.java

    WordCount.java

    WordCount.java,代码详细解释,已测试,输出结果为: Bye 1 Goodbye 1 Hadoop 2 Hello 2 World 2

    JAVA实现的屏幕共享程序

    使用JAVA写的简单屏幕共享程序,服务器端截取屏幕图片多线程发送给局域网内的客户端,客户端接受并显示

    通用分页工具类PageInfo.java

    通用分页工具类PageInfo.java

    db2java.jar db2java.jar

    db2java.jar db2java.jar db2java.jar db2java.jar

    JAVA_API1.6文档(中文)

    java.awt.geom 提供用于在与二维几何形状相关的对象上定义和执行操作的 Java 2D 类。 java.awt.im 提供输入方法框架所需的类和接口。 java.awt.im.spi 提供启用可以与 Java 运行时环境一起使用的输入方法开发的...

    Thinking.in.Java.第四版完整英文版part6

    Thinking.in.Java.第四版完整英文版pdf格式电子书part6 一共分成7个包

    java api最新7.0

    java.awt.geom 提供用于在与二维几何形状相关的对象上定义和执行操作的 Java 2D 类。 java.awt.im 提供输入方法框架所需的类和接口。 java.awt.im.spi 提供启用可以与 Java 运行时环境一起使用的输入方法开发的接口...

Global site tag (gtag.js) - Google Analytics