是不稳定的昂贵?

阅读JSR-133编译器编写者手册,了解易失性的实现,特别是“与primefaces指令的交互”部分。我假设读取一个易失性variables而不更新它需要一个LoadLoad或一个LoadStore的障碍。 再往下看,LoadLoad和LoadStore在X86 CPU上是无效的。 这是否意味着可以在x86上执行易失性读操作而不显示caching失效,并且像正常variables读取一样快(不考虑volatile的重sorting约束)?

我相信我不明白这一点。 有人能照顾我吗?

编辑:我不知道在多处理器环境中是否有差异。 在单CPU系统上,CPU可能会看它自己的线程caching,正如John V.指出的那样,但是在多CPU系统中,CPU必须有一些configuration选项,这是不够的,主存必须被命中,使得volatile变慢在多CP​​U系统上,对吗?

PS:在我学习更多关于这个的方法中,我偶然发现了以下很棒的文章,因为这个问题可能对别人很有趣,所以我会在这里分享我的链接:

  • Java理论与实践:修复Java存储模型,第1部分和
  • Java理论与实践:修复Java内存模型,第2部分

在英特尔,一个非争用的易失性读取相当便宜。 如果我们考虑以下简单情况:

public static long l; public static void run() { if (l == -1) System.exit(-1); if (l == -2) System.exit(-1); } 

使用Java 7的能力来打印汇编代码的运行方法看起来像这样:

 # {method} 'run2' '()V' in 'Test2' # [sp+0x10] (sp of caller) 0xb396ce80: mov %eax,-0x3000(%esp) 0xb396ce87: push %ebp 0xb396ce88: sub $0x8,%esp ;*synchronization entry ; - Test2::run2@-1 (line 33) 0xb396ce8e: mov $0xffffffff,%ecx 0xb396ce93: mov $0xffffffff,%ebx 0xb396ce98: mov $0x6fa2b2f0,%esi ; {oop('Test2')} 0xb396ce9d: mov 0x150(%esi),%ebp 0xb396cea3: mov 0x154(%esi),%edi ;*getstatic l ; - Test2::run@0 (line 33) 0xb396cea9: cmp %ecx,%ebp 0xb396ceab: jne 0xb396ceaf 0xb396cead: cmp %ebx,%edi 0xb396ceaf: je 0xb396cece ;*getstatic l ; - Test2::run@14 (line 37) 0xb396ceb1: mov $0xfffffffe,%ecx 0xb396ceb6: mov $0xffffffff,%ebx 0xb396cebb: cmp %ecx,%ebp 0xb396cebd: jne 0xb396cec1 0xb396cebf: cmp %ebx,%edi 0xb396cec1: je 0xb396ceeb ;*return ; - Test2::run@28 (line 40) 0xb396cec3: add $0x8,%esp 0xb396cec6: pop %ebp 0xb396cec7: test %eax,0xb7732000 ; {poll_return} ;... lines removed 

如果你看到两个引用getstatic,第一个涉及从内存加载,第二个跳过的负载,因为它的价值是从已经加载的寄存器(s)重用(长是64位,并在我的32位笔记本电脑它使用2个寄存器)。

如果我们使得lvariables易变,则生成的程序集是不同的。

 # {method} 'run2' '()V' in 'Test2' # [sp+0x10] (sp of caller) 0xb3ab9340: mov %eax,-0x3000(%esp) 0xb3ab9347: push %ebp 0xb3ab9348: sub $0x8,%esp ;*synchronization entry ; - Test2::run2@-1 (line 32) 0xb3ab934e: mov $0xffffffff,%ecx 0xb3ab9353: mov $0xffffffff,%ebx 0xb3ab9358: mov $0x150,%ebp 0xb3ab935d: movsd 0x6fb7b2f0(%ebp),%xmm0 ; {oop('Test2')} 0xb3ab9365: movd %xmm0,%eax 0xb3ab9369: psrlq $0x20,%xmm0 0xb3ab936e: movd %xmm0,%edx ;*getstatic l ; - Test2::run@0 (line 32) 0xb3ab9372: cmp %ecx,%eax 0xb3ab9374: jne 0xb3ab9378 0xb3ab9376: cmp %ebx,%edx 0xb3ab9378: je 0xb3ab93ac 0xb3ab937a: mov $0xfffffffe,%ecx 0xb3ab937f: mov $0xffffffff,%ebx 0xb3ab9384: movsd 0x6fb7b2f0(%ebp),%xmm0 ; {oop('Test2')} 0xb3ab938c: movd %xmm0,%ebp 0xb3ab9390: psrlq $0x20,%xmm0 0xb3ab9395: movd %xmm0,%edi ;*getstatic l ; - Test2::run@14 (line 36) 0xb3ab9399: cmp %ecx,%ebp 0xb3ab939b: jne 0xb3ab939f 0xb3ab939d: cmp %ebx,%edi 0xb3ab939f: je 0xb3ab93ba ;*return ;... lines removed 

