覚えたら書く

IT関係のデベロッパとして日々覚えたことを書き残したいです。 twitter: @yyoshikaw

JMH - 実行条件をアノテーションで記述する

本ブログ内で、JMHを使ってベンチマークの取得をいくつか実施してきましたが、実行条件は(mainメソッドの中で)OptionsBuilderのメソッドを実行して指定してきました。

JMHには、実行条件の指定をするためのアノテーションも用意されています。
それらアノテーションを使ったコードサンプルを以下に記載します。

そのために用意されているのが以下のようなアノテーションになります

  • @Warmup - 計測の前に実行されるウォームアップのイテレーション回数などを指定します
  • @Measurement - 計測のイテレーション回数などを指定します
  • @BenchMarkMode - 計測モードを指定します
  • @Fork - fork数を指定します
  • @OutputTimeUnit - 計測結果を出力する際の時間の単位を指定します
  • @Threads - スレッド数を指定します
  • @Timeout - 計測のタイムアウト値を指定します


実行条件の指定にアノテーションを使わない場合

計測の実行条件の指定にアノテーションを使わない場合、以下のようなコードを書くことで実現できます
計測を実行する側(main)で、OptionsBuilderの各種メソッドを呼び出して実行条件を指定します

本例での実行条件は以下となっています

  • ウォームアップのイテレーション=15回
  • frok数=1
  • 計測モード=Throughput
  • 出力の時間の単位=ミリ秒

■ベンチマーク取得対象のクラス

import org.openjdk.jmh.annotations.Benchmark;

public class ExampleBenchmark1 {

    @Benchmark
    public void date() {
        java.util.Date d = new java.util.Date();
    }

    @Benchmark
    public void calndar() {
        java.util.Calendar c = java.util.Calendar.getInstance();
    }

}

■計測実行用の処理

import java.util.concurrent.TimeUnit;

import org.openjdk.jmh.annotations.Mode;
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 BenchmarkLauncher1 {

    public static void main(String[] args) throws RunnerException {
        Options opt = new OptionsBuilder()
                 .include(ExampleBenchmark1.class.getName())
                .warmupIterations(15)
                .forks(1)
                .mode(Mode.Throughput)
                .timeUnit(TimeUnit.MILLISECONDS)
                .build();
        new Runner(opt).run();
    }
}

■実行結果

(一部省略)

# Warmup: 5 iterations, 1 s each
# Measurement: 20 iterations, 1 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Throughput, ops/time
# Benchmark: org.jmh.sample.ExampleBenchmark1.calndar

# Run progress: 0.00% complete, ETA 00:00:50
# Fork: 1 of 1
# Warmup Iteration   1: 861.749 ops/ms
# Warmup Iteration   2: 1862.797 ops/ms
# Warmup Iteration   3: 2086.060 ops/ms
# Warmup Iteration   4: 2234.196 ops/ms
# Warmup Iteration   5: 2243.222 ops/ms
Iteration   1: 2204.222 ops/ms
Iteration   2: 2244.006 ops/ms
Iteration   3: 2250.402 ops/ms
Iteration   4: 2195.908 ops/ms
Iteration   5: 2213.133 ops/ms
Iteration   6: 2208.291 ops/ms
Iteration   7: 2254.084 ops/ms
Iteration   8: 2203.429 ops/ms
Iteration   9: 2172.140 ops/ms
Iteration  10: 2217.819 ops/ms
Iteration  11: 2231.414 ops/ms
Iteration  12: 2209.062 ops/ms
Iteration  13: 2214.501 ops/ms
Iteration  14: 2171.192 ops/ms
Iteration  15: 2194.317 ops/ms
Iteration  16: 2238.315 ops/ms
Iteration  17: 2223.204 ops/ms
Iteration  18: 2210.162 ops/ms
Iteration  19: 2206.248 ops/ms
Iteration  20: 2226.900 ops/ms


