Java - 微基准测试:初学者指南

你好,未来的Java巫师们!? 今天,我们将开始一段激动人心的旅程,进入Java微基准测试的世界。如果你以前从未写过一行代码,别担心——我们会从最基础的知识开始,然后一起逐步深入。所以,拿一杯咖啡(或者茶,如果你喜欢的话),让我们开始吧!

Java - Microbenchmark

什么是微基准测试?

在我们深入了解Java微基准测试的细节之前,先来了解一下微基准测试究竟是什么。

想象你是一位试图完善食谱的厨师。你不会只尝一下最后的菜肴来判断它是否美味,对吧?你会品尝每一种原料,测试不同的烹饪时间,尝试各种技巧。微基准测试在编程中就是这样——它是一种测量代码中小的、孤立部分的性能的方法。

为什么Java基准测试很重要?

现在,你可能会想,“我为什么应该关心基准测试?”好吧,让我给你讲一个小故事。

当我还是一个初级开发者的时候,我曾经编写了一个在我的电脑上运行得非常完美的程序。但是当我们把它部署到公司的服务器上时,它慢得像一只背着沉重背包的乌龟!那时我意识到了基准测试的重要性。它帮助我们:

  1. 识别性能瓶颈
  2. 比较不同的实现
  3. 确保我们的代码在不同的系统上高效运行

Java基准测试技术

让我们看看一些常见的Java基准测试技术:

1. 手动计时

最简单的基准测试方法是手动计时。以下是一个基本示例:

public class SimpleTimingExample {
public static void main(String[] args) {
long startTime = System.nanoTime();

// 在这里放置你的代码
for (int i = 0; i < 1000000; i++) {
Math.sqrt(i);
}

long endTime = System.nanoTime();
long duration = (endTime - startTime);
System.out.println("执行时间: " + duration + " 纳秒");
}
}

在这个例子中,我们使用System.nanoTime()来测量计算0到999,999的数字的平方根需要多长时间。

2. 使用JMH (Java微基准测试工具)

虽然手动计时很简单,但并不总是准确。这时JMH就派上用场了。JMH是用于构建、运行和分析纳秒/微秒/毫秒/宏观基准测试的Java工具。

要使用JMH,你需要将它添加到你的项目中。如果你使用Maven,将这些依赖项添加到你的pom.xml

<dependencies>
<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>
</dependencies>

现在,让我们编写一个简单的JMH基准测试:

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.util.concurrent.TimeUnit;

@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(Scope.Thread)
@Fork(value = 2, jvmArgs = {"-Xms2G", "-Xmx2G"})
@Warmup(iterations = 3)
@Measurement(iterations = 3)
public class JMHExample {

@Benchmark
public void benchmarkMathSqrt() {
Math.sqrt(143);
}

public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder()
.include(JMHExample.class.getSimpleName())
.forks(1)
.build();

new Runner(opt).run();
}
}

这个基准测试测量计算143的平方根的平均时间。让我们解释一下这些注释:

  • @BenchmarkMode:指定要测量什么(在这个例子中是平均时间)
  • @OutputTimeUnit:指定结果单位
  • @State:定义“状态”对象将在其中共享的作用域
  • @Fork:单个基准测试要复制多少次
  • @Warmup@Measurement:定义要进行多少次预热和测量迭代

Java集合算法

在我们讨论基准测试的同时,让我们快速了解一下Java集合算法。这些是非常有用的工具,可以显著影响程序的性能。

以下是一些常见算法的表格:

算法 描述 使用场景
Collections.sort() 对列表进行排序 当你需要对元素进行排序时
Collections.binarySearch() 在已排序的列表中搜索 在大型、已排序的列表中查找元素
Collections.reverse() 反转列表 当你需要反转元素的顺序时
Collections.shuffle() 随机排列列表 随机化元素的顺序
Collections.fill() 用指定元素替换所有元素 用特定值初始化列表

让我们对使用Collections.sort()进行列表排序的性能进行基准测试:

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.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.concurrent.TimeUnit;

@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@State(Scope.Thread)
@Fork(value = 2, jvmArgs = {"-Xms2G", "-Xmx2G"})
@Warmup(iterations = 3)
@Measurement(iterations = 3)
public class SortingBenchmark {

@Param({"100", "1000", "10000"})
private int listSize;

private List<Integer> list;

@Setup
public void setup() {
list = new ArrayList<>(listSize);
Random rand = new Random();
for (int i = 0; i < listSize; i++) {
list.add(rand.nextInt());
}
}

@Benchmark
public void benchmarkCollectionsSort() {
Collections.sort(list);
}

public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder()
.include(SortingBenchmark.class.getSimpleName())
.forks(1)
.build();

new Runner(opt).run();
}
}

这个基准测试测量对不同大小的列表(100、1000和10000个元素)进行排序所需的时间。运行这个测试将给你一个关于排序时间随列表大小增加的好主意。

结论

好了,各位!我们只是简单介绍了Java微基准测试。请记住,基准测试不仅仅是编写快速的代码——它是关于理解你的代码的性能特征并做出明智的决定。

在你继续Java之旅的过程中,请将基准测试作为你的工具箱中的一部分。它就像一个可靠的指南针,将帮助你在这个有时波涛汹涌的软件性能海洋中导航。

编程愉快,愿你的基准测试总是富有洞察力!??‍??‍?

Credits: Image by storyset