当前位置: 首页 > 面试题库 >

JIT是否可以在某些表达式中将两个易失性读取折叠为一个?

胡意致
2023-03-14
问题内容

假设我们有一个volatile int a。一线程做

while (true) {
    a = 1;
    a = 0;
}

而另一个线程

while (true) {
    System.out.println(a+a);
}

现在,JIT编译器发出与而不是相对应的程序集是否 非法2*a``a+a

一方面,易失性读取的主要目的是应该始终从内存中读取数据。

另一方面,两次读取之间没有同步点,因此我看不到a+a原子地进行处理是非法的,在这种情况下,我看不到诸如此类的优化2*a将如何破坏规格。

对JLS的引用将不胜感激。


问题答案:

简短答案:

是的,允许这种优化。折叠两个顺序的读取操作将产生可观察到的 原子 序列行为,但不会表现为操作的 重新排序
。在单个执行线程上执行的任何动作序列都可以作为原子单元执行。通常,很难确保一系列操作以原子方式执行,并且很少会导致性能提高,因为大多数执行环境都会
引入开销 以原子方式执行项目。

在原始问题给出的示例中,所讨论的操作序列如下:

read(a)
read(a)

原子地执行这些操作可确保第一行读取的值等于第二行读取的值。此外,这意味着在第二行读取的值是a在执行第一次读取时包含的值(反之亦然,因为根据程序的可观察执行状态,两个原子读取操作都同时发生)。所讨论的优化将第一读取的值重用于第二读取,该优化等效于编译器和/或JIT以原子方式执行该序列,因此是有效的。

原来较长的答案:

Java内存模型使用 部分排序 之前发生的事件来
描述操作。为了表达的限制,即第一次读r1和二读r2a,不能折叠,则需要证明一些操作语义需要在它们之间出现。

使用r1和进行的线程操作r2如下:

--> r(a) --> r(a) --> add -->

为了表达的需要的东西(比如说y之间)的谎言r1r2,你需要要求r1 的之前发生 y y 之前发生
r2。碰巧的是,没有规则在“ 先发生后发生” 关系的左侧出现读取操作。您可以得到的最接近的说法是:y before-before
r2,但是部分顺序也可以y发生在before之前r1,因此会破坏读取操作。

如果不存在 要求 运算符介于r1和之间的方案r2,那么您可以声明 没有运算符
出现在之间r1r2并且不违反语言的必需语义。使用单个读取操作将等同于此声明。

编辑 我的答案被否决了,所以我将进一步探讨其他细节。

以下是一些相关问题:

  • 是Java编译器或JVM 需要 折叠这些读取操作?

否。在add表达式中使用的表达式aa表达式不是常数表达式,因此不需要将它们折叠。

  • __JVM 是否 折叠这些读取操作?

为此,我不确定答案。通过编译程序并使用javap -c,很容易看出Java编译器不会折叠这些读取操作。不幸的是,要证明JVM不会折叠操作(甚至更难于处理器本身)并不容易。

  • __JVM 是否应该 折叠这些读取操作?

可能不是。每次优化都需要花费时间才能执行,因此在分析代码所花费的时间与您期望获得的收益之间是一个平衡。事实证明,某些优化(例如数组边界检查消除或检查空引用)对现实应用程序具有
广泛的 好处。该特定优化可能会提高性能的唯一情况是两个相同的读取操作顺序出现的情况。

此外,如对此答案的响应以及其他答案所示,此特定更改将导致用户可能不希望的某些应用程序发生 意外的 行为更改。

编辑2:
关于拉斐尔(Rafael)的描述,它声称两个读取操作无法重新排序。该语句旨在突出显示以下事实,即a按以下顺序缓存的读取操作可能会产生错误的结果:

a1 = read(a)
b1 = read(b)
a2 = read(a)
result = op(a1, b1, a2)

假设最初ab有其默认值0。然后你执行只是第一read(a)

现在,假设另一个线程执行以下序列:

a = 1
b = 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被缓存(有效地将所有三个读取操作视为一个原子操作,而不仅仅是前两个)。



 类似资料:
  • 可以编写一个可以折叠到迭代器上的常量函数吗?当我尝试时: 我发现一个编译器错误: 我假设我的匿名函数

  • 我有一个regex语句,如下所示: <代码>(。*)_(ce)_(。*)_([0-9]{8}).([A-Za-z]{1,20})(?:\\.[A-Za-z]{1,20})? 它应该将分组。 因此,这是一个可能的传递字符串: 。 这些群体是: 我现在想使(ce)成为可选的,并使正则表达式如下所示: ( 这样就会通过:,组将是: 这将传递:,组将是: 所以,正则表达式有效!问题是,当(ce_)出现在正

  • 问题内容: 所以…例如,我正在尝试将一个电子邮件“模板”引入iframe中,作为angularjs应用程序内用户的“预览”。iframe位于控制器区域内(我们称其为MainCtrl)。然后,用户将能够使用MainCtrl内提供的表单元素基于其输入来更新预览。举例来说,假设我们将模板拉入iframe的过程如下所示: 因此,在我们的index.html(angularjs应用)中,我们将具有绑定到{{

  • 问题内容: 在此系统中,我们存储产品,产品图像(产品可能有很多图像)和产品的默认图像。数据库: 如您所见,和,是循环参考。可以吗 问题答案: 不,那不行。表之间的循环引用是混乱的。请参阅这篇(已有十年的历史)文章:SQL By Design:循环参考 一些DBMS可以特别小心地处理这些问题,但是MySQL会遇到问题。 作为您的设计,第一选择是使两个FK之一为可空。这使您能够解决“鸡与蛋”问题(我应

  • 例如,我知道在检查字符串时,可以执行如下操作 但是是否有一种方法来检查一个字符是否匹配一个可能性列表?或者我必须逐一检查,例如 ...等。

  • 我已经阅读了许多相互矛盾的信息(msdn,SO等),关于易失性和VoletleRead(ReadAcquireFence)。 我理解这些限制的内存访问重新排序含义——我仍然完全搞不清楚的是新鲜度保证——这对我来说非常重要。 msdn doc用于挥发性提及: (…)这样可以确保字段中始终存在最新的值。 挥发性字段的msdn文档提到: 对易失性字段的读取称为易失性读取。易失性读取具有“获取语义”;也就