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

时间:2020-03-06 14:29:57  来源:igfitidea点击:
String s = "";
for(i=0;i<....){
    s = some Assignment;
}

或者

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

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

编辑:回应米尔豪斯的回答。在循环中将String分配给常量是没有意义的吗?不,这里的"某项分配"是指从要迭代的列表中获得的不断变化的值。

另外,问题不是因为我担心内存管理。只想知道哪个更好。

解决方案

从理论上讲,在循环内部声明字符串是浪费资源的。
但是,实际上,我们呈现的两个代码片段都将编译为相同的代码(循环外的声明)。

因此,如果编译器进行了许多优化,则没有任何区别。

通常,我会选择第二个,因为's'变量的范围仅限于循环。好处:

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

为了增加@Esteban Araya的答案,它们每次都需要在循环中都创建一个新的字符串(作为" some Assignment"表达式的返回值)。无论哪种方式,都需要对这些字符串进行垃圾回收。

在我看来,我们需要对该问题进行更多说明。

s = some Assignment;

没有指定这是什么类型的任务。如果分配是

s = "" + i + "";

那么就需要分配一个新的字符串。

但是如果是

s = some Constant;

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

似乎我有点傻,不必担心对解释的语言恕我直言的for循环进行大量优化。

范围最好

使用第二个选项:

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

范围不影响性能

如果我们使用JDK的javap工具反汇编每个编译器的代码,则在两种情况下,我们都将看到循环编译为完全相同的JVM指令。还要注意,Brian R. Bondy的"选项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];
    ...
  }
}

可以在各自的循环中声明变量s和n,但是由于没有声明,因此编译器在堆栈帧中使用两个" slots"。如果在循环内声明它们,则编译器可以重用相同的插槽,从而使堆栈帧更小。

真正重要的是

但是,这些问题大多数都不重要。一个好的JIT编译器将看到不可能读取我们浪费分配的初始值,并且无法优化分配。在此处保存插槽,否则将无法建立或者破坏应用程序。

重要的是使代码易于阅读且易于维护,在这方面,使用有限的范围显然更好。变量的范围越小,则越容易理解其用法以及对代码进行的更改将产生何种影响。

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

代替

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];
}

已经提到了任何其他应考虑的事项,所以只提到了我的两分钱(请参阅埃里克森帖子)

加的特Greetz

我知道这是一个古老的问题,但是我想我要补充一点点。

我在浏览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;
}