java-使用jmh基准测试框架比较五种字符串拼接性能

java-使用jmh基准测试框架比较五种字符串拼接性能

引言

Java中提供了5种字符串拼接的方法,使用+拼接字符串是最长见的方法。除此还有StringBuilder、StringBuffer、MessageFormat、StringFormat

单纯从拼接执行时间上比较下五种方式的性能。

比较结果

先看下执行结果,其中的Score是执行耗时(微妙),Error可以看做是误差。

执行时间由短到长为:+ < StringBuilder < StringBuilder < MessageFormat < StringFormat

Benchmark                  Mode  Cnt     Score     Error  Units
JmhTest.testMessageFormat  avgt    5   722.346 ± 134.540  us/op
JmhTest.testStringBase     avgt    5     6.905 ±   2.604  us/op
JmhTest.testStringBuffer   avgt    5     8.291 ±   5.311  us/op
JmhTest.testStringBuilder  avgt    5     7.192 ±   3.861  us/op
JmhTest.testStringFormat   avgt    5  1273.906 ±  69.336  us/op

jmh基准测试

添加依赖

<!--基准测试-->
<dependency>
    <groupId>org.openjdk.jmh</groupId>
    <artifactId>jmh-core</artifactId>
    <version>1.35</version>
</dependency>
<dependency>
    <groupId>org.openjdk.jmh</groupId>
    <artifactId>jmh-generator-annprocess</artifactId>
    <version>1.35</version>
</dependency>

JMH注解说明

@BenchMarkMode 设置基准测试的模式 【方法或者类】

@OutPutTimeUnit 报告结果的默认时间单位【类、方法】

@Warmup 预热,设置具体的配置参数如次数,时间等

@Measurement 类似预热,但是设置的是测量时的

@Fork 整体测试几次

@State 设置配置对象的作用域,定义线程之间的共享程度

@Setup 线程执行前的配置函数、初始化

@TearDown 测试后处理操作 【方法】

@BenchMark 标记测试基准 【方法】

@OperationsPerInvocation 与基准进行多操作通信,运行JMH调整

  • @BenchMarkMode
    设置运行基准测试的模式,可以选择放在方法上面,只对该方法生效。

    Mode.Throughput : 吞吐量模式,获得单位时间的操作数量,连续运行@BenchMark的方法,计算所有的工作线程的总吞吐量。

    Mode.AverageTime: 平均时间模式, 获得每次操作的平均时间,计算所有工作线程的平均时间。

    Mode.SimpleTime: 时间采样模式, 对每一个操作函数的时间进行采样,连续运行@BenchMark的函数,随机抽取运行所需要的时间。

    Mode.SingleShotTime: 单次触发模式, 测试单次操作的时间,连续运行@BenchMark函数,只运行一次并计算时间: 该模式只是运行一次@BenchMark函数,所以需要预热, 如果基准数值小,使用SimpleTime模式采样。

    Mode.All : 无模式,采用所有的基准模式,效果最好。

编写测试代码

运行main方法启动测试,耐性等待测试完成即可。

import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;

import java.text.MessageFormat;
import java.util.concurrent.TimeUnit;

@BenchmarkMode(Mode.AverageTime) //基准测试的默认模式
@OutputTimeUnit(TimeUnit.MICROSECONDS) //时间单位:纳秒、微妙、毫秒、秒、分、时
@State(Scope.Thread)
@Fork(1) //进程数一般设置为1
//@Threads(1) //线程数
@Warmup(iterations = 2,time = 2) //预热迭代次数,time控制每次迭代的间隔时间(默认秒)
@Measurement(iterations = 5,time = 2) //测量迭代次数,time控制每次迭代的间隔时间(默认秒)
public class JmhTest {
    private int _loop = 1000;
    public static void main(String[] args) throws RunnerException {
        Options opt = new OptionsBuilder().include(JmhTest.class.getSimpleName()).build();
        new Runner(opt).run();
    }
    //StringBuffer
    @Benchmark //被测试的方法
    public void testStringBuffer() {
        for(int i = 0; i< _loop; i++){
            StringBuffer sbr = new StringBuffer();
            String s = sbr.append("1111").append(",")
                    .append("2222").append(",")
                    .append("3333").append(",")
                    .append("4444").toString();
        }
    }
    //StringBuilder
    @Benchmark //被测试的方法
    public void testStringBuilder() {
        for(int i = 0; i< _loop; i++){
            StringBuilder sbr = new StringBuilder();
            String s = sbr.append("1111").append(",")
                    .append("2222").append(",")
                    .append("3333").append(",")
                    .append("4444").toString();
        }
    }
    //MessageFormat
    @Benchmark
    public void testMessageFormat() {
        for(int i = 0; i< _loop; i++){
            String s = MessageFormat.format("{0},{1},{2},{3}","1111","2222","3333","4444");
        }
    }
    //String.format
    @Benchmark
    public void testStringFormat() {
        for(int i = 0; i< _loop; i++){
            String s = String.format("%s,%s,%s,%s","1111","2222","3333","4444");
        }
    }
    //字符串拼接
    @Benchmark
    public void testStringBase() {
        for(int i = 0; i< _loop; i++){
            String s = "1111";
            s+=",2222";
            s+=",3333";
            s+=",4444";
        }
    }

}

测试结果说明

以上真对5个函数进行基准测试,测试的配置是每个函数预热执行1次,对之后的5次进行平均执行时间统计。

真对使用Mode.AverageTime模式测试,每个函数执行完成后的输出为:

Result "com.cnpc.epai.researchdata.data.service.JmhTest.testStringFormat":
  1254.609 ±(99.9%) 174.543 us/op [Average]
  (min, avg, max) = (1174.821, 1254.609, 1282.413), stdev = 45.328
  CI (99.9%): [1080.066, 1429.151] (assumes normal distribution)

最终的执行结果是:

Benchmark                  Mode  Cnt     Score     Error  Units
JmhTest.testMessageFormat  avgt    5   722.346 ± 134.540  us/op
JmhTest.testStringBase     avgt    5     6.905 ±   2.604  us/op
JmhTest.testStringBuffer   avgt    5     8.291 ±   5.311  us/op
JmhTest.testStringBuilder  avgt    5     7.192 ±   3.861  us/op
JmhTest.testStringFormat   avgt    5  1273.906 ±  69.336  us/op

其中Score为执行耗时(微妙)Error为误差;

结论:

1.使用StringBuilder的方式是效率最高的。
2.如果不是在循环体中进行字符串拼接的话,直接使用+就好了。
3.如果在并发场景中进行字符串拼接的话,要使用StringBuffer来代替StringBuilder。

猜你喜欢

转载自blog.csdn.net/xxj_jing/article/details/129680765