JVM伪共享

伪共享False sharing说明JVM底层技术也不让人那么放心。

内存缓存系统中基本单元是高速缓存行(Cache lines). cpu会把数据从内存加载到高速缓存中 ,这样可以获得更好的性能,高速缓存默认大小是64 Byte为一个区域,一个区域在一个时间点只允许一个核心操作,也就是说不能有多个核心同时操作一个缓存区域。

因为高速缓存是64字节,而Hotspot JVM的对象头是两个部分组成,第一部分是由24字节的hash code和8字节的锁等状态标识组成,第二部分是指向该对象类的引用。基本类型字节如下:
doubles (8) and longs (8)
ints (4) and floats (4)
shorts (2) and chars (2)
booleans (1) and bytes (1)
references (4/8)

因此,一个高速缓存64字节可以放下多个字段,如果这多个字段位于同一个高速缓存区,虽然它们是类的不同字段,如下代码:


Class A{
int x;
int y;
}

x和y被放在同一个高速缓存区,如果一个线程修改x;那么另外一个线程修改y,必须等待x修改完成后才能实施。

虽然两个线程修改各种独立变量,但是因为这些独立变量被放在同一个高速缓存区,性能就影响了。测试结果如后面。

当多核CPU线程同时修改在同一个高速缓存行各自独立的变量时,会不自不觉地影响性能,这就发生了伪共享False sharing,伪共享是性能的无声杀手。

解决方便是将高速缓存剩余的字节填充填满(pad),确保不发生多个字段被挤入一个高速缓存区,下面测试结果图就是和填充后性能比较。

实现字节填充的框架有 Disruptor,在RingBuffer中实现填充。关于Disruptor可见infoQ这个视频,用1毫秒的延时得到100K+ TPS吞吐量,JDK的ArrayQueue并行环境不见得是最快的,该视频后面讨论很多,让人大跌眼镜啊,开放源码多有好处啊,别人能发现你不能发现的漏洞。另外一篇解剖Disruptor


C也有类似伪共享发生,见这里

[该贴被banq于2011-09-01 09:21修改过]


高速缓存默认是64 个字节,
为什么在disruptor源码里面的sequence 里面有

private volatile long p1 = 7L, p2 = 7L, p3 = 7L, p4 = 7L, p5 = 7L, p6 = 7L, p7 = 7L,
value = Sequencer.INITIAL_CURSOR_VALUE,
q1 = 7L, q2 = 7L, q3 = 7L, q4 = 7L, q5 = 7L, q6 = 7L, q7 = 7L;
这么多的填充值呢

不是只要8个long值就可以填满吗?

同楼上的问题一样,这个填充是如何做到的,个人感觉是填充sequence,也就是下标,但是为什么在源码中会有p1---p7和q1到q7呢?

这个问题怎么才能观察到呢?我的测试观察到就是共享的,两个进程访问两个变量和一个进程访问一个变量效率没区别
我的CPU i7 四核

加上volatile 可以观察到,另“第一部分是由24字节的hash code和8字节的锁等状态标识组成”写错了吧?

这个不只是java和C有这个问题吧 这种填充的方法的确不用做一致性控制了 但是空间浪费不少哦

自己测试了一下,的确如此:
volatile long:
duration = 58,185660258
duration = 56,183948170

volatile long with pad:
duration = 5,962906668
duration = 6,307130864

另外,使用volatile进行修饰,是不是为了放大效果:
These counters will be volatile longs so the world can see their progress.

还有一个疑问:false sharing应该在很多情况下都会出现,如何权衡?如果追究这点,很多代码都需要pad。


请banq老大帮忙看下

2012-10-26 12:10 "@michaelchan0921"的内容
volatile进行修饰,是不是为了放大效果 ...

是为了线程们彼此看见对方的修改后的值,这涉及线程工作原理,线程将主内存变量拷贝到自己工作内存中进行修改,各自修改后就彼此看不见对方的值了,用volatile是为让彼此看见。

pad问题应该都存在,是软硬结合的缝隙吧.

原文中的false sharing的测试用例,实际上并不涉及到volatile,多个线程之间的值本来就是独立的。

即使去掉volatile,应该也能得出一样的结果,只是效果不是那么的明显。

但是这点,我还不确定,只能看作者写的,因为cache lines涉及到硬件原理,不懂 :(
[该贴被michaelchan0921于2012-10-27 10:00修改过]

Sequence类为啥不用7个元素的long数组呢,每次访问第7个元素呢?这样岂不是更省内存,也能做到高速缓存内只占一行

@banq
banq,你好,不知道是否翻译有误还是我理解错误,关于 hotspot jvm的对象头的组成中,hash code应该不是24字节
从 [url= http://www.ibm.com/developerworks/java/library/j-codetoheap/index.html] 文章中我获取的信息是对象头包括
固定的 8个字节:class指针和flag的一个集合(这个flag集合里包括了hashcode)
如果看到的话请回答一下,谢谢