Result "calndar":
  2214.437 ±(99.9%) 19.548 ops/ms [Average]
  (min, avg, max) = (2171.192, 2214.437, 2254.084), stdev = 22.511
  CI (99.9%): [2194.890, 2233.985] (assumes normal distribution)


# Warmup: 5 iterations, 1 s each
# Measurement: 20 iterations, 1 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Throughput, ops/time
# Benchmark: org.jmh.sample.ExampleBenchmark1.date

# Run progress: 50.00% complete, ETA 00:00:26
# Fork: 1 of 1
# Warmup Iteration   1: 35249.245 ops/ms
# Warmup Iteration   2: 39838.538 ops/ms
# Warmup Iteration   3: 43733.803 ops/ms
# Warmup Iteration   4: 43663.578 ops/ms
# Warmup Iteration   5: 43655.036 ops/ms
Iteration   1: 43993.768 ops/ms
Iteration   2: 43269.545 ops/ms
Iteration   3: 43614.727 ops/ms
Iteration   4: 43441.403 ops/ms
Iteration   5: 43472.141 ops/ms
Iteration   6: 43363.339 ops/ms
Iteration   7: 44011.688 ops/ms
Iteration   8: 43423.339 ops/ms
Iteration   9: 43427.006 ops/ms
Iteration  10: 43973.837 ops/ms
Iteration  11: 43350.491 ops/ms
Iteration  12: 43293.717 ops/ms
Iteration  13: 43886.610 ops/ms
Iteration  14: 43485.981 ops/ms
Iteration  15: 42867.314 ops/ms
Iteration  16: 43501.033 ops/ms
Iteration  17: 43125.571 ops/ms
Iteration  18: 43069.324 ops/ms
Iteration  19: 43349.363 ops/ms
Iteration  20: 43822.362 ops/ms


Result "date":
  43487.128 ±(99.9%) 274.190 ops/ms [Average]
  (min, avg, max) = (42867.314, 43487.128, 44011.688), stdev = 315.757
  CI (99.9%): [43212.938, 43761.318] (assumes normal distribution)


# Run complete. Total time: 00:00:52

Benchmark                   Mode  Cnt      Score     Error   Units
ExampleBenchmark1.calndar  thrpt   20   2214.437 ±  19.548  ops/ms
ExampleBenchmark1.date     thrpt   20  43487.128 ± 274.190  ops/ms


実行条件の指定にアノテーションを使う場合

計測の実行条件の指定にアノテーションを使う場合、以下のようなコードを書くことで実現できます
計測を実行する側(main)ではなく、ベンチマーク取得対象のクラス側で実行条件を記述することになります。

ここでは、「実行条件の指定にアノテーションを使わない場合」のサンプルをアノテーションを使用したコードに置き換えてみています。

■ベンチマーク取得対象のクラス

ベンチマーク取得対象のクラスに実行条件の各種アノテーションが付与されていることが分かります

import java.util.concurrent.TimeUnit;

import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Warmup;

@Warmup(iterations=15)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@BenchmarkMode(Mode.Throughput)
@Fork(1)
public class ExampleBenchmark2 {

    @Benchmark
    public void date() {
        java.util.Date d = new java.util.Date();
    }

    @Benchmark
    public void calndar() {
        java.util.Calendar c = java.util.Calendar.getInstance();
    }
}

■計測実行用の処理

どのクラスを対象にするかという内容以外の実行条件の指定のコードが削除されていることが分かります

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 BenchmarkLauncher2 {

    public static void main(String[] args) throws RunnerException {
        Options opt = new OptionsBuilder()
                 .include(ExampleBenchmark2.class.getName())
                .build();
        new Runner(opt).run();
    }

}

■実行結果

(一部省略)

# Warmup: 5 iterations, 1 s each
# Measurement: 20 iterations, 1 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Throughput, ops/time
# Benchmark: org.jmh.sample.ExampleBenchmark2.calndar

