gpt4 book ai didi

java - 为什么 StringBuilder#append(int) 在 Java 7 中比在 Java 8 中更快?

转载 作者:IT老高 更新时间:2023-10-28 11:48:26 27 4
gpt4 key购买 nike

在调查 little debate 时w.r.t.使用 "" + n Integer.toString(int) 将整数原语转换为字符串我写了这个 JMH微基准:

@Fork(1)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@State(Scope.Benchmark)
public class IntStr {
protected int counter;


@GenerateMicroBenchmark
public String integerToString() {
return Integer.toString(this.counter++);
}

@GenerateMicroBenchmark
public String stringBuilder0() {
return new StringBuilder().append(this.counter++).toString();
}

@GenerateMicroBenchmark
public String stringBuilder1() {
return new StringBuilder().append("").append(this.counter++).toString();
}

@GenerateMicroBenchmark
public String stringBuilder2() {
return new StringBuilder().append("").append(Integer.toString(this.counter++)).toString();
}

@GenerateMicroBenchmark
public String stringFormat() {
return String.format("%d", this.counter++);
}

@Setup(Level.Iteration)
public void prepareIteration() {
this.counter = 0;
}
}

我使用 Linux 机器上存在的两个 Java VM(最新的 Mageia 4 64 位、Intel i7-3770 CPU、32GB RAM)使用默认 JMH 选项运行它。第一个 JVM 是随 Oracle JDK 提供的
8u5 64 位:
java version "1.8.0_05"
Java(TM) SE Runtime Environment (build 1.8.0_05-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.5-b02, mixed mode)

有了这个 JVM,我得到了我所期望的:
Benchmark                    Mode   Samples         Mean   Mean error    Units
b.IntStr.integerToString thrpt 20 32317.048 698.703 ops/ms
b.IntStr.stringBuilder0 thrpt 20 28129.499 421.520 ops/ms
b.IntStr.stringBuilder1 thrpt 20 28106.692 1117.958 ops/ms
b.IntStr.stringBuilder2 thrpt 20 20066.939 1052.937 ops/ms
b.IntStr.stringFormat thrpt 20 2346.452 37.422 ops/ms

即使用 StringBuilder由于创建 StringBuilder 的额外开销,类的速度较慢对象并附加一个空字符串。使用 String.format(String, ...)甚至更慢,一个数量级左右。

另一方面,发行版提供的编译器基于 OpenJDK 1.7:
java version "1.7.0_55"
OpenJDK Runtime Environment (mageia-2.4.7.1.mga4-x86_64 u55-b13)
OpenJDK 64-Bit Server VM (build 24.51-b03, mixed mode)

这里的结果很有趣:
Benchmark                    Mode   Samples         Mean   Mean error    Units
b.IntStr.integerToString thrpt 20 31249.306 881.125 ops/ms
b.IntStr.stringBuilder0 thrpt 20 39486.857 663.766 ops/ms
b.IntStr.stringBuilder1 thrpt 20 41072.058 484.353 ops/ms
b.IntStr.stringBuilder2 thrpt 20 20513.913 466.130 ops/ms
b.IntStr.stringFormat thrpt 20 2068.471 44.964 ops/ms

为什么 StringBuilder.append(int)用这个 JVM 看起来快得多?看着 StringBuilder类源代码没有发现特别有趣的东西 - 所讨论的方法几乎与 Integer#toString(int) 相同.有趣的是,附加 Integer.toString(int) 的结果( stringBuilder2 微基准测试)似乎并没有更快。

这种性能差异是否是测试工具的问题?或者我的 OpenJDK JVM 是否包含会影响这个特定代码(反)模式的优化?

编辑:

为了进行更直接的比较,我安装了 Oracle JDK 1.7u55:
java version "1.7.0_55"
Java(TM) SE Runtime Environment (build 1.7.0_55-b13)
Java HotSpot(TM) 64-Bit Server VM (build 24.55-b03, mixed mode)

结果与OpenJDK类似:
Benchmark                    Mode   Samples         Mean   Mean error    Units
b.IntStr.integerToString thrpt 20 32502.493 501.928 ops/ms
b.IntStr.stringBuilder0 thrpt 20 39592.174 428.967 ops/ms
b.IntStr.stringBuilder1 thrpt 20 40978.633 544.236 ops/ms

似乎这是一个更普遍的 Java 7 与 Java 8 问题。也许 Java 7 有更积极的字符串优化?

编辑 2 :

为了完整起见,以下是这两个 JVM 的与字符串相关的 VM 选项:

