return 表达式总是被计算出来,还是可以被编译器优化掉?
Is the return expression always computed, or can it be optimized out by the compiler?
我知道在这段代码中:
public static void main(String[] args) {myMethod();}
private Object myMethod() {
Object o = new Object();
return o;
}
垃圾收集器会在myMethod
执行后销毁o
,因为myMethod
的return值没有赋值,因此没有对它的引用.但是如果代码是这样的:
public static void main(String[] args) {myMethod();}
private Object myMethod() {
int i = 5;
return i + 10;
}
编译器是否会处理 i + 10
,因为 return 值未分配?
如果 i
不是一个简单的原始对象,而是一个更大的对象:
public static void main(String[] args) {myMethod();}
private Object myMethod() {
return new LargeObject();
}
其中 LargeObject
有一个昂贵的构造函数,编译器是否仍会分配内存并调用构造函数,以防它有任何副作用?
如果 return 表达式很复杂,但没有副作用,这将尤其重要,例如:
public static void main(String[] args) {
List<Integer> list = new LinkedList();
getMiddle();
}
private Object getMiddle(List list) {
return list.get((int) list(size) / 2);
}
在现实生活中调用此方法而不使用 return 值将毫无意义,但这是为了举例。
我的问题是:给定这些示例(对象构造函数、基元操作、无副作用的方法调用),编译器是否可以在发现值无效时跳过方法的 return 语句'没有分配给任何东西?
我知道我可以针对这些问题提出很多测试,但我不知道我是否会相信它们。我对代码优化和 GC 的理解是相当基本的,但我认为我知道的足以说明对特定代码位的处理不一定具有普遍性。这就是我问的原因。
像 "what will the compiler do?" 关于 Java 的问题有点幼稚。首先,涉及两个编译器和一个解释器。静态编译器会做一些简单的优化,比如 或许 使用有效的最终操作数优化任何算术表达式。它肯定将常量、文字和常量表达式编译成字节码literals.The真正的魔法发生在运行时。
我看不出为什么要优化结果计算,除非 return 值被忽略。忽略 return 值的情况很少见,而且应该更罕见。
在运行时,上下文中提供了更多信息。对于优化,运行时解释器加上编译器动态二重奏可以解决诸如 "Is this section of code even worth optimizing?" HotSpot 之类的问题,如果调用者使用 return 值,它的同类不会优化掉 return new Foo();
实例化。但他们可能会采取不同的做法,可能会将属性扔到堆栈上,甚至在寄存器中,如果情况允许的话。因此,虽然对象存在于逻辑 Java 堆上,但它可能存在于物理 JVM 组件的其他地方。
谁知道具体的优化是否会发生?没有人。但是它们或类似的东西,或者更神奇的东西,可能会发生。 HotSpot 执行的优化可能与我们的预期或想象不同,但比我们预期或想象的更好,当它明智地决定不厌其烦地进行优化时。
哦,在运行时 HotSpot 可能会取消优化它之前优化过的代码。这是为了维护 Java 代码的语义。
首先,让我们来处理一下您的问题和一些评论中明显存在的误解。
在 HotSpot(Oracle 或 OpenJDK)Java 平台中,实际上有两个编译器需要考虑:
javac
编译器将 Java 源代码翻译成字节码。它做最小的优化。事实上,它所做的唯一重要优化是评估 compile-time-constant 表达式(这实际上是某些 comile-time 检查所必需的)和字符串连接序列的 re-writing。
你可以很容易地看到做了哪些优化......使用javap
......但它也具有误导性,因为heavy-duty优化还没有完成。基本上,javap
输出在优化方面几乎没有帮助。
JIT 编译器执行 heavy-weight 优化。当您的程序处于 运行ning 时,它会在 运行 时间被调用。
它不会立即调用。通常,您的字节码会在任何方法被调用的前几次被解释。 JVM 正在收集行为统计数据,JIT 编译器将使用这些数据来优化 (!)。
因此,在您的示例中,main
方法被调用一次,myMethd
被调用一次。 JIT 编译器甚至不会 运行,所以实际上字节码会被解释。 但这很酷。与通过 运行优化器节省的时间相比,JIT 编译器进行优化所花费的时间要多几个数量级。
但是假设优化器做了 运行 ...
JIT 代码编译器通常有几个策略:
- 在一个方法中,它根据方法的本地信息进行优化。
- 当一个方法被调用时,它会在调用处查看被调用的方法是否可以被内联。内联后,代码可以在其上下文中进一步优化。
所以这就是可能发生的事情。
那么你的myMethod()
就作为一个独立的方法进行了优化,不需要的语句不会被优化掉。因为它们在所有可能的情况下都不是不必要的。
当/如果对 myMethod()
的方法调用被内联(例如,进入 main(...)
方法,优化器将确定(例如)这些语句
int i = 5;
return i + 10;
在此上下文中是不必要的,并对其进行优化。
但请记住,JIT 编译器一直在发展。因此,准确预测将发生哪些优化以及何时发生几乎是不可能的。而且可能没有结果。
建议:
值得思考您是否在"gross"级别进行不必要的计算。选择正确的算法或数据结构往往很关键。
在细粒度级别,一般不值得。让 JIT 编译器来处理它。
除非你有明确的证据表明你需要优化(即客观上太慢的基准),并且那里有明确的证据是特定点的性能瓶颈(例如分析结果)。
我知道在这段代码中:
public static void main(String[] args) {myMethod();}
private Object myMethod() {
Object o = new Object();
return o;
}
垃圾收集器会在myMethod
执行后销毁o
,因为myMethod
的return值没有赋值,因此没有对它的引用.但是如果代码是这样的:
public static void main(String[] args) {myMethod();}
private Object myMethod() {
int i = 5;
return i + 10;
}
编译器是否会处理 i + 10
,因为 return 值未分配?
如果 i
不是一个简单的原始对象,而是一个更大的对象:
public static void main(String[] args) {myMethod();}
private Object myMethod() {
return new LargeObject();
}
其中 LargeObject
有一个昂贵的构造函数,编译器是否仍会分配内存并调用构造函数,以防它有任何副作用?
如果 return 表达式很复杂,但没有副作用,这将尤其重要,例如:
public static void main(String[] args) {
List<Integer> list = new LinkedList();
getMiddle();
}
private Object getMiddle(List list) {
return list.get((int) list(size) / 2);
}
在现实生活中调用此方法而不使用 return 值将毫无意义,但这是为了举例。
我的问题是:给定这些示例(对象构造函数、基元操作、无副作用的方法调用),编译器是否可以在发现值无效时跳过方法的 return 语句'没有分配给任何东西?
我知道我可以针对这些问题提出很多测试,但我不知道我是否会相信它们。我对代码优化和 GC 的理解是相当基本的,但我认为我知道的足以说明对特定代码位的处理不一定具有普遍性。这就是我问的原因。
像 "what will the compiler do?" 关于 Java 的问题有点幼稚。首先,涉及两个编译器和一个解释器。静态编译器会做一些简单的优化,比如 或许 使用有效的最终操作数优化任何算术表达式。它肯定将常量、文字和常量表达式编译成字节码literals.The真正的魔法发生在运行时。
我看不出为什么要优化结果计算,除非 return 值被忽略。忽略 return 值的情况很少见,而且应该更罕见。
在运行时,上下文中提供了更多信息。对于优化,运行时解释器加上编译器动态二重奏可以解决诸如 "Is this section of code even worth optimizing?" HotSpot 之类的问题,如果调用者使用 return 值,它的同类不会优化掉 return new Foo();
实例化。但他们可能会采取不同的做法,可能会将属性扔到堆栈上,甚至在寄存器中,如果情况允许的话。因此,虽然对象存在于逻辑 Java 堆上,但它可能存在于物理 JVM 组件的其他地方。
谁知道具体的优化是否会发生?没有人。但是它们或类似的东西,或者更神奇的东西,可能会发生。 HotSpot 执行的优化可能与我们的预期或想象不同,但比我们预期或想象的更好,当它明智地决定不厌其烦地进行优化时。
哦,在运行时 HotSpot 可能会取消优化它之前优化过的代码。这是为了维护 Java 代码的语义。
首先,让我们来处理一下您的问题和一些评论中明显存在的误解。
在 HotSpot(Oracle 或 OpenJDK)Java 平台中,实际上有两个编译器需要考虑:
javac
编译器将 Java 源代码翻译成字节码。它做最小的优化。事实上,它所做的唯一重要优化是评估 compile-time-constant 表达式(这实际上是某些 comile-time 检查所必需的)和字符串连接序列的 re-writing。你可以很容易地看到做了哪些优化......使用
javap
......但它也具有误导性,因为heavy-duty优化还没有完成。基本上,javap
输出在优化方面几乎没有帮助。JIT 编译器执行 heavy-weight 优化。当您的程序处于 运行ning 时,它会在 运行 时间被调用。
它不会立即调用。通常,您的字节码会在任何方法被调用的前几次被解释。 JVM 正在收集行为统计数据,JIT 编译器将使用这些数据来优化 (!)。
因此,在您的示例中,main
方法被调用一次,myMethd
被调用一次。 JIT 编译器甚至不会 运行,所以实际上字节码会被解释。 但这很酷。与通过 运行优化器节省的时间相比,JIT 编译器进行优化所花费的时间要多几个数量级。
但是假设优化器做了 运行 ...
JIT 代码编译器通常有几个策略:
- 在一个方法中,它根据方法的本地信息进行优化。
- 当一个方法被调用时,它会在调用处查看被调用的方法是否可以被内联。内联后,代码可以在其上下文中进一步优化。
所以这就是可能发生的事情。
那么你的
myMethod()
就作为一个独立的方法进行了优化,不需要的语句不会被优化掉。因为它们在所有可能的情况下都不是不必要的。当/如果对
myMethod()
的方法调用被内联(例如,进入main(...)
方法,优化器将确定(例如)这些语句int i = 5; return i + 10;
在此上下文中是不必要的,并对其进行优化。
但请记住,JIT 编译器一直在发展。因此,准确预测将发生哪些优化以及何时发生几乎是不可能的。而且可能没有结果。
建议:
值得思考您是否在"gross"级别进行不必要的计算。选择正确的算法或数据结构往往很关键。
在细粒度级别,一般不值得。让 JIT 编译器来处理它。
除非你有明确的证据表明你需要优化(即客观上太慢的基准),并且那里有明确的证据是特定点的性能瓶颈(例如分析结果)。