# Run progress: 0.00% complete, ETA 00:00:50
# Fork: 1 of 1
# Warmup Iteration   1: 830.180 ops/ms
# Warmup Iteration   2: 1813.431 ops/ms
# Warmup Iteration   3: 2099.242 ops/ms
# Warmup Iteration   4: 2243.294 ops/ms
# Warmup Iteration   5: 2210.313 ops/ms
Iteration   1: 2244.349 ops/ms
Iteration   2: 2221.076 ops/ms
Iteration   3: 2242.440 ops/ms
Iteration   4: 2197.051 ops/ms
Iteration   5: 2203.198 ops/ms
Iteration   6: 2181.174 ops/ms
Iteration   7: 2214.532 ops/ms
Iteration   8: 2061.776 ops/ms
Iteration   9: 2214.944 ops/ms
Iteration  10: 2161.181 ops/ms
Iteration  11: 1733.958 ops/ms
Iteration  12: 2105.287 ops/ms
Iteration  13: 2209.597 ops/ms
Iteration  14: 2204.175 ops/ms
Iteration  15: 2170.124 ops/ms
Iteration  16: 2239.582 ops/ms
Iteration  17: 2195.874 ops/ms
Iteration  18: 2167.110 ops/ms
Iteration  19: 2172.308 ops/ms
Iteration  20: 2121.897 ops/ms


Result "calndar":
  2163.082 ±(99.9%) 96.588 ops/ms [Average]
  (min, avg, max) = (1733.958, 2163.082, 2244.349), stdev = 111.231
  CI (99.9%): [2066.494, 2259.670] (assumes normal distribution)


# Warmup: 5 iterations, 1 s each
# Measurement: 20 iterations, 1 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Throughput, ops/time
# Benchmark: org.jmh.sample.ExampleBenchmark2.date

# Run progress: 50.00% complete, ETA 00:00:26
# Fork: 1 of 1
# Warmup Iteration   1: 32257.483 ops/ms
# Warmup Iteration   2: 36190.540 ops/ms
# Warmup Iteration   3: 42043.936 ops/ms
# Warmup Iteration   4: 42342.327 ops/ms
# Warmup Iteration   5: 40960.967 ops/ms
Iteration   1: 40321.739 ops/ms
Iteration   2: 41924.838 ops/ms
Iteration   3: 42757.773 ops/ms
Iteration   4: 43310.839 ops/ms
Iteration   5: 43586.979 ops/ms
Iteration   6: 43188.242 ops/ms
Iteration   7: 42846.474 ops/ms
Iteration   8: 42704.245 ops/ms
Iteration   9: 43567.226 ops/ms
Iteration  10: 43095.412 ops/ms
Iteration  11: 42914.787 ops/ms
Iteration  12: 43407.346 ops/ms
Iteration  13: 43654.773 ops/ms
Iteration  14: 43706.141 ops/ms
Iteration  15: 42202.706 ops/ms
Iteration  16: 41867.707 ops/ms
Iteration  17: 43452.996 ops/ms
Iteration  18: 42754.032 ops/ms
Iteration  19: 42233.397 ops/ms
Iteration  20: 42209.165 ops/ms


Result "date":
  42785.341 ±(99.9%) 715.005 ops/ms [Average]
  (min, avg, max) = (40321.739, 42785.341, 43706.141), stdev = 823.400
  CI (99.9%): [42070.336, 43500.345] (assumes normal distribution)


# Run complete. Total time: 00:00:52

Benchmark                   Mode  Cnt      Score     Error   Units
ExampleBenchmark2.calndar  thrpt   20   2163.082 ±  96.588  ops/ms
ExampleBenchmark2.date     thrpt   20  42785.341 ± 715.005  ops/ms

先の例(アノテーションを使わないで実行条件を指定した場合)と同じ内容での計測が行われていることが分かります


ベンチマーク対象のメソッドにアノテーションを付与

アノテーションで実行条件を指定する場合、ベンチ―取得のクラスだけではなくメソッドにも付与して条件を指定するとができます

■ベンチマーク取得対象のクラス

