使用 JUnit 混淆 StringBuilder 与 StringBuffer 的基准测试结果

Confusing benchmarking results for StringBuilder vs StringBuffer with JUnit

我 运行 以下 JUnit 测试用例,并且能够连续获得 StringbufferStringBuilder 更好的性能结果。我确定我在这里遗漏了一些东西,但我找不到 StringBufferStringBuilder 速度更快的原因。

我的测试用例是,

    @Test
    public void stringTest(){

        String s1 = "s1";
        String s2 = "s2";

        for(int i=0;i<=100000;i++){
            s1 = s1+s2;
        }
        System.out.println(s1);
    }

    @Test
    public void stringBuilderTest(){

        StringBuilder s1 = new StringBuilder("s1");
        String s2 = "s2";

        for(int i=0;i<=100000;i++){
            s1.append(s2);
        }
        System.out.println(s1);
    }


    @Test
    public void stringBufferTest(){

        StringBuffer s1 = new StringBuffer("s1");
        String s2 = "s2";

        for(int i=0;i<=100000;i++){
            s1.append(s2);
        }
        System.out.println(s1);
    }

请查JUnit测试结果,

正如您在上面的结果中看到的那样,stringBufferTest 案例比 stringBuilderTest 案例执行得更快。我的问题是这是为什么?我知道这在理论上是不可能的,但我是如何得到这个结果的?


更新

根据@Henry 的评论,我删除了 SysOuts,结果发生了巨大变化。

然后我将循环计数增加了 100000 -> 1000000 并且能够得到一些我一直期望的真实结果,

我的新问题是,

  1. 为什么当我删除 系统输出?
  2. 当负载从低到高增加到 1000000 时,StringBuffer 给出 优于 StringBuilder 的最佳结果,这是为什么呢?

恐怕你的基准基本上是无效的。

微基准测试(代码很少,完成相对较快)在 Java(以及许多其他语言)中是出了名的难。使这些基准测试变得困难的一些问题:

  • 根据测试的编写方式,编译器可能会优化一些代码,导致基准测试无法衡量您想要衡量的内容。
  • 在 运行 时间(而不是编译时间)发生的优化通常是增量发生的。最初,代码被解释。只有当它的某些部分执行得足够频繁时,即时编译器才会生成优化的机器代码。微基准测试通常不会触发此优化。 http://www.oracle.com/technetwork/java/whitepaper-135217.html 有关于此主题的更多信息。
  • 即使代码符合优化条件,根据其编写方式,即时编译器也可能无法完全修复它,因此它必须回退到次优优化。搜索 "On stack replacement" 了解更多详情。

Oracle 的这篇文章详细介绍了这些问题:http://www.oracle.com/technetwork/articles/java/architect-benchmarking-2266277.html

最后,归结为:除非您对此非常有经验,否则不要从头开始编写微基准测试。您获得的结果与该代码在实际应用程序中的执行方式无关。

改用像 JMH (Java Microbenchmark Harness) 这样的框架。我上面链接的文章包含 JMH 的介绍,但也有其他教程(例如 http://java-performance.info/jmh/)。

花点时间学习使用 JMH。这很值得。当您 运行 使用 JMH 进行基准测试时,您会在其输出中看到由于 JVM 优化的发生(JMH 多次调用您的测试),基准时间随时间变化的幅度有多大。

如果您 运行 在 JMH 中测试您的 StringBuilder 与 StringBuffer,您应该会看到两者 类 在现代 CPU 上的表现几乎相同。 StringBuilder 稍快一些,但没那么快。尽管如此,我还是会使用 StringBuilder,因为它稍微快一些。