在循环内部或外部声明一个对象?

时间:2008-12-18 13:03:13

标签: java performance

以下代码段是否存在任何性能损失?

for (int i=0; i<someValue; i++)
{
    Object o = someList.get(i);
    o.doSomething;
}

或者这段代码实际上更有意义吗?

Object o;
for (int i=0; i<someValue; i++)
{
    o = someList.get(i);
    o.doSomething;
}

如果在字节代码中这两个是完全等效的,那么显然第一种方法在样式方面看起来更好,但我想确保这种情况。

16 个答案:

答案 0 :(得分:46)

在今天的编译器中,没有。我在最小的范围内声明对象,因为它对下一个人来说更具可读性。

答案 1 :(得分:15)

To quote Knuth, who may be quoting Hoare

  

过早优化是万恶之源。

编译器是否会通过在循环外定义变量来产生稍微快一点的代码是有争议的,我想它不会。我会猜测它会产生相同的字节码。

将此与您可能通过使用循环声明正确确定变量范围的错误数量进行比较......

答案 2 :(得分:10)

在循环中声明Object o没有性能损失。 编译器生成非常相似的字节码并进行正确的优化。

有关类似示例,请参阅文章Myth - Defining loop variables inside the loop is bad for performance

答案 3 :(得分:7)

您可以使用javap -c反汇编代码并检查编译器实际发出的内容。在我的设置(使用eclipse编译的java 1.5 / mac)中,循环的字节码是相同的。

答案 4 :(得分:5)

第一个代码更好,因为它将o变量的范围限制为for块。从性能的角度来看,它可能对Java没有任何影响,但它可能在较低级别的编译器中。如果你做第一个,他们可能会把变量放在寄存器中。

事实上,有些人可能会认为如果编译器很笨,那么第二个片段在性能方面更好。这是一些导师在大学时告诉我的,我嘲笑他这个建议!基本上,编译器在方法开始时只在方法的局部变量上分配一次内存(通过调整堆栈指针)并在方法结束时释放它(再次通过调整堆栈指针,假设它不是C ++)或者它没有任何析构函数被调用)。因此,方法中的所有基于堆栈的局部变量都会立即分配,无论它们在何处被声明,以及它们需要多少内存。实际上,如果编译器是愚蠢的,性能方面没有区别,但如果它足够智能,第一个代码实际上可以更好,因为它将帮助编译器理解变量的范围和生命周期!顺便说一句,如果它真的很聪明,那么性能应该没有差别,因为它可以推断实际范围。

当然,使用new构建对象与仅仅声明它完全不同。

我认为可读性对性能更重要,从可读性的角度来看,第一个代码肯定更好。

答案 5 :(得分:4)

我必须承认我不懂java。但这两个是等价的吗?对象的生命周期是否相同?在第一个例子中,我假设(不知道java)o将有资格立即循环终止垃圾收集。

但是在第二个例子中,在退出外部范围(未显示)之前,o肯定没有资格进行垃圾收集?

答案 6 :(得分:4)

不要过早优化。比其中任何一个都好:

for(Object o : someList) {
    o.doSomething();
}

因为它消除了样板并澄清了意图。

除非您正在使用嵌入式系统,否则所有投注均已关闭。否则,不要试图超越JVM。

答案 7 :(得分:1)

我一直认为现在大多数编译器都足够聪明,可以选择后者。假设是这种情况,我会说第一个看起来确实更好。如果循环变得非常大,则无需四处寻找o声明的位置。

答案 8 :(得分:1)

它们具有不同的语义。哪个更有意义?

为了“表现原因”而重复使用对象通常是错误的。

问题是对象“意味着什么”?你为什么创造它?它代表什么?对象必须与现实世界的东西并行。事物被创造,经历状态变化,并且出于原因报告他们的状态。

这是什么原因?您的对象如何建模并反映这些原因?

答案 9 :(得分:1)

