哪个循环有更好的性能?为什么?

时间:2008-09-21 02:46:43

标签: java performance string garbage-collection

String s = "";
for(i=0;i<....){
    s = some Assignment;
}

for(i=0;i<..){
    String s = some Assignment;
}

我不需要再次在循环外使用's'了。 第一个选项可能更好,因为每次都不会初始化新的String。然而,第二种情况会导致变量的范围仅限于循环本身。

编辑:回应米尔豪斯的回答。将String分配给循环中的常量是没有意义的吗?不,这里的“一些分配”意味着从正在迭代的列表中获得的变化值。

此外,问题不在于我担心内存管理问题。只是想知道哪个更好。

8 个答案:

答案 0 :(得分:107)

有限范围是最佳

使用您的第二个选项:

for ( ... ) {
  String s = ...;
}

范围不影响性能

如果你反编译每个编译的代码(使用JDK的javap工具),你会发现在这两种情况下循环都会编译成完全相同的JVM指令。另请注意,Brian R. Bondy's“选项#3”与选项#1相同。使用更严格的范围时,不会在堆栈中添加或删除任何额外的内容,并且在两种情况下都会在堆栈上使用相同的数据。

避免过早初始化

这两种情况的唯一区别在于,在第一个示例中,变量s被不必要地初始化。这是与变量声明的位置不同的问题。这会添加两条浪费的指令(加载字符串常量并将其存储在堆栈帧插槽中)。一个好的静态分析工具会警告你,你永远不会读取你赋给s的值,而一个好的JIT编译器可能会在运行时忽略它。

你可以通过使用一个空声明(即String s;)来解决这个问题,但这被认为是不好的做法,并且下面讨论了另一个副作用。

通常会将类似null的伪值分配给变量,只是为了避免编译器错误,即读取变量而不进行初始化。此错误可以视为变量作用域太大,并且在需要接收有效值之前声明它。空声明强制您考虑每个代码路径;不要通过指定虚假值来忽略这个有价值的警告。

保存堆栈插槽

如前所述,虽然JVM指令在两种情况下都是相同的,但是有一个微妙的副作用,它在JVM级别上最好使用尽可能最有限的范围。这在方法的“局部变量表”中可见。考虑如果你有多个循环会发生什么,变量在不必要的大范围内声明:

void x(String[] strings, Integer[] integers) {
  String s;
  for (int i = 0; i < strings.length; ++i) {
    s = strings[0];
    ...
  }
  Integer n;
  for (int i = 0; i < integers.length; ++i) {
    n = integers[i];
    ...
  }
}

变量sn可以在它们各自的循环中声明,但由于它们不是,编译器在堆栈帧中使用两个“槽”。如果它们在循环内声明,编译器可以重用相同的插槽,使堆栈帧更小。

真正重要的是什么

但是,大多数这些问题并不重要。一个好的JIT编译器会发现无法读取您浪费分配的初始值,并优化分配。在这里或那里保存一个插槽不会决定你的应用程序。

重要的是让您的代码可读且易于维护,在这方面,使用有限的范围显然更好。变量具有的范围越小,就越容易理解它的使用方式以及对代码的任何更改会产生什么影响。

答案 1 :(得分:22)

理论中,在循环中声明字符串是浪费资源。 但是,在 practice 中,您呈现的两个片段将编译为相同的代码(循环外的声明)。

因此,如果您的编译器进行了任何数量的优化,那就没有区别了。

答案 2 :(得分:17)

一般来说,我会选择第二个,因为's'变量的范围仅限于循环。优点:

  • 这对程序员来说更好,因为你不必担心在函数后面的某个地方再次使用
  • 这对编译器来说更好,因为变量的范围较小,因此可以进行更多的分析和优化
  • 这对未来的读者来说更好,因为他们不会想知道为什么's'变量在循环之外声明,如果它以后从未使用过

答案 3 :(得分:6)

如果你想加速循环,我更喜欢在计数器旁边声明一个max变量,这样就不需要重复查找condidtion了:

而不是

for (int i = 0; i < array.length; i++) {
  Object next = array[i];
}

我更喜欢

for (int i = 0, max = array.lenth; i < max; i++) {
  Object next = array[i];
}

已经提到了应该考虑的任何其他事情,所以只需要我的两分钱(见erickons帖子)

Greetz,GHad

答案 4 :(得分:4)

要向@ Esteban Araya's answer添加一个位,它们都需要在每次循环时都创建一个新字符串(作为some Assignment表达式的返回值)。无论如何,这些字符串都需要进行垃圾收集。

答案 5 :(得分:3)

我知道这是一个老问题,但我想我会添加一些与略有相关的内容。

我在浏览Java源代码时注意到,某些方法(如String.contentEquals(下面重复))会产生冗余的局部变量,这些变量只是类变量的副本。我相信在某处有一个注释,这意味着访问局部变量比访问类变量更快。

在这种情况下,“v1”和“v2”似乎是不必要的,可以删除以简化代码,但是为了提高性能而添加了它们。

public boolean contentEquals(StringBuffer sb) {
    synchronized(sb) {
        if (count != sb.length())
            return false;
        char v1[] = value;
        char v2[] = sb.getValue();
        int i = offset;
        int j = 0;
        int n = count;
        while (n-- != 0) {
            if (v1[i++] != v2[j++])
                return false;
        }
    }
    return true;
}

答案 6 :(得分:1)

在我看来,我们需要更多的问题说明。

s = some Assignment;

没有指定这是什么类型的赋值。如果作业是

s = "" + i + "";

然后需要分配新的刺痛。

但如果是

s = some Constant;

s只会指向常量的内存位置,因此第一个版本的内存效率会更高。

似乎我有点愚蠢地担心解释语言恕我直言的for循环的优化。

答案 7 :(得分:1)

当我使用多个线程(50+)时,我发现这是一个非常有效的方法来处理鬼线程问题而无法正确关闭进程....如果我错了,请让我知道为什么我错了:

Process one;
BufferedInputStream two;
try{
one = Runtime.getRuntime().exec(command);
two = new BufferedInputStream(one.getInputStream());
}
}catch(e){
e.printstacktrace
}
finally{
//null to ensure they are erased
one = null;
two = null;
//nudge the gc
System.gc();
}