一个符合规范的Java编译器能够优化这段代码吗?

16

今天我在教授一门介绍性编程课程,讲解了Java中涉及变量赋值的简单代码。这段代码的目的不是为了展示任何特别激动人心的东西,而是为了确保学生理解变量赋值语句。

我在黑板上写下了以下方法,并逐行解释:

private void simpleMethod() {
    int myInt = 137;
    myInt = 42;
    myInt = myInt + 1;

    /* ... code using myInt ... */
}
一个学生问我,在程序运行时,变量myInt是否会实际持有值137和42,或者它是否会直接跳到持有43的值。我告诉学生,代码将逐行执行,因此变量实际上会持有这些中间值。
但老实说,我不确定javac会生成什么字节码(完全忽略JVM所做的优化)。 javac(或任何Java编译器)是否可以合法地将愚蠢的赋值语句优化掉,并直接将myInt初始化为43?
根据我的系统上的javap,使用javac编译的上述代码产生
   0: sipush        137
   3: istore_1      
   4: bipush        42
   6: istore_1      
   7: iload_1       
   8: iconst_1      
   9: iadd          
  10: istore_1      
  11: return        

所以这里没有进行任何优化。我的问题是,是否可以对此进行优化,以便解决问题。


жҲ‘и®ӨдёәдҪ еҸҜд»ҘдҪҝз”ЁjavapиҪ»жқҫжөӢиҜ•дёҖдёӢиөӢеҖјжҳҜеҗҰиў«дјҳеҢ–дәҶгҖӮиҮідәҺиҝҷж ·еҒҡжҳҜеҗҰеҗҲжі•...иҝҷд№ҹжҳҜжҲ‘жғізҹҘйҒ“зҡ„дәӢжғ…гҖӮ - nhahtdh
1
@nhahtdh- 上面显示的是javap的输出结果。没有进行任何优化。 - templatetypedef
4个回答

8
JLS仅规定程序产生的可观察行为的合同。由于myInt是本地变量,编译时优化确实可以进行优化,因为这将产生与规范一致的行为,并且规范中没有任何说明不允许这样做(至少我没有找到!)。 规范的第1章明确规定了规范的可观察性: 此文档完全指定了表达式的(显然)求值顺序...。由于常数折叠到myInt = 43不会改变显然的行为,因此优化将与JLS一致。
事实上,Java应用程序的编译目标甚至在JLS中都没有指定。第1章说Java应用程序“通常”编译为JVM规范中指定的字节码(一个单独的文档),但它并不要求这样做。有一些语句必须在编译时进行优化,但myInt不是其中之一。即使myInt是一个字段,我认为优化也是允许的;不同的行为仍然是有效的行为,即使myInt是易失性的(因为它代表事件的一个有效排序)。所以,简短的回答是,我认为你的学生是正确的;将其优化为myInt = 43是完全可以的。话虽如此,javac通常几乎不会进行任何优化。优化几乎全部在JIT中完成。

5
我相信Java编译器允许在编译时静态确定的任何常量折叠。
因此,是的,这可以由javac进行优化。
然而,javac不需要进行此优化,因为JVM JIT编译器几乎肯定会在稍后进行相同的优化。从这个角度来看,Java到字节码编译器是否进行此优化可能与运行时执行的实际本机代码的影响无关。

1
唯一可以在编译时安全地完成的情况可能是局部变量。如果代码涉及实例或类成员,则编译时优化可能会导致意外行为。 - nhahtdh
1
你能找到支持这个观点的参考资料吗?如果我的请求看起来有些不寻常,我很抱歉,但我已经思考了这类问题一段时间,如果可能的话,我想要一个明确的答案。 - templatetypedef
我正在寻找JLS的参考资料,但似乎找不到。也许这是一种任何不与JLS相矛盾的情况都是允许的情况...... - mikera

0
所以这里没有进行任何优化。
这并不奇怪,因为Java使用动态编译。
JVM在运行时优化几乎所有的代码,这意味着无论您使用1996年Java 1.0编译的代码还是Scala或JRuby、JGo等代码,您都可以获得特定CPU型号本地代码优化的全部好处。
因此,许多语言都有JVM实现,这样它们就不需要能够为JVM运行的所有平台生成最佳代码。

虽然这是正确的,但我的问题是编译器是否被法律允许进行此优化,而不是它是否是一个好主意或者正确的优化位置。不过还是谢谢! - templatetypedef
@templatetypedef 鉴于过去16年编译器设计的目标是尽可能简化优化,我认为javac不太可能实现这一点。编译器很可能会避免进行任何可以由理智的开发人员轻松完成的优化。请注意:JIT不太可能对代码进行优化,除非它已经运行了10,000次。简而言之,在理论上优化是合法的,但在实践中不太可能发生。 - Peter Lawrey

0
尽管如此,我的问题是编译器是否被法律允许进行这种优化?
通过传统的数据流分析(流/转移函数、内集合、外集合、生成集合、杀死集合等),特别是某些路径的前向数据流,也称为到达定义(或使用-定义链)……是的。本质上,优化器可以将每个变量的使用链接到达它的定义。
在这种情况下,优化器可以确定myInt的初始定义(= 137)从未到达其使用,而其他定义(= 42 ^1)确实到达其使用,因为在任何(一些)从该定义到其使用的路径上都没有重新定义它。
参考文献:
  • Dragon book 1ed (1986) chapter 10
  • Dragon book 2ed (2007) chapter 9 (9.2 specifically)
1 - 我在我的教学代码中经常使用42。我打赌我们都是。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接