使用 JUnit 混淆 StringBuilder 与 StringBuffer 的基准测试结果
Confusing benchmarking results for StringBuilder vs StringBuffer with JUnit
我 运行 以下 JUnit 测试用例,并且能够连续获得 Stringbuffer
比 StringBuilder
更好的性能结果。我确定我在这里遗漏了一些东西,但我找不到 StringBuffer
比 StringBuilder
速度更快的原因。
我的测试用例是,
@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 并且能够得到一些我一直期望的真实结果,
我的新问题是,
- 为什么当我删除
系统输出?
- 当负载从低到高增加到 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,因为它稍微快一些。
我 运行 以下 JUnit 测试用例,并且能够连续获得 Stringbuffer
比 StringBuilder
更好的性能结果。我确定我在这里遗漏了一些东西,但我找不到 StringBuffer
比 StringBuilder
速度更快的原因。
我的测试用例是,
@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 并且能够得到一些我一直期望的真实结果,
我的新问题是,
- 为什么当我删除 系统输出?
- 当负载从低到高增加到 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,因为它稍微快一些。