伪共享(False Sharing)
来源:互联网 发布:淘宝模特图片 编辑:程序博客网 时间:2024/05/21 21:49
转自:http://ifeve.com/falsesharing/
原文地址:http://ifeve.com/false-sharing/
作者:Martin Thompson 译者:丁一
缓存系统中是以缓存行(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字节以提高性能。因此当封装对象的时候为了高效率,对象字段声明的顺序会被重排序成下列基于字节大小的顺序:
- doubles (8) 和 longs (8)
- ints (4) 和 floats (4)
- shorts (2) 和 chars (2)
- booleans (1) 和 bytes (1)
- references (4/8)
- <子类字段重复上述顺序>
(译注:更多HotSpot虚拟机对象结构相关内容:http://www.infoq.com/cn/articles/jvm-hotspot)
了解这些之后就可以在任意字段间用7个long来填充缓存行。在Disruptor里我们对RingBuffer的cursor和BatchEventProcessor的序列进行了缓存行填充。
为了展示其性能影响,我们启动几个线程,每个都更新它自己独立的计数器。计数器是volatile long类型的,所以其它线程能看到它们的进展。
01
public
final
class
FalseSharing
02
implements
Runnable
03
{
04
public
final
static
int
NUM_THREADS =
4
;
// change
05
public
final
static
long
ITERATIONS = 500L * 1000L * 1000L;
06
private
final
int
arrayIndex;
07
08
private
static
VolatileLong[] longs =
new
VolatileLong[NUM_THREADS];
09
static
10
{
11
for
(
int
i =
0
; i < longs.length; i++)
12
{
13
longs[i] =
new
VolatileLong();
14
}
15
}
16
17
public
FalseSharing(
final
int
arrayIndex)
18
{
19
this
.arrayIndex = arrayIndex;
20
}
21
22
public
static
void
main(
final
String[] args)
throws
Exception
23
{
24
final
long
start = System.nanoTime();
25
runTest();
26
System.out.println(
"duration = "
+ (System.nanoTime() - start));
27
}
28
29
private
static
void
runTest()
throws
InterruptedException
30
{
31
Thread[] threads =
new
Thread[NUM_THREADS];
32
33
for
(
int
i =
0
; i < threads.length; i++)
34
{
35
threads[i] =
new
Thread(
new
FalseSharing(i));
36
}
37
38
for
(Thread t : threads)
39
{
40
t.start();
41
}
42
43
for
(Thread t : threads)
44
{
45
t.join();
46
}
47
}
48
49
public
void
run()
50
{
51
long
i = ITERATIONS +
1
;
52
while
(
0
!= --i)
53
{
54
longs[arrayIndex].value = i;
55
}
56
}
57
58
public
final
static
class
VolatileLong
59
{
60
public
volatile
long
value = 0L;
61
public
long
p1, p2, p3, p4, p5, p6;
// comment out
62
}
63
}
结果(Results)
运行上面的代码,增加线程数以及添加/移除缓存行的填充,下面的图2描述了我得到的结果。这是在我4核Nehalem上测得的运行时间。
图 2.从不断上升的测试所需时间中能够明显看出伪共享的影响。没有缓存行竞争时,我们几近达到了随着线程数的线性扩展。
这并不是个完美的测试,因为我们不能确定这些VolatileLong会布局在内存的什么位置。它们是独立的对象。但是经验告诉我们同一时间分配的对象趋向集中于一块。
所以你也看到了,伪共享可能是无声的性能杀手。
注意:更多伪共享相关的内容,请阅读我后续blog。
原创文章,转载请注明: 转载自并发编程网 – ifeve.com
- 伪共享false sharing
- 伪共享(False Sharing)
- 伪共享(False Sharing)
- 伪共享(False Sharing)
- 伪共享(False Sharing)
- 伪共享(False Sharing)
- 伪共享(False Sharing)
- 内存伪共享(False Sharing)
- 多线程伪共享(false sharing)问题分析
- 多线程伪共享(false sharing)问题分析
- Java 伪共享(False Sharing)
- 多线程伪共享(false sharing)问题分析
- 【并发】伪共享 —— False Sharing
- 从Java视角理解伪共享(False Sharing)
- Java8中用sun.misc.Contended避免伪共享(false sharing)
- 从Java视角理解伪共享(False Sharing)
- java 伪共享(false sharing)解决思路
- 从Java视角理解伪共享(False Sharing)
- HDU_1051_WoodenSticks
- OpenGL关于glEnable(GL_LINE_STIPPLE) glLineStipple() glLineWidth() glDisable()的实例
- Unix系统中的系统调用和库调用
- H264/AVC Profile and Level 简介
- 【HDU 2586】LCA模板
- 伪共享(False Sharing)
- HDU 2031 进制转化
- WEB 端批量移动设备管理控制工具 STF 的环境搭建和运行
- SAP之成本中心类型与功能范围
- CC2530定时器3通道1输入捕获中断
- Chrome浏览器V43版本不支持silverlight 5.0的解决办法
- Java排序算法实现
- spring mvc 上传文件
- 代码如人