原文地址:http://ifeve.com/false-sharing/
缓存系统中是以缓存行(cache line)为单位存储的。缓存行是2的整数幂个连续字节,一般为32-256个字节。最常见的缓存行大小是64个字节。当多线程修改互相独立的变量时,如果这些变量共享同一个缓存行,就会无意中影响彼此的性能,这就是伪共享。缓存行上的写竞争是运行在SMP系统中并行线程实现可伸缩性最重要的限制因素。有人将伪共享描述成无声的性能杀手,因为从代码中很难看清楚是否会出现伪共享。
为了让可伸缩性与线程数呈线性关系,就必须确保不会有两个线程往同一个变量或缓存行中写。两个线程写同一个变量可以在代码中发现。为了确定互相独立的变量是否共享了同一个缓存行,就需要了解内存布局,或找个工具告诉我们。Intel VTune就是这样一个分析工具。本文中我将解释Java对象的内存布局以及我们该如何填充缓存行以避免伪共享。
|
图 1. |
图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
|
04 |
public final static int NUM_THREADS = 4 ;
|
05 |
public final static long ITERATIONS = 500L * 1000L * 1000L;
|
06 |
private final int arrayIndex;
|
08 |
private static VolatileLong[] longs = new VolatileLong[NUM_THREADS];
|
11 |
for ( int i = 0 ; i < longs.length; i++)
|
13 |
longs[i] = new VolatileLong();
|
17 |
public FalseSharing( final int arrayIndex)
|
19 |
this .arrayIndex = arrayIndex;
|
22 |
public static void main( final String[] args) throws Exception
|
24 |
final long start = System.nanoTime();
|
26 |
System.out.println( "duration = " + (System.nanoTime() - start));
|
29 |
private static void runTest() throws InterruptedException
|
31 |
Thread[] threads = new Thread[NUM_THREADS];
|
33 |
for ( int i = 0 ; i < threads.length; i++)
|
35 |
threads[i] = new Thread( new FalseSharing(i));
|
38 |
for (Thread t : threads)
|
43 |
for (Thread t : threads)
|
51 |
long i = ITERATIONS + 1 ;
|
54 |
longs[arrayIndex].value = i;
|
58 |
public final static class VolatileLong
|
60 |
public volatile long value = 0L;
|
61 |
public long p1, p2, p3, p4, p5, p6;
|
结果(Results)
运行上面的代码,增加线程数以及添加/移除缓存行的填充,下面的图2描述了我得到的结果。这是在我4核Nehalem上测得的运行时间。
|
图 2. |
从不断上升的测试所需时间中能够明显看出伪共享的影响。没有缓存行竞争时,我们几近达到了随着线程数的线性扩展。
这并不是个完美的测试,因为我们不能确定这些VolatileLong会布局在内存的什么位置。它们是独立的对象。但是经验告诉我们同一时间分配的对象趋向集中于一块。
所以你也看到了,伪共享可能是无声的性能杀手。
分享到:
相关推荐
1. **减少写入争用**:通过避免在生产者和消费者之间共享状态,Disruptor消除了传统队列中的竞争条件,从而降低了延迟。 2. **优化的缓存使用**:Disruptor的设计使得数据在处理器之间移动时能更好地利用缓存,减少...
总的来说,Disruptor是一种革命性的并发解决方案,它挑战了传统的并发编程思维,为高性能系统设计开辟了新的路径。无论是金融交易、大数据处理还是实时计算,Disruptor都提供了优化并发性能的强大工具,有望在未来的...
深入研究和对计算机科学的理解,特别是考虑到现代CPU的工作原理——我们称之为“机械同情”(mechanical sympathy),即通过良好的设计原则来区分不同的关注点,开发团队提出了名为Disruptor的数据结构和使用模式。...
2017 Conversant Disruptor - 仍然是世界上最快的入门运行 maven build 来构建和使用包。 $ mvn -U clean package Conversant Disruptor 在 Maven Central 上对于 Java 9 及更高版本: <dependency> <groupId>...
赠送jar包:disruptor-3.3.0.jar; 赠送原API文档:disruptor-3.3.0-javadoc.jar; 赠送源代码:disruptor-3.3.0-sources.jar; 赠送Maven依赖信息文件:disruptor-3.3.0.pom; 包含翻译后的API文档:disruptor-...
赠送jar包:disruptor-3.3.0.jar; 赠送原API文档:disruptor-3.3.0-javadoc.jar; 赠送源代码:disruptor-3.3.0-sources.jar; 赠送Maven依赖信息文件:disruptor-3.3.0.pom; 包含翻译后的API文档:disruptor-...
为了避免多线程之间的伪共享(False Sharing),Disruptor采用了缓存行填充技术。这意味着在Ring Buffer的每个元素之间填充额外的未使用的字节,以确保每个元素位于不同的缓存行上。这样,即使多个线程同时访问不同...
Disruptor的设计目标是解决多线程环境下的数据共享问题,通过优化并发性能,实现极低的延迟和高吞吐量。在Java社区中,Disruptor因其高效的数据交换机制而备受赞誉。 1. **Disruptor的核心概念** - **Ring Buffer*...
《tiny_disruptor:简化的Disruptor实践》 Disruptor是由LMAX公司开发的一款高性能、低延迟的并发框架,最初是用Java语言编写的。这个框架在处理高并发场景下,通过优化数据同步方式,实现了惊人的性能提升。而...
《Spring Boot Starter Disruptor深度解析》 在现代软件开发中,高性能和低延迟往往是系统设计的关键要素。Spring Boot作为Java领域最受欢迎的微服务框架,提供了丰富的启动器(starters)来简化开发工作。"spring-...
在使用Disruptor过程中,开发者可能会遇到`FatalExceptionHandler`的错误,这通常是由于处理流程中的异常没有被正确处理,导致Disruptor内部的默认异常处理器介入。下面将详细解析这个问题,并提供解决方案。 首先...
Disruptor,由LMAX公司开源的一款并发框架,为处理高并发场景提供了一种新颖且高效的解决方案。它通过消除锁和线程间通信的开销,实现了微秒级的延迟和极高的吞吐量,尤其适用于金融交易、实时分析等对性能有苛刻...
Disruptor的核心思想是通过消除线程间的共享数据和锁竞争,以提高多线程环境下的并发性能。它采用环形缓冲区(Ring Buffer)作为主要的数据结构,结合生产者-消费者模型,实现了高效的事件处理机制。这种设计模式...
赠送jar包:disruptor-3.3.7.jar; 赠送原API文档:disruptor-3.3.7-javadoc.jar; 赠送源代码:disruptor-3.3.7-sources.jar; 赠送Maven依赖信息文件:disruptor-3.3.7.pom; 包含翻译后的API文档:disruptor-...
Disruptor的设计理念是避免传统的锁机制,转而采用一种称为“环形缓冲区”的数据结构,以及基于事件的处理模型,从而实现低延迟、高吞吐量的并发编程。 在"Disruptor demo"中,我们可以看到如何使用Disruptor来实现...
赠送jar包:disruptor-3.3.7.jar 赠送原API文档:disruptor-3.3.7-javadoc.jar 赠送源代码:disruptor-3.3.7-sources.jar 包含翻译后的API文档:disruptor-3.3.7-javadoc-API文档-中文(简体)-英语-对照版.zip ...
"DisruptorDemo.zip"的实例代码为我们提供了学习和理解Disruptor的一个良好起点,通过实际操作,我们可以更直观地感受Disruptor的强大性能。在实际项目中,尤其是对于需要处理大量并发请求的系统,Disruptor是一个...
《LMAX Disruptor的C++实现解析》 LMAX Disruptor是由LMAX公司开发的一款高性能、低延迟的消息传递框架,最初是用Java编写的。它的核心设计理念是通过消除传统锁机制,采用无锁数据结构和单向事件传递路径,以达到...
4. **工作窃取算法**:在Disruptor中,如果一个消费者处理得更快,它可以“窃取”其他消费者未处理的事件,前提是这些事件尚未被其他消费者处理。这种设计提高了处理器利用率,防止了资源浪费。 5. **中断与唤醒...