对于 Oracle JDK 8u5:
$ /usr/java/default/bin/java -XX:+PrintFlagsFinal 2>/dev/null | grep String
bool OptimizeStringConcat = true {C2 product}
intx PerfMaxStringConstLength = 1024 {product}
bool PrintStringTableStatistics = false {product}
uintx StringTableSize = 60013 {product}

对于 OpenJDK 1.7:
$ java -XX:+PrintFlagsFinal 2>/dev/null | grep String
bool OptimizeStringConcat = true {C2 product}
intx PerfMaxStringConstLength = 1024 {product}
bool PrintStringTableStatistics = false {product}
uintx StringTableSize = 60013 {product}
bool UseStringCache = false {product}
UseStringCache选项在 Java 8 中被删除,没有替换,所以我怀疑这有什么区别。其余选项似乎具有相同的设置。

编辑 3:
AbstractStringBuilder源代码的并排对比, StringBuilderInteger来自 src.zip 的类文件显示没有什么值得注意的。除了大量的外观和文档更改之外, Integer现在有一些对无符号整数和 StringBuilder 的支持已稍微重构以与 StringBuffer 共享更多代码.这些更改似乎都不会影响 StringBuilder#append(int) 使用的代码路径,虽然我可能错过了一些东西。

IntStr#integerToString() 生成的汇编代码的比较和 IntStr#stringBuilder0()有趣得多。为 IntStr#integerToString()生成的代码的基本布局两种 JVM 都相似,尽管 Oracle JDK 8u5 似乎更具侵略性 w.r.t.在 Integer#toString(int) 中内联一些调用代码。与 Java 源代码有明确的对应关系,即使对于汇编经验极少的人也是如此。
IntStr#stringBuilder0() 的汇编代码然而,却完全不同。 Oracle JDK 8u5 生成的代码再次与 Java 源代码直接相关——我可以轻松识别相同的布局。相反,OpenJDK 7 生成的代码对于未受过训练的人来说几乎无法识别(就像我的一样)。 new StringBuilder()调用似乎被删除了,就像在 StringBuilder 中创建数组一样。构造函数。此外,反汇编插件无法像在 JDK 8 中那样提供尽可能多的源代码引用。

我认为这是 OpenJDK 7 中更积极的优化传递的结果,或者更有可能是为某些 StringBuilder 插入手写低级代码的结果。操作。我不确定为什么这种优化不会在我的 JVM 8 实现中发生,或者为什么没有为 Integer#toString(int) 实现相同的优化在 JVM 7 中。我想熟悉 JRE 源代码相关部分的人必须回答这些问题......

最佳答案

TL;博士: append 中的副作用显然打破 StringConcat 优化。

原始问题和更新中的分析非常好!

