【问题标题】:Which loop has better performance? Why?哪个循环的性能更好?为什么?
【发布时间】:2010-09-11 17:23:51
【问题描述】:
String s = "";
for(i=0;i<....){
    s = some Assignment;
}

或

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

我再也不需要在循环外使用“s”了。 第一个选项可能更好,因为每次都不会初始化新的字符串。然而,第二个会导致变量的范围被限制在循环本身。

编辑:回应 Milhous 的回答。将字符串分配给循环内的常量是没有意义的,不是吗?不,这里的“一些分配”是指从被迭代的列表中获得的变化值。

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

【问题讨论】:

  • 迭代指定为文字的字符串集合并不罕见。例如,表的列标题可能硬编码为 String[]。不过,重要的是在两种情况下都会发生相同的分配,因此不会影响答案。
  • 另一条评论:不要忘记,如果您不打算更改s 的值,您应该 声明它final。许多 Java 程序员经常忘记这一点。

标签: java performance string garbage-collection


【解决方案1】:

在理论上,在循环内声明字符串是一种资源浪费。 然而,在实践中,您提供的两个 sn-ps 都将编译为相同的代码(循环外的声明)。

所以,如果你的编译器做了任何优化,没有区别。

【讨论】:

  • 引用只是放在这个方法调用的堆栈帧中,对吧?
  • 我认为您误解了 1800 INFORMATION 所写的内容。 Java 字符串的不可变性在这里无关紧要:“一些赋值”每次都会产生一个新的字符串,无论字符串是否不可变。
  • 已更新问题。分配一个硬编码的字符串是不合逻辑的。
  • 哪个理论告诉你声明变量会浪费资源?
  • jrudolph:如果我没记错我的 Java,创建一个新的空字符串会创建一个新的垃圾收集对象。
【解决方案2】:

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

  • 这对程序员来说更好,因为您不必担心函数稍后会再次使用 's'
  • 这对编译器来说更好,因为变量的范围更小,因此它可以进行更多的分析和优化
  • 这对未来的读者来说更好,因为他们不会想知道为什么“s”变量在以后不再使用的情况下被声明在循环之外

【讨论】:

    【解决方案3】:

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

    【讨论】:

    • 不一定。我们不知道在循环内分配给“s”的是什么。也许它是一个在 PermGen 空间中分配的字符串常量,永远不会被垃圾收集。我们所知道的是,无论它是什么,在两种情况下都是一样的,所以没关系。
    • @erickson:我同意它可以是一个字符串常量,但在这种情况下,我希望编译器可能会使用常量传播将 s 移出循环体。我假设它不是一个常数,因为明智的程序员也会这样做。
    • 我并不是说每次迭代都使用相同的字符串常量。请参阅我对 OP 的评论。
    • 是的,在这种情况下不会有优化
    【解决方案4】:

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

    s = some Assignment;
    

    没有具体说明这是什么类型的赋值。如果分配是

    s = "" + i + "";
    

    那么需要分配一个新的sting。

    如果是的话

    s = some Constant;
    

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

    对于解释语言恕我直言的 for 循环的大量优化,我似乎有点傻。

    【讨论】:

      【解决方案5】:

      有限的范围是最好的

      使用第二个选项:

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

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

      真正重要的是什么

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

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

      【讨论】:

      • 我仍然不完全相信,但我删除了我的答案,因为它是错误的,但作为这个问题的参考。这是我所拥有的:{//不要删除大括号 String s; for(i=0;i<.... s="一些赋值;">
      • 如果可以的话,我会两次投票赞成这条评论。我还会将问题标记为“早期优化”。
      • 多么好的答案;如果可以的话,我也会多次投票。
      • 我之前没有玩过 javap,所以我检查了一个获取日期并将 String 设置为 date.toString 的 for 循环。我做的反汇编表明代码是不同的。您可以看到 s 正在内部循环中的每个循环中设置。这是 jit 编译器可以解决的问题吗?
      • @Philip T. - 我不确定我是否理解您所描述的内容。如果您的意思是在循环的每次迭代中为 String 分配了相同的值,并且您询问是否可以“提升”该计算,是的,这是可能的,但这取决于 JVM。
      【解决方案6】:

      如果你想加快 for 循环,我更喜欢在计数器旁边声明一个 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];
      }
      

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

      Greetz,加德

      【讨论】:

        【解决方案7】:

        我知道这是一个老问题,但我想我会补充一点与轻微相关的内容。

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

        【讨论】:

        • 这在旧虚拟机上可能比现在更有用。我很难相信这对 HotSpot 有任何影响。
        【解决方案8】:

        当我使用多个线程 (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();
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-11-28
          • 2012-02-16
          • 2023-02-22
          • 2015-10-18
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多