基准测试是一种检查应用程序或应用程序代码段性能的技术,包括吞吐量、平均所需时间等指标。在 Java 基准测试中,我们会检查正在使用的应用程序代码或库的性能。这种基准测试有助于识别在高负载情况下代码的瓶颈或应用程序性能下降的问题。
Java 基准测试的重要性
应用程序性能是任何应用程序的一个非常重要的属性。编写不良的代码可能导致应用程序无响应,并且高内存使用会导致糟糕的用户体验,甚至使应用程序无法使用。为了控制这些问题,对应用程序进行基准测试是非常重要的。
Java 开发者应该能够使用基准测试技术找到应用程序中的问题并修复它们,这可以帮助识别缓慢的代码。
Java 基准测试技术
开发者可以采用多种技术来对应用程序代码、系统、库或任何组件进行基准测试。以下是一些重要的技术。
使用起始/结束时间进行基准测试
此技术简单并且适用于小范围的代码片段,在调用一段代码之前获取当前时间(纳秒),并在方法执行后再次获取时间。现在,通过结束时间和开始时间的差值可以得出代码所花费的时间。这种方法简单但不可靠,因为性能会根据许多因素而变化,例如垃圾回收器的操作以及在该期间运行的任何系统进程。
long startTime = System.nanoTime();
long result = operations.timeTakingProcess();
long endTime = System.nanoTime();
long timeElapsed = endTime - startTime;
示例
下面的例子展示了一个运行示例来演示上述概念。
package com.tutorialspoint;
public class Operations {
public static void main(String[] args) {
Operations operations = new Operations();
long startTime = System.nanoTime();
long result = operations.timeTakingProcess();
long endTime = System.nanoTime();
long timeElapsed = endTime - startTime;
System.out.println("Sum of 100,00 natural numbers: " + result);
System.out.println("Elapsed time: " + timeElapsed + " nanoseconds");
}
public long timeTakingProcess() {
long sum = 0;
for (int i = 0; i < 100000; i++) {
sum += i;
}
return sum;
}
}
编译并运行上述程序,将会得到如下结果:
Sum of 100,00 natural numbers: 4999950000
Elapsed time: 1111300 nanoseconds
使用 Java 微基准测试工具包 (JMH) 进行基准测试
Java 微基准测试工具包 (JMH) 是由 OpenJDK 社区开发的一个强大的基准测试 API,用于检查代码的性能。它提供了一种简单的基于注解的方法来获取方法/类的基准测试数据,所需的编码工作量很小。
第一步 - 注解要基准测试的类/方法
@Benchmark
public long timeTakingProcess() {
}
第二步 - 准备基准测试选项并通过基准测试运行器运行
Options options = new OptionsBuilder()
.include(Operations.class.getSimpleName()) // 使用要基准测试其方法的类
.forks(1) // 创建将用于运行迭代的分支
.build();
new Runner(options).run();
为了使用基准测试库,我们需要在 Maven 项目的 pom.xml 文件中添加以下依赖项。
<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>
以下是完整的 pom.xml 文件,用于运行上述基准测试示例。
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.tutorialspoint</groupId>
<artifactId>test</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>test</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<jmh.version>1.35</jmh.version>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-core</artifactId>
<version>${jmh.version}</version>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-generator-annprocess</artifactId>
<version>${jmh.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>17</source>
<target>17</target>
<annotationProcessorPaths>
<path>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-generator-annprocess</artifactId>
<version>${jmh.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.0</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<finalName>benchmarks</finalName>
<transformers>
<transformer
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>org.openjdk.jmh.Main</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
示例
以下是用于运行上述基准测试示例的完整类代码。
package com.tutorialspoint.test;
import org.openjdk.jmh.annotations.Benchmark;
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;
public class Operations {
public static void main(String[] args) throws RunnerException {
Options options = new OptionsBuilder()
.include(Operations.class.getSimpleName())
.forks(1)
.build();
new Runner(options).run();
}
@Benchmark
public long timeTakingProcess() {
long sum = 0;
for (int i = 0; i < 100000; i++) {
sum += i;
}
return sum;
}
}
编译并运行上述程序,将会得到如下结果:
迭代 1: 34497.252 ops/s
迭代 2: 34338.847 ops/s
迭代 3: 34355.355 ops/s
迭代 4: 34105.801 ops/s
迭代 5: 34104.127 ops/s
结果 "com.tutorialspoint.test.Operations.timeTakingProcess":
34280.276 ±(99.9%) 660.293 ops/s [平均]
(最小, 平均, 最大) = (34104.127, 34280.276, 34497.252), 标准偏差 = 171.476
置信区间 (99.9%): [33619.984, 34940.569] (假设正态分布)
记住: 下面的数字只是数据。要获得可重复利用的见解,你需要跟进
为什么数字是这样的。使用分析器 (见 -prof, -lprof),设计因子实验,
执行基线和负面测试以提供实验控制,确保
在 JVM/操作系统/硬件级别的基准测试环境是安全的,寻求领域专家的评审。
不要假设这些数字告诉了你想知道的信息。
注意: 当前的 JVM 实验性支持编译器黑洞,并且正在使用。请在信任结果时
格外小心,查看生成的代码以检查基准测试是否仍然有效,并考虑
新的 VM 错误的小概率。另外,虽然在不同 JVM 之间的比较已经存在问题,
由于不同的黑洞模式导致的性能差异可能非常显著。请确保你在比较时使用一致的黑洞模式。
Benchmark Mode Cnt Score Error Units
Operations.timeTakingProcess thrpt 5 34280.276 ± 660.293 ops/s