在这种情况下,对variablesl的getstatic引用都涉及来自内存的加载,即该值不能跨多个volatile读取保存在寄存器中。 为了确保有一个primefaces读取,将数据从主存储器读取到MMX寄存器中movsd 0x6fb7b2f0(%ebp),%xmm0使读取操作成为单一指令(从前面的例子中我们看到64位值通常需要两个32位读取一个32位系统)。

因此,易失性读取的总体成本将大致相当于内存负载,并且可以像L1caching访问一样便宜。 但是,如果另一个内核正在写入易失性variables,那么caching行将会失效,需要主内存或L3caching访问。 实际成本在很大程度上取决于CPU架构。 即使在Intel和AMD之间,caching一致性协议也是不同的。

一般而言,在大多数现代处理器上,易失性负载与正常负载相当。 一个不稳定的商店是一个单独进入/监视器出口的1/3的时间。 这是在caching一致的系统上看到的。

为了回答OP的问题,易失性写入是昂贵的,而读取通常不是。

这是否意味着在x86上没有明确的高速caching失效可以完成易失性读操作,并且是一个快速的正常variables读取(不考虑volatile的重新sorting约束)?

是的,有时在validation某个字段时,CPU甚至可能不会触发主内存,而是监视其他线程caching并从中获取值(非常一般的解释)。

不过,我第二次尼尔的build议,如果你有一个字段访问多个线程,你要把它包装成一个AtomicReference。 作为AtomicReference,它执行的读/写吞吐量大致相同,但更明显的是该字段将被多个线程访问和修改。

编辑回答OP的编辑:

高速caching一致性是一个复杂的协议,但简而言之:CPU将共享一个连接到主内存的公共高速caching行。 如果一个CPU加载内存,并且没有其他的CPU,那么CPU将认为它是最新的值。 如果另一个CPU尝试加载相同的内存位置,则已加载的CPU将会知道这一点,并将caching的引用实际上共享给请求的CPU – 现在请求CPU在其CPUcaching中有该内存的一个副本。 (它从来不需要在主内存中查找参考)

涉及的协议相当多,但是这给出了正在发生的事情。 同样为了回答你的另一个问题,在没有多个处理器的情况下,使用多个处理器时,volatile读/写实际上可以更快。 有一些应用程序实际上可以同时运行更快的单个CPU和多个应用程序。

用Java内存模型(在JSR 133中定义为Java 5+)的话来说,对volatilevariables的任何操作(读或写)都会在同一个variables上创build一个before-before关系。 这意味着编译器和JIT被迫避免某些优化,例如在线程中对指令进行重新sorting,或仅在本地高速caching中执行操作。

由于一些优化是不可用的,所得到的代码必然要慢,尽pipe可能不是很多。

尽pipe如此,除非您知道它将从synchronized块之外的多个线程访问,否则不应该使variablesvolatile 。 即使这样,你应该考虑volatile是否是synchronizedAtomicReference及其朋友,显式Lock类等的最佳select。

访问一个volatilevariables在许多方面类似于在一个synchronized块中包装对普通variables的访问。 例如,访问volatilevariables可以防止CPU在访问之前和之后重新sorting指令,这通常会减慢执行速度(尽pipe我不能说多less)。

更一般地说,在多处理器系统上,我看不出如何访问volatilevariables而不会受到惩罚 – 必须有某种方法来确保处理器A上的写入将与处理器B上的读取同步。