要理解这个问题的核心...... [请注意,如果JLS允许,非JVM实现可能会有不同的做法......]

首先,请记住示例中的局部变量“o”是指针,而不是实际对象。

所有局部变量都在4字节插槽中分配在运行时堆栈上。双打和长排需要两个插槽;其他原语和指针需要一个。 (甚至布尔都会占据一席之地)

必须为每个方法调用创建固定的运行时堆栈大小。此大小由方法中任何给定点所需的最大局部变量“slot”确定。

在上面的示例中,两个版本的代码都需要相同的最大局部变量数。

在这两种情况下,都会生成相同的字节码,更新运行时堆栈中的相同插槽。

换句话说,根本没有性能损失。

但是,根据方法中的其余代码,“循环外声明”版本实际上可能需要更大的运行时堆栈分配。例如,比较

for (...) { Object o = ... }
for (...) { Object o = ... }

Object o;
for (...) {  /* loop 1 */ }
for (...) { Object x =...; }

在第一个示例中,两个循环都需要相同的运行时堆栈分配。

在第二个例子中,因为“o”超过了循环,“x”需要一个额外的运行时堆栈槽。

希望这有帮助, - 斯科特

答案 10 :(得分:0)

第一个更有意义。它将变量保留在其使用的范围内。并且防止在稍后的迭代中使用的一次迭代中分配的值,这更具防御性。

前者有时被认为更有效率,但任何合理的编译器都应该能够将其优化为与后者完全相同。

答案 11 :(得分:0)

在这两种情况下,对象o的类型信息在编译时确定。在第二个实例中,o被视为for循环的全局,在第一个实例中,聪明的Java编译器知道o将必须只要循环持续,就可以使用,因此将优化代码,使得在每次迭代中不会重新指定o的类型。 因此,在这两种情况下,o类型的规范将进行一次,这意味着唯一的性能差异将在o的范围内。显然,较窄的范围总能提高性能,因此回答您的问题:不,第一个代码片段没有性能损失;实际上,这个代码片段比第二个更优化。

在第二部分中,o被赋予了不必要的范围,除了性能问题之外,还可能是一个安全问题。

答案 12 :(得分:0)

作为维护代码而不是代码的人。

版本1是首选 - 将范围尽可能保持在本地对于理解是必不可少的。它也更容易重构这种代码。

如上所述 - 我怀疑这会对效率产生任何影响。事实上,我认为如果范围更局部,编译器可以用它做更多的事情!

答案 13 :(得分:0)

当使用多个线程(如果你做50+)时,我发现这是处理鬼线问题的一种非常有效的方法:

Object one;
Object two;
Object three;
Object four;
Object five;
try{
for (int i=0; i<someValue; i++)
{
o = someList.get(i);
o.doSomething;
}
}catch(e){
e.printstacktrace
}
finally{
one = null;
two = null;
three = null;
four = null;
five = null;
System.gc();
}

答案 14 :(得分:-1)

答案部分取决于构造函数的作用以及循环后对象的作用,因为这在很大程度上决定了代码的优化方式。

如果对象很大或很复杂,绝对要在循环外声明它。否则,人们告诉你不要过早地优化是正确的。

答案 15 :(得分:-3)

我实际上在我面前的代码看起来像这样:

for (int i = offset; i < offset + length; i++) {
    char append = (char) (data[i] & 0xFF);
    buffer.append(append);
}
...
for (int i = offset; i < offset + length; i++) {
    char append = (char) (data[i] & 0xFF);
    buffer.append(append);
}
...
for (int i = offset; i < offset + length; i++) {
    char append = (char) (data[i] & 0xFF);
    buffer.append(append);
}

所以,依靠编译器的能力,我可以假设 i 只有一个堆栈分配,追加只有一个堆栈分配。然后一切都会好起来,除了重复的代码。

作为旁注,已知java应用程序速度很慢。我从未试图在java中进行分析,但我认为性能影响主要来自内存分配管理。

相关问题