简短答案:
是的,允许这种优化。折叠两个顺序的读取 *** 作将产生可观察到的 原子 序列行为,但不会表现为 *** 作的 重新排序
。在单个执行线程上执行的任何动作序列都可以作为原子单元执行。通常,很难确保一系列 *** 作以原子方式执行,并且很少会导致性能提高,因为大多数执行环境都会
引入开销 以原子方式执行项目。
在原始问题给出的示例中,所讨论的 *** 作序列如下:
read(a)read(a)
原子地执行这些 *** 作可确保第一行读取的值等于第二行读取的值。此外,这意味着在第二行读取的值是
a在执行第一次读取时包含的值(反之亦然,因为根据程序的可观察执行状态,两个原子读取 *** 作都同时发生)。所讨论的优化将第一读取的值重用于第二读取,该优化等效于编译器和/或JIT以原子方式执行该序列,因此是有效的。
原来较长的答案:
Java内存模型使用 在 部分排序 之前发生的事件来
描述 *** 作。为了表达的限制,即第一次读
r1和二读
r2的
a,不能折叠,则需要证明一些 *** 作语义需要在它们之间出现。
使用
r1和进行的线程 *** 作
r2如下:
--> r(a) --> r(a) --> add -->
为了表达的需要的东西(比如说
y之间)的谎言
r1和
r2,你需要要求
r1的之前发生
y和
y之前发生
r2。碰巧的是,没有规则在“ 先发生后发生” 关系的左侧出现读取 *** 作。您可以得到的最接近的说法是:
ybefore-before
r2,但是部分顺序也可以
y发生在before之前
r1,因此会破坏读取 *** 作。
如果不存在 要求 运算符介于
r1和之间的方案
r2,那么您可以声明 没有运算符
出现在之间
r1,
r2并且不违反语言的必需语义。使用单个读取 *** 作将等同于此声明。
编辑 我的答案被否决了,所以我将进一步探讨其他细节。
以下是一些相关问题:
- 是Java编译器或JVM 需要 折叠这些读取 *** 作?
否。在add表达式中使用的表达式
a和
a表达式不是常数表达式,因此不需要将它们折叠。
- __JVM 是否 折叠这些读取 *** 作?
为此,我不确定答案。通过编译程序并使用
javap-c,很容易看出Java编译器不会折叠这些读取 *** 作。不幸的是,要证明JVM不会折叠 *** 作(甚至更难于处理器本身)并不容易。
- __JVM 是否应该 折叠这些读取 *** 作?
可能不是。每次优化都需要花费时间才能执行,因此在分析代码所花费的时间与您期望获得的收益之间是一个平衡。事实证明,某些优化(例如数组边界检查消除或检查空引用)对现实应用程序具有
广泛的 好处。该特定优化可能会提高性能的唯一情况是两个相同的读取 *** 作顺序出现的情况。
此外,如对此答案的响应以及其他答案所示,此特定更改将导致用户可能不希望的某些应用程序发生 意外的 行为更改。
编辑2:
关于拉斐尔(Rafael)的描述,它声称两个读取 *** 作无法重新排序。该语句旨在突出显示以下事实,即
a按以下顺序缓存的读取 *** 作可能会产生错误的结果:
a1 = read(a)b1 = read(b)a2 = read(a)result = op(a1, b1, a2)
假设最初
a并
b有其默认值0。然后你执行只是第一
read(a)。
现在,假设另一个线程执行以下序列:
a = 1b = 1
最后,假设第一个线程执行line
read(b)。如果要缓存的原始读取值
a,将以以下调用结束:
op(0, 1, 0)
这是不正确的。由于更新后的值
a是在写入之前存储的
b,因此无法读取该值
b1 = 1,然后再 读取该值
a2 =0。如果不进行缓存,则正确的事件顺序将导致随后的调用。
op(0, 1, 1)
但是,如果您要问“是否有任何方法
a可以缓存读取的内容?”,答案是肯定的。如果您可以作为 原子单位 在第一个线程序列中执行 所有三个
读取 *** 作,则可以缓存该值。虽然很难跨多个变量进行同步,并且很少能提供机会优化优势,但是可以肯定遇到异常。例如,假设和分别为4个字节,并且它们在内存中顺序出现,并在8字节边界上对齐。64位进程可以将序列实现为原子64位加载 *** 作,这将允许
__
a``b``a``read(a) read(b)``a被缓存(有效地将所有三个读取 *** 作视为一个原子 *** 作,而不仅仅是前两个)。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)