クラスに各メソッドで共通となる条件を指定して、各メソッドに個別の条件を指定しています

import java.util.concurrent.TimeUnit;

import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Warmup;

@BenchmarkMode(Mode.Throughput)
@Fork(1)
public class ExampleBenchmark3 {

    @Warmup(iterations=5)
    @Measurement(iterations=6)
    @OutputTimeUnit(TimeUnit.MILLISECONDS)
    @Benchmark
    public void date() {
        java.util.Date d = new java.util.Date();
    }

    @Warmup(iterations=10)
    @Measurement(iterations=5)
    @OutputTimeUnit(TimeUnit.SECONDS)
    @Benchmark
    public void calndar() {
        java.util.Calendar c = java.util.Calendar.getInstance();
    }

}

■計測実行用の処理

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 BenchmarkLauncher3 {

    public static void main(String[] args) throws RunnerException {
        Options opt = new OptionsBuilder()
                 .include(ExampleBenchmark3.class.getName())
                .build();
        new Runner(opt).run();
    }

}

■実行結果

(見やすいように一部省略しています)

# Warmup: 10 iterations, 1 s each
# Measurement: 5 iterations, 1 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Throughput, ops/time
# Benchmark: org.jmh.sample.ExampleBenchmark3.calndar

# Run progress: 0.00% complete, ETA 00:00:26
# Fork: 1 of 1
# Warmup Iteration   1: 831998.265 ops/s
# Warmup Iteration   2: 1843097.120 ops/s
# Warmup Iteration   3: 2135803.035 ops/s
# Warmup Iteration   4: 2230390.265 ops/s
# Warmup Iteration   5: 2257604.127 ops/s
# Warmup Iteration   6: 2182396.572 ops/s
# Warmup Iteration   7: 2194433.253 ops/s
# Warmup Iteration   8: 2238500.506 ops/s
# Warmup Iteration   9: 2202598.413 ops/s
# Warmup Iteration  10: 2186766.643 ops/s
Iteration   1: 2209673.894 ops/s
Iteration   2: 2212802.599 ops/s
Iteration   3: 2227286.424 ops/s
Iteration   4: 2203750.319 ops/s
Iteration   5: 2232765.697 ops/s


Result "calndar":
  2217255.786 ±(99.9%) 47195.318 ops/s [Average]
  (min, avg, max) = (2203750.319, 2217255.786, 2232765.697), stdev = 12256.474
  CI (99.9%): [2170060.468, 2264451.105] (assumes normal distribution)


# Warmup: 5 iterations, 1 s each
# Measurement: 6 iterations, 1 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Throughput, ops/time
# Benchmark: org.jmh.sample.ExampleBenchmark3.date

# Run progress: 57.69% complete, ETA 00:00:11
# Fork: 1 of 1
# Warmup Iteration   1: 37112.900 ops/ms
# Warmup Iteration   2: 39439.666 ops/ms
# Warmup Iteration   3: 43483.290 ops/ms
# Warmup Iteration   4: 43699.785 ops/ms
# Warmup Iteration   5: 43782.495 ops/ms
Iteration   1: 43773.365 ops/ms
Iteration   2: 43375.840 ops/ms
Iteration   3: 43685.114 ops/ms
Iteration   4: 43565.948 ops/ms
Iteration   5: 43899.921 ops/ms
Iteration   6: 43913.513 ops/ms


Result "date":
  43702.283 ±(99.9%) 580.376 ops/ms [Average]
  (min, avg, max) = (43375.840, 43702.283, 43913.513), stdev = 206.968
  CI (99.9%): [43121.908, 44282.659] (assumes normal distribution)


# Run complete. Total time: 00:00:28

Benchmark                   Mode  Cnt        Score       Error   Units
ExampleBenchmark3.calndar  thrpt    5  2217255.786 ± 47195.318   ops/s
ExampleBenchmark3.date     thrpt    6    43702.283 ±   580.376  ops/ms

各メソッドの個別の実行条件が反映された計測になっていることが分かります



関連エントリ