2014-02-27 68 views
3

在作为JSR166的一部分引入的类中,作者使用所谓的填充来填充Striped64.Cell类的单个值字段。为什么jsr166e.Striped64.Cell类中值域的额外填充值?

这里是一个类的摘录:

/** 
* Padded variant of AtomicLong supporting only raw accesses plus CAS. The value field is placed 
* between pads, hoping that the JVM doesn't reorder them. 
* <p/> 
* JVM intrinsics note: It would be possible to use a release-only form of CAS here, if it were 
* provided. 
*/ 
static final class Cell { 
    volatile long p0, p1, p2, p3, p4, p5, p6; 
    volatile long value; 
    volatile long q0, q1, q2, q3, q4, q5, q6; 

    ... 

笔者然后使用CAS原子地修改值。

在Striped64类中,作者还使用不安全来访问两个其他字段,但不应用任何此类填充。

我的问题是:为什么需要做这样的事情,引入14个冗余字段填充单个值字段?

回答

4

填充是为了防止共享value字段的高速缓存行 - 否则,可能必须从内存中重新获取该值,因为高速缓存行上的其他内容需要使整行无效。所以目标是提高性能。为了使事情变得简单,Java 8引入了@Contended annotation,它在底层执行相同的操作,不同之处在于它由JVM本身处理。

+0

感谢您的回答。我认为这是正确的,但为什么额外的q0 ... q6作为p0..p6 +值的总体大小已经是64字节,这在我的理解中完全适合单个缓存行?是否容纳实现更长缓存行的其他体系结构? – silkentrance

+2

@silkentrance它将使用2个缓存行。如果你只有p0,p1,p6的值,那么你最终可能会在一个高速缓存行上使用p0-p6,其他数据和另一行上的值以及其他数据。 – assylias

4

尽管我同意assylias的回答,但我认为这需要一些解释。

为什么缓存缺失重要?

因为从主内存中读取比缓存中的要慢得多。如果你有一个需要经常使用的变量,让它缓存很重要。此外,如果此变量与其他变量共享相同的缓存,则可能会导致整个缓存行失效。

考虑变量1与变量2位于同一个缓存时的例子。变量1由thread1使用,变量2由thread2使用。因为他们生活在同一个缓存行中,所以如果有更新的变量2,并且thread1需要使用变量1,则需要删除缓存行(即使它不使用此变量,也需要删除)并从主内存中读取。这被称为虚假分享

为什么实际上有7个多长?

如果只有JVM没有决定重新对内存进行重新排序,那么从哪里开始读取这个变量并不重要(可以从第3行读取它,在8个“缓存行“) - 你仍然会在缓存行中得到一个单一的值。所以,无论你开始阅读,只有一个对你很重要的值将在缓存行中,因此不可能存在“错误共享”的“缓存未命中”。

P.S.这就是为什么Java对象的大小可以被8整除。