当前位置: 首页 > 科技观察

JMH

时间:2023-03-12 03:14:14 科技观察

iffastorswitchfast是性能调优的必备工具吗?是否应该指定HashMap的初始化大小,指定后性能能提升多少?各种序列化方法中哪种方法花费的时间更少?无论出于何种原因进行绩效评估,量化指标始终是必要的。在大多数情况下,简单地回答谁快谁慢是不够的。如何量化程序性能?这就需要我们的主角JMH出场了!JMH简介JMH(JavaMicrobenchmarkHarness)是一个代码微基准测试工具套件,主要基于方法级基准测试,精度可达纳秒级。这个工具是由Oracle内部JIT实现背后的人编写的,他们应该比任何人都更了解JIT和JVM对基准测试的影响。当你定位到一个热点方法,想进一步优化该方法的性能时,可以使用JMH对优化结果进行定量分析。JMH的典型应用场景如下:想知道一个方法到底执行了多长时间,以及执行时间和输入的相关性比较给定条件下不同接口实现的吞吐量查看请求在多少中完成的百分比好久我们以字符串拼接的两种方式为例,使用JMH做benchmark测试。添加依赖,因为JMH是JDK9自带的。如果是JDK9之前的版本,需要添加如下依赖(目前JMH最新版本为1.23):org.openjdk.jmhjmh-core1.23org.openjdk.jmhjmh-generator-annprocess1.23写一个benchmark测试接下来创建一个JMH测试类,判断+和StringBuilder.append()哪个字符串拼接耗时少。具体代码如下:AverageTime)@Warmup(iterations=3,time=1)@Measurement(iterations=5,time=5)@Threads(4)@Fork(1)@State(value=Scope.Benchmark)@OutputTimeUnit(TimeUnit.NANOSECONDS)publicclassStringConnectTest{@Param(value={"10","50","100"})privateintlength;@BenchmarkpublicvoidtestStringAdd(Blackholeblackhole){Stringa="";for(inti=0;ihttp://hg.openjdk.java.net/code-tools/jmh/file/tip/jmh-samples/src/main/java/org/openjdk/jmh/samples/执行基准测试的准备工作已经完成,接下来运行代码,稍等片刻,测试结果就出来了,这里简单介绍一下结果说明:#JMHversion:1.23#VMversion:JDK1.8.0_201,JavaHotSpot(TM)64-BitServerVM,25.201-b09#VMinvoker:D:\Software\Java\jdk1.8.0_201\jre\bin\java.exe#VMoptions:-javaagent:D:\Software\JetBrains\IntelliJIDEA2019.1.3\lib\idea_rt.jar=61018:D:\Software\JetBrains\IntelliJIDEA2019.1.3\bin-Dfile.encoding=UTF-8#Warmup:3iterations,1seach#Measurement:5iterations,5seach#Timeout:10minperiteration#Threads:4threads,willsynchronizeiterations#Benchmarkmode:Averagetime,time/op#Benchmark:com.wupx.jmh.StringConnectTest。testStringBuilderAdd#Parameters:(length=100)这部分是测试的基本信息,比如使用的Java路径,预热代码的迭代次数,测量代码的迭代次数,使用的线程数,测试的统计单位等。#WarmupIteration1:1083.569±(99.9%)393.884ns/op#WarmupIteration2:864.685±(99.9%)174.120ns/op#WarmupIteration3:798.310±(99.9%)121.161ns/op这部分是每次warmup中的性能指标,热身测试不作为最终统计结果。预热的目的是让JVM对被测代码进行足够的优化。例如,在预热之后,被测代码应该进行完全的JIT编译和优化。Iteration1:810.667±(99.9%)51.505ns/opIteration2:807.861±(99.9%)13.163ns/opIteration3:851.421±(99.9%)33.564ns/opIteration4:805.675±(99.9%)33.038ns/opIteration5:821.92%33.038ns/opIteration5:821.92%36ns/opResult"com.wupx.jmh.StringConnectTest.testStringBuilderAdd":819.329±(99.9%)72.698ns/op[平均](min,avg,max)=(805.675,819.329,851.421),stdev=18.879CI(99.9%):[746.631,892.027](assumesnormaldistribution)Benchmark(length)ModeCntScoreErrorUnitsStringConnectTest.testStringBuilderAdd100avgt5819.329±72.698ns/op这部分展示了测量迭代的情况,每一次迭代展示了当前的执行速率,即一次运行时间花费。5次迭代后,进行统计。本例中,当长度为100时,testStringBuilderAdd方法的平均执行时间为819.329ns,误差为72.698ns。最后的测试结果如下所示:Benchmark(length)ModeCntScoreErrorUnitsStringConnectTest.testStringAdd10avgt5161.496±17.097ns/opStringConnectTest.testStringAdd50avgt51854.657±227.902ns/opStringConnectTest.testStringAdd100avgt56490.062±327.626ns/opStringConnectTest.testStringBuilderAdd10avgt568.769±4.460ns/opStringConnectTest.testStringBuilderAdd50avgt5413.021±30.950ns/opStringConnectTest.testStringBuilderAdd100avgt5819.329±72.698ns/op的结果表明,StringBuilder.append()的性能在拼接字符数越多时越好。生成jar包执行对于一些小测试,只要按照上面的方式写一个main函数,手动执行即可。对于大规模测试,测试时间比较长,线程数比较多。除了测试对服务器的要求外,一般在Linux服务器上执行。JMH官方提供了生成jar包执行的方法。我们需要在maven中添加一个插件。具体配置如下:org.apache.maven.pluginsmaven-shade-plugin2.4.1packageshadejmh-demoorg.openjdk.jmh.Main/transformers>然后执行maven命令生成可执行jar包执行:mvncleaninstalljava-jartarget/jmh-demo.jarStringConnectTestJMH基础为了更好的使用JMH的各种功能,下面解释JMH的基本概念:@BenchmarkMode使用配置模式选项,可用于类或方法。这个注解的值是一个数组,可以将几种Mode组合起来执行,例如:@BenchmarkMode({Mode.SampleTime,Mode.AverageTime}),也可以设置为Mode.All,即全部执行一次Throughput:总体吞吐量,每秒执行多少次调用,单位为ops/timeAverageTime:平均时间spent,每次操作的平均时间,单位为time/opSampleTime:随机采样,最终输出采样结果的分布SingleShotTime:只运行一次,经常同时设置Warmups的个数为0,用于测试冷启动All的表现:以上@State的All模式执行一次@State可以通过State指定一个对象的作用域,JMH根据作用域进行实例化和共享操作。@State可以被继承和使用。如果父类定义了注解,则子类不需要定义。由于JMH允许多个线程同时执行测试,所以不同选项的含义如下:Scope.Benchmark:所有测试线程共享一个实例,测试多线程共享下有状态实例的性能Scope.Group:该同一个线程在同一个组中共享InstanceScope.Thread:默认状态,每个测试线程分配一个实例@OutputTimeUnit作为统计结果的时间单位,可以用于类或者方法注解@Warmup的一些基本测试参数需要配置预热可用于类或方法。一般前几次程序测试会比较慢,所以让程序进行几轮预热,以保证测试的准确性。参数如下:iterations:预热次数time:每次预热的时间timeUnit:时间单位,默认秒batchSize:批量大小,每次操作调用多少次Why你需要热身吗?由于JVM的JIT机制,如果一个函数被多次调用,JVM会尝试将其编译成机器码来提高执行速度,所以为了让benchmark结果更接近真实情况,需要被热身。@Measurement一些基本的测试参数需要配置才能真正调用方法,可以用在类或者方法上,参数和@Warmup一样。@Threads每个进程中的测试线程,可以用在类或方法上。@Forkfork次数,可以用在类或方法上。如果fork数为2,JMH会fork两个进程进行测试。@Param指定了某个参数的多种情况,特别适用于测试一个函数在不同参数输入条件下的性能。它只能用于字段。要使用此注释,您必须定义@State注释。介绍完常用的注解,再看看JMH都有哪些坑。JMH陷阱使用JMH时需要避免一些陷阱。比如JIT优化中的死代码剔除,比如下面的代码:@BenchmarkpublicvoidtestStringAdd(Blackholeblackhole){Stringa="";for(inti=0;iSettings...->Plugins,然后搜索jmh,选择安装JMH插件:JMHplugin这个插件可以让我们像JUnit一样使用JMH,主要功能如下:使用@Benchmark自动生成方法和JUnit一样,运行单个Benchmark方法运行类中的所有Benchmark方法例如,您可以右键单击Generate...并选择GenerateJMHbenchmark可以生成一个带有@Benchmark的方法。还有一个Benchmark方法,它通过将光标移动到方法声明并调用Run操作来运行。将光标移动到类名所在行,右键Run运行,该类下所有带有@Benchmark注解的方法都会执行。JMHvisualization此外,如果你想以图表的形式可视化测试结果,你可以试试这些网站:JMHVisualChart:http://deepoove.com/jmh-visual-chart/JMHVisualizer:https://jmh.morethan.io/例如通过导入上述测试示例结果的json文件,可以实现可视化:总结本文主要介绍性能基准测试工具JMH,可以避免在JVM中通过JIT或其他优化进行性能测试通过一些功能影响。只需用@Benchmark注解标注需要测试的业务逻辑,JMH的注解处理器就可以自动生成真正的性能测试代码和对应的性能测试配置文件。