【发布时间】:2021-10-01 05:55:08
【问题描述】:
假设 Java JIT 编译代码,例如构建一个链表,
new Link(及其构造函数)是否有可能在完全不访问 RAM 的情况下返回?换句话说,VM 至少在理论上是否可以只在处理器缓存中执行分配,而在刷新时只执行实际的内存分配(例如,一次性分配整个列表或列表段)?
除了它本身很有趣之外,当我考虑在与新对象创建配对时使用 volatile 关键字的实际相对惩罚时,这个问题就出现了。假设一个可变列表定义如下:
class Link<E> {
final E elem;
Link<E> next = null
Link(E e, Link<E> tail) {
elem = e;
next = tail;
}
public void append(E e) {
next = new Link(e, null);
}
}
可以将volatile 关键字添加到字段next 影响重复调用append 的性能(可能不久之后取消引用整个集合,释放内存用于垃圾收集),限制JVM 能够进行的优化以重要的方式处理代码(至少在理论上)?
【问题讨论】:
-
“仅在处理器缓存中执行分配” - 不,我不这么认为。我对你关于
volatile的观点有点困惑——如果你需要正确的语义,你需要使用它,与正确性相比,在这种情况下性能无关紧要。 -
如果有足够的 CPU 缓存,操作可以完全在 CPU 缓存中完成,因为它们在所有现代处理器上都是一致的。 volatile 的成本不是因为刷新到内存(这不会发生)。代价是减少编译器优化和执行防止 CPU 内重新排序的栅栏。
-
例如;如果 volatile 存储之后是对不同地址的 volatile 加载,则需要按顺序执行它们,因为这通常是 CPU 想要优化的东西(查找存储缓冲区)在某些平台 (ARM/X86) 上,它会导致从缓存加载到存储提交到缓存的延迟。这通常是使 volatile 变得昂贵的原因。
-
现代处理器都是加载/存储架构。这意味着像 ALU 操作这样的大多数操作都不能直接访问内存,并且总是需要通过寄存器。即使是内存/寄存器架构的 X86,一旦转换为 uops 也是加载/存储架构。所以 volatile 不能阻止使用寄存器;关键区别在于 volatile 控制它将在寄存器中保留多长时间,或者是否需要将其写入缓存。这是编译器的问题。
-
如果高速缓存行在进行访问的 CPU 上处于正确状态,则访问高速缓存非常便宜。根本不需要访问主存储器。要付出的主要代价是限制 CPU 或编译器中的重新排序。而且当然;如果缓存行在缓存中没有处于正确的状态,那么价格就会很高。