【发布时间】:2015-03-02 14:24:09
【问题描述】:
我编写了这个简单的 Java 程序:
package com.salil.threads;
public class IncrementClass {
static volatile int j = 0;
static int i = 0;
public static void main(String args[]) {
for(int a=0;a<1000000;a++);
i++;
j++;
}
}
这将为 i++ 和 j++ 生成以下反汇编代码(删除剩余的反汇编代码):
0x0000000002961a6c: 49ba98e8d0d507000000 mov r10,7d5d0e898h
; {oop(a 'java/lang/Class' = 'com/salil/threads/IncrementClass')}
0x0000000002961a76: 41ff4274 inc dword ptr [r10+74h]
;*if_icmpge
; - com.salil.threads.IncrementClass::main@5 (line 10)
0x0000000002961a7a: 458b5a70 mov r11d,dword ptr [r10+70h]
0x0000000002961a7e: 41ffc3 inc r11d
0x0000000002961a81: 45895a70 mov dword ptr [r10+70h],r11d
0x0000000002961a85: f083042400 lock add dword ptr [rsp],0h
;*putstatic j
; - com.salil.threads.IncrementClass::main@27 (line 14)
这是我对以下汇编代码的理解:
- mov r10,7d5d0e898h : 将指向 IncrementClass.class 的指针移动到寄存器 r10
- inc dword ptr [r10+74h] : 递增地址 [r10 + 74h],(i.e. i) 处的 4 字节值
- mov r11d,dword ptr [r10+70h] :将地址[r10 + 70h]处的4个值的值移动到寄存器r11d(即把j的值移动到r11d)
- inc r11d : 增加 r11d
- mov dword ptr [r10+70h],r11d : 将 r11d 的值写入 [r10 + 70h] 使其对其他线程可见 -lock add dword ptr [rsp],0h : 锁定栈指针rsp表示的内存地址,并加0。
JMM 指出,在每次 volatile 读取之前必须有一个加载内存屏障,并且在每次 volatile 写入之后必须有一个存储屏障。我的问题是:
- 为什么在将 j 读入 r11d 之前没有负载屏障?
- 锁和添加到 rsp 如何确保将 r11d 中 j 的值传播回主内存。我从 intel 规范中读到的只是 lock 在操作期间为 cpu 提供了对指定内存地址的独占锁。
【问题讨论】:
-
该代码非常糟糕。
lock inc dword [r10+70h]会做 load/inc/store/full-barrier 所做的一切,甚至更多(即实际上是原子的)。它至少会一样快,而且代码字节数会少得多。lock add [rsp], 0是一个完整的障碍,因为每个locked 指令都是。关于 MFENCE 或其他无操作锁定 insn 到堆栈内存(在 L1 中应该已经处于 E 状态)是否更好存在争议。 MFENCE 的吞吐量更差,但微指令更少,因此当 MFENCE 链不是您正在做的全部时,对周围指令的影响可能更小。 -
mov r10, imm64也很可疑。这在循环里面???这是来自 JIT 的 优化 代码吗?inc r11d是循环计数器,还是至少保存在寄存器中? -
@SalilSurendran 我知道这是旧的,但语句不应该是在每次易失性读取之后必须有一个负载内存屏障吗?阅读之后,而不是之前
标签: java multithreading assembly intel