gpt4 book ai didi

java - volatile 贵吗?

转载 作者:IT老高 更新时间:2023-10-28 11:23:52 26 4
gpt4 key购买 nike

看完The JSR-133 Cookbook for Compiler Writers关于 volatile 的实现,尤其是“与原子指令的交互”部分,我假设读取 volatile 变量而不更新它需要 LoadLoad 或 LoadStore 屏障。在页面的下方,我看到 LoadLoad 和 LoadStore 在 X86 CPU 上实际上是无操作的。这是否意味着 volatile 读取操作可以在 x86 上没有显式缓存失效的情况下完成,并且与普通变量读取一样快(忽略 volatile 的重新排序约束)?

我相信我没有正确理解这一点。有人可以开导我吗?

编辑:我想知道多处理器环境是否存在差异。正如 John V. 所说,在单 CPU 系统上,CPU 可能会查看它自己的线程缓存,但在多 CPU 系统上,CPU 必须有一些配置选项,这还不够,并且必须命中主内存,从而使 volatile 变慢在多 CPU 系统上,对吧?

PS:在了解更多信息的过程中,我偶然发现了以下精彩文章,由于其他人可能会对这个问题感兴趣,因此我将在此处分享我的链接:

最佳答案

在 Intel 上,非竞争性 volatile 读取非常便宜。如果我们考虑以下简单的情况:

public static long l;

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

if (l == -2)
System.exit(-1);
}

使用 Java 7 打印汇编代码的能力,run 方法看起来像:

# {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 的 2 个引用,第一个涉及从内存加载,第二个跳过加载,因为该值从已加载到的寄存器中重用(long 是 64 位,在我的32 位笔记本电脑,它使用 2 个寄存器)。

如果我们将 l 变量设为 volatile,则生成的程序集会有所不同。

# {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

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

因此, volatile 读取的总成本大致相当于内存负载,并且可以与 L1 缓存访问一样便宜。但是,如果另一个内核正在写入 volatile 变量,则缓存行将失效,需要主存储器或 L3 缓存访问。实际成本将在很大程度上取决于 CPU 架构。即使在 Intel 和 AMD 之间,缓存一致性协议(protocol)也不同。

关于java - volatile 贵吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/4633866/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com