为了完整起见,以下是一些缺失的步骤:

  • 看穿了-XX:+PrintInlining对于 7u55 和 8u5。在 7u55 中,您将看到如下内容:

     @ 16   org.sample.IntStr::inlineSideEffect (25 bytes)   force inline by CompilerOracle
    @ 4 java.lang.StringBuilder::<init> (7 bytes) inline (hot)
    @ 18 java.lang.StringBuilder::append (8 bytes) already compiled into a big method
    @ 21 java.lang.StringBuilder::toString (17 bytes) inline (hot)


    ...在 8u5 中:

     @ 16   org.sample.IntStr::inlineSideEffect (25 bytes)   force inline by CompilerOracle
    @ 4 java.lang.StringBuilder::<init> (7 bytes) inline (hot)
    @ 3 java.lang.AbstractStringBuilder::<init> (12 bytes) inline (hot)
    @ 1 java.lang.Object::<init> (1 bytes) inline (hot)
    @ 18 java.lang.StringBuilder::append (8 bytes) inline (hot)
    @ 2 java.lang.AbstractStringBuilder::append (62 bytes) already compiled into a big method
    @ 21 java.lang.StringBuilder::toString (17 bytes) inline (hot)
    @ 13 java.lang.String::<init> (62 bytes) inline (hot)
    @ 1 java.lang.Object::<init> (1 bytes) inline (hot)
    @ 55 java.util.Arrays::copyOfRange (63 bytes) inline (hot)
    @ 54 java.lang.Math::min (11 bytes) (intrinsic)
    @ 57 java.lang.System::arraycopy (0 bytes) (intrinsic)


    您可能会注意到 7u55 版本更浅,而且在 StringBuilder 之后似乎没有调用任何内容。方法——这是字符串优化生效的一个很好的迹象。事实上,如果你用 -XX:-OptimizeStringConcat 运行 7u55 ,子调用将重新出现,性能下降到 8u5 级别。
  • 好的,那么我们需要弄清楚为什么8u5没有做同样的优化。格雷普 http://hg.openjdk.java.net/jdk9/jdk9/hotspot让“StringBuilder”找出VM在哪里处理StringConcat优化;这会让你进入 src/share/vm/opto/stringopts.cpp
  • hg log src/share/vm/opto/stringopts.cpp找出那里的最新变化。其中一位候选人是:

    changeset:   5493:90abdd727e64
    user: iveresov
    date: Wed Oct 16 11:13:15 2013 -0700
    summary: 8009303: Tiered: incorrect results in VM tests stringconcat...
  • 在 OpenJDK 邮件列表上查找评论线程(很容易在谷歌上搜索变更集摘要):http://mail.openjdk.java.net/pipermail/hotspot-compiler-dev/2013-October/012084.html
  • 现货“字符串concat优化优化将模式[...]折叠成一个字符串的单个分配并直接形成结果。优化代码中可能发生的所有可能的deopts从头开始重新启动该模式(从StringBuffer分配开始) . 这意味着整个模式必须我没有副作用。 “ Eureka ?
  • 写出对比基准:

    @Fork(5)
    @Warmup(iterations = 5)
    @Measurement(iterations = 5)
    @BenchmarkMode(Mode.AverageTime)
    @OutputTimeUnit(TimeUnit.NANOSECONDS)
    @State(Scope.Benchmark)
    public class IntStr {
    private int counter;

    @GenerateMicroBenchmark
    public String inlineSideEffect() {
    return new StringBuilder().append(counter++).toString();
    }

    @GenerateMicroBenchmark
    public String spliceSideEffect() {
    int cnt = counter++;
    return new StringBuilder().append(cnt).toString();
    }
    }
  • 在 JDK 7u55 上测量它,看到内联/拼接副作用的相同性能:

    Benchmark                       Mode   Samples         Mean   Mean error    Units
    o.s.IntStr.inlineSideEffect avgt 25 65.460 1.747 ns/op
    o.s.IntStr.spliceSideEffect avgt 25 64.414 1.323 ns/op
  • 在 JDK 8u5 上测量,看到性能下降和内联效果:

    Benchmark                       Mode   Samples         Mean   Mean error    Units
    o.s.IntStr.inlineSideEffect avgt 25 84.953 2.274 ns/op
    o.s.IntStr.spliceSideEffect avgt 25 65.386 1.194 ns/op
  • 提交错误报告 ( https://bugs.openjdk.java.net/browse/JDK-8043677 ) 以与 VM 人员讨论此行为。原始修复的基本原理是坚如磐石,有趣的是,如果我们可以/应该在像这样的一些微不足道的情况下恢复这种优化。
  • ???
  • 利润。

  • 是的,我应该发布基准的结果,该基准将增量从 StringBuilder 移动链,在整个链之前做。此外,切换到平均时间和 ns/op。这是 JDK 7u55:

    Benchmark                      Mode   Samples         Mean   Mean error    Units
    o.s.IntStr.integerToString avgt 25 153.805 1.093 ns/op
    o.s.IntStr.stringBuilder0 avgt 25 128.284 6.797 ns/op
    o.s.IntStr.stringBuilder1 avgt 25 131.524 3.116 ns/op
    o.s.IntStr.stringBuilder2 avgt 25 254.384 9.204 ns/op
    o.s.IntStr.stringFormat avgt 25 2302.501 103.032 ns/op


    这是8u5:

    Benchmark                      Mode   Samples         Mean   Mean error    Units
    o.s.IntStr.integerToString avgt 25 153.032 3.295 ns/op
    o.s.IntStr.stringBuilder0 avgt 25 127.796 1.158 ns/op
    o.s.IntStr.stringBuilder1 avgt 25 131.585 1.137 ns/op
    o.s.IntStr.stringBuilder2 avgt 25 250.980 2.773 ns/op
    o.s.IntStr.stringFormat avgt 25 2123.706 25.105 ns/op

    stringFormat实际上在 8u5 中要快一些,所有其他测试都相同。这巩固了原始问题的主要罪魁祸首 SB 链中的副作用断裂的假设。

    关于java - 为什么 StringBuilder#append(int) 在 Java 7 中比在 Java 8 中更快?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23756966/

    27 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com