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

廣告