Java 教程

Java 控制语句

面向对象编程

Java 内置类

Java 文件处理

Java 错误和异常

Java 多线程

Java 同步

Java 网络

Java 集合

Java 接口

Java 数据结构

Java 集合算法

高级 Java

Java 杂项

Java API 和框架

Java 类参考

Java 有用资源

Java - 微基准测试



Java 基准测试

基准测试是一种检查应用程序或应用程序代码部分性能的技术,以吞吐量、平均花费时间等为指标。在Java 基准测试中,我们检查应用程序代码或正在使用的库的性能。此基准测试有助于在遇到大量负载时识别代码中的任何瓶颈,或识别应用程序性能下降的情况。

Java 基准测试的重要性

应用程序性能是任何应用程序的一个非常重要的属性。编写不当的代码会导致应用程序无响应,以及高内存使用率,这可能导致糟糕的用户体验,甚至使应用程序无法使用。为了解决此类问题,对应用程序进行基准测试至关重要。

Java 开发人员应该能够找到应用程序中的问题并使用基准测试技术修复它们,这可以帮助识别缓慢的代码。

Java 基准测试技术

开发人员为了基准测试应用程序代码、系统、库或任何组件,部署了多种技术。以下是其中一些重要技术。

使用开始/结束时间进行基准测试

此技术易于使用,并且适用于一小段代码,我们可以在调用代码之前以纳秒为单位获取开始时间,然后在执行方法后,我们再次获取时间。现在,使用结束时间和开始时间的差值可以了解代码花费了多少时间。此技术很简单,但不可靠,因为性能会因许多因素而异,例如正在运行的垃圾收集器以及在此期间运行的任何系统进程。

// get the start time
long startTime = System.nanoTime();
// execute the code to be benchmarked
long result = operations.timeTakingProcess();
// get the end time
long endTime = System.nanoTime();
// compute the time taken
long timeElapsed = endTime - startTime;

示例

以下示例显示了一个运行示例,以演示上述概念。

package com.tutorialspoint;

public class Operations {
   public static void main(String[] args) {
      Operations operations = new Operations();
      // get the start time
      long startTime = System.nanoTime();
      // execute the code to be benchmarked
      long result = operations.timeTakingProcess();
      // get the end time
      long endTime = System.nanoTime();
      // compute the time taken
      long timeElapsed = endTime - startTime;
      System.out.println("Sum of 100,00 natural numbers: " + result);
      System.out.println("Elapsed time: " + timeElapsed + " nanoseconds");
   }

   // get the sum of first 100,000 natural numbers
   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,用于检查代码的性能。它提供了一种简单的基于注释的方法来获取方法/的基准数据,开发人员只需编写很少的代码。

步骤 1 - 注释要进行基准测试的类/方法。

@Benchmark
public long timeTakingProcess() {
}

步骤 2 - 准备基准测试选项,并使用基准测试运行器运行。

// prepare the options
Options options = new OptionsBuilder()
	  .include(Operations.class.getSimpleName())  // use the class whose method is to be benchmarked
	  .forks(1)  // create the fork(s) which will be used to run the iterations
	  .build();

// run the benchmark runner
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();
   }

   // get the sum of first 100,000 natural numbers
   @Benchmark
   public long timeTakingProcess() {
      long sum = 0;
      for(int i = 0; i < 100000; i++ ) {
         sum += i;
      }
      return sum;
   }	
}

让我们编译并运行上述程序,这将产生以下结果 -

# JMH version: 1.35
# VM version: JDK 21.0.2, Java HotSpot(TM) 64-Bit Server VM, 21.0.2+13-LTS-58
# VM invoker: C:\Program Files\Java\jdk-21\bin\java.exe
# VM options: -Dfile.encoding=UTF-8 -Dstdout.encoding=UTF-8 -Dstderr.encoding=UTF-8 -XX:+ShowCodeDetailsInExceptionMessages
# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable)
# Warmup: 5 iterations, 10 s each
# Measurement: 5 iterations, 10 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Throughput, ops/time
# Benchmark: com.tutorialspoint.test.Operations.timeTakingProcess

# Run progress: 0.00% complete, ETA 00:01:40
# Fork: 1 of 1
# Warmup Iteration   1: 33922.775 ops/s
# Warmup Iteration   2: 34104.930 ops/s
# Warmup Iteration   3: 34519.419 ops/s
# Warmup Iteration   4: 34535.636 ops/s
# Warmup Iteration   5: 34508.781 ops/s
Iteration   1: 34497.252 ops/s
Iteration   2: 34338.847 ops/s
Iteration   3: 34355.355 ops/s
Iteration   4: 34105.801 ops/s
Iteration   5: 34104.127 ops/s


Result "com.tutorialspoint.test.Operations.timeTakingProcess":
  34280.276 ±(99.9%) 660.293 ops/s [Average]
  (min, avg, max) = (34104.127, 34280.276, 34497.252), stdev = 171.476
  CI (99.9%): [33619.984, 34940.569] (assumes normal distribution)


# Run complete. Total time: 00:01:40

REMEMBER: The numbers below are just data. To gain reusable insights, you need to follow up on
why the numbers are the way they are. Use profilers (see -prof, -lprof), design factorial
experiments, perform baseline and negative tests that provide experimental control, make sure
the benchmarking environment is safe on JVM/OS/HW level, ask for reviews from the domain experts.
Do not assume the numbers tell you what you want them to tell.

NOTE: Current JVM experimentally supports Compiler Blackholes, and they are in use. Please exercise
extra caution when trusting the results, look into the generated code to check the benchmark still
works, and factor in a small probability of new VM bugs. Additionally, while comparisons between
different JVMs are already problematic, the performance difference caused by different Blackhole
modes can be very significant. Please make sure you use the consistent Blackhole mode for comparisons.

Benchmark                      Mode  Cnt      Score     Error  Units
Operations.timeTakingProcess  thrpt    5  34280.276 ± 660.293  ops/s

广告