【问题标题】:Will the Java compiler optimize out String.length() in a for-loop's condition?Java 编译器会在 for 循环的条件下优化 String.length() 吗?
【发布时间】:2014-10-18 03:50:38
【问题描述】:

考虑以下 Java 代码片段:

String buffer = "...";
for (int i = 0; i < buffer.length(); i++)
{
    System.out.println(buffer.charAt(i));
}

由于String 是不可变的并且buffer 没有在循环内重新分配,Java 编译器是否足够聪明以优化for 循环条件中的buffer.length() 调用?例如,它是否会发出等同于以下内容的字节码,其中buffer.length() 被分配给一个变量,并且该变量在循环条件中使用?我读过一些像 C# 这样的语言会做这种优化。

String buffer = "...";
int length = buffer.length();
for (int i = 0; i < length; i++)
{
    System.out.println(buffer.charAt(i));
}

【问题讨论】:

    标签: java


    【解决方案1】:

    在 Java(和 .Net)中,字符串是计算长度的(UTF-16 代码点的数量),因此查找长度是一个简单的操作。

    编译器 (javac) 可能执行也可能不执行 hoisting,但 JVM JIT 编译器会将 almost certainly 内联对 .length() 的调用,使 buffer.length() 只不过是内存访问。

    【讨论】:

    • 那么长的字符串呢,比如说几个 1000K?
    • 这是O(1) 成本,所以没关系。字符串存储为{ length = 1000, character data = { 0x65, ... 0x65 } }
    • 我认为他在谈论带有抖动的 JIT(即时编译器)
    • @Drejc:不管有多长,String 都知道自己的长度而不用计算。 (必须如此,因为无法计算。与(比如)C 中的字符串以\0 结尾的情况不同,Java 中无法通过检查字符内容来确定字符串的长度。)跨度>
    • 底线是,除非分析发现某些问题是一个问题,否则我不会为了潜在的速度而牺牲可读性。 “必须编写程序供人们阅读,并且只是偶然地供机器执行”〜H。阿贝尔森
    【解决方案2】:

    Java 编译器 (javac) 不执行此类优化。 JIT 编译器可能会内联length() 方法,这至少可以避免方法调用的开销。

    根据您运行的 JDK,length() 方法本身可能会返回最终的 length 字段,这是一种廉价的内存访问,或字符串的内部 char[] 数组的长度。在后一种情况下,数组的长度是恒定的,并且数组引用大概是final,因此 JIT 可能足够复杂,可以按照您的建议临时记录一次长度。但是,这种事情是实现细节。除非您控制您的代码将在其上运行的每台机器,否则您不应该对它将在哪个 JVM 上运行或将执行哪些优化做出太多假设。

    至于您应该如何编写代码,直接在循环条件中调用length() 是一种常见的代码模式,并受益于可读性。我会让事情变得简单,让 JIT 优化器完成它的工作,除非你处于一个表现出性能问题的关键代码路径中,并且你同样证明了这种微优化是值得的。

    【讨论】:

    • “JIT 编译器可能会内联 length() 方法”。有这方面的文件吗?我喜欢阅读这类东西。
    • 它实际上不返回char[] 的长度,因为String 可能只使用它的一部分。 Strings 有自己的 final int 字段来记住长度。
    • 默认情况下,我相信 Oracle JVM 将为至少被调用一次的方法内联最多 35 个字节的字节码。我相信经常调用的方法有一个更大的门槛。您可以查看this StackOverflow question。 @resueman,我正在查看的源版本没有单独的字段。另一个我们不应该对客户端机器的 JVM/JDK 做出假设的例子:)。
    • @resueman 字符串数组共享至少从 Java 7 开始就消失了。它确实是这样实现的,但那是过去的事情了。 Java7/8 使用一个私有数组,其长度正好是 chars。
    • @MikeStrobel 很有趣。我以为每个实现都是这样处理的。 openjdk-7 文档可以。绝对表明你不应该相信这样的细节,除非你完全控制环境:)
    【解决方案3】:

    您可以做几件事来检查您的实现的两种变体。

    1. (难度:容易)对每个版本的代码在相似条件下进行测试并测量速度。确保您的循环足够显着以注意到差异,可能没有差异。

    2. (难度:中等)用 javap 检查字节码,看看编译器是如何解释两个版本的(这可能因 javac 实现而异),也可能不是(当规范中指定了行为并且没有实现者解释的余地​​)。

    3. (难度:难)使用 JITWatch 检查两个版本的 JIT 输出,您需要对字节码和汇编程序有很好的了解。

    【讨论】:

      猜你喜欢
      • 2017-01-27
      • 2011-03-04
      • 2013-08-15
      • 1970-01-01
      • 1970-01-01
      • 2013-11-25
      • 2012-12-11
      • 1970-01-01
      • 2017-11-05
      相关资源
      最近更新 更多