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 + " ナノ秒");
}
}

この例では、0から999,999までの数の平方根を計算する時間をSystem.nanoTime()を使用して測定しています。

2. JMH (Java Microbenchmark Harness)を使用する

手動タイミングは簡単ですが、必ずしも正確ではありません。そんなときに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の要素)のリストを並べ替える時間を測定します。これを実行することで、リストのサイズが増えると並べ替え時間がどのように増加するかを理解するのに役立ちます。

結論

それでは、皆さん!我々はJust Javaのマイクロベンチマークの表面を刈り込んだのです。ベンチマークはただ高速なコードを書くことではなく、あなたのコードのパフォーマンス特性を理解し、情報に基づいた決定を行うことができることを忘れないでください。

あなたがJavaの旅を続ける中で、ベンチマークをツールボックスに入れておきましょう。それは、ソフトウェアパフォーマンスの時々激しい海を航海するのに役立つ信頼できる羅針盤です。

幸せなコーディング、そしてあなたのベンチマークが常に有益であることを!??‍??‍?

Credits: Image by storyset