私の最終的な目標は、標準の Java コレクションをベースラインとして使用して、いくつかの Java プリミティブ コレクション ライブラリの包括的なベンチマーク セットを作成することです。過去に、私はこれらの種類のマイクロベンチマークを作成するループ方法を使用しました。ベンチマークしている関数をループに入れて 100 万回以上反復するので、jit はウォームアップする機会があります。ループの合計時間を取り、反復回数で割って、ベンチマークしている関数への 1 回の呼び出しにかかる時間を見積もります。最近、JMHプロジェクト、具体的には次の例について読んだ後: JMHSample_11_Loopsこのアプローチの問題を確認しました。
私のマシン:
Windows 7 64-bit
Core i7-2760QM @ 2.40 GHz
8.00 GB Ram
jdk1.7.0_45 64-bit
上記のループ メソッド コードの簡略化された単純な例を次に示します。
public static void main(String[] args) {
HashMap<Long, Long> hmap = new HashMap<Long, Long>();
long val = 0;
//populating the hashmap
for (long idx = 0; idx < 10000000; idx++) {
hmap.put(idx, idx);
}
Stopwatch s = Stopwatch.createStarted();
long x = 0;
for (long idx = 0; idx < 10000000; idx++) {
x = hmap.get(idx);
}
s.stop();
System.out.println(s); //5.522 s
System.out.println(x); //9999999
//5.522 seconds / 10000000 = 552.2 nanoseconds
}
JMH を使用してこのベンチマークを書き直そうとしているのは次のとおりです。
package com.test.benchmarks;
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.HashMap;
import java.util.concurrent.TimeUnit;
@State(Scope.Thread)
public class MyBenchmark {
private HashMap<Long, Long> hmap = new HashMap<Long, Long>();
private long key;
@Setup(Level.Iteration)
public void setup(){
key = 0;
for(long i = 0; i < 10000000; i++) {
hmap.put(i, i);
}
}
@Benchmark
@BenchmarkMode(Mode.SampleTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public long testGetExistingKey() throws InterruptedException{
if(key >= 10000000) key=0;
return hmap.get(key++);
}
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder()
.include(".*" + MyBenchmark.class.getSimpleName() + ".*")
.warmupIterations(5)
.measurementIterations(25)
.forks(1)
.build();
new Runner(opt).run();
}
}
結果は次のとおりです。
Result: 31.163 ±(99.9%) 11.732 ns/op [Average]
Statistics: (min, avg, max) = (0.000, 31.163, 939008.000), stdev = 1831.428
Confidence interval (99.9%): [19.431, 42.895]
Samples, N = 263849
mean = 31.163 ±(99.9%) 11.732 ns/op
min = 0.000 ns/op
p( 0.0000) = 0.000 ns/op
p(50.0000) = 0.000 ns/op
p(90.0000) = 0.000 ns/op
p(95.0000) = 427.000 ns/op
p(99.0000) = 428.000 ns/op
p(99.9000) = 428.000 ns/op
p(99.9900) = 856.000 ns/op
p(99.9990) = 9198.716 ns/op
p(99.9999) = 939008.000 ns/op
max = 939008.000 ns/op
# Run complete. Total time: 00:02:07
Benchmark Mode Samples Score Score error Units
c.t.b.MyBenchmark.testGetExistingKey sample 263849 31.163 11.732 ns/op
私が知る限り、JMH の同じベンチマークでは、ハッシュマップが31ナノ秒であるのに対し、ループ テストでは552ナノ秒です。31 ナノ秒は、私には少し速すぎるようです。すべてのプログラマーが知っておくべきレイテンシの数値を見るメインメモリの参照は約 100 ナノ秒です。L2 キャッシュ参照は約 7 ナノ秒ですが、1,000 万の Long キーと値を持つ HashMap は L2 をはるかに上回ります。また、JMH の結果は奇妙に見えます。get 呼び出しの 90% に 0.0 ナノ秒かかる?
これはユーザーエラーだと思います。ヘルプ/ポインタをいただければ幸いです。ありがとう。
アップデート
実行した結果は次のとおりAverageTime
です。これは私の期待とより一致しています。ありがとう@oleg-estekhin!以下のコメントで、私はAverageTime
以前にテストを行い、SampleTime
. その実行を行うには、はるかに少ないエントリで HashMap を使用し、ルックアップの高速化は理にかなっているはずです。
Result: 266.306 ±(99.9%) 139.359 ns/op [Average]
Statistics: (min, avg, max) = (27.266, 266.306, 1917.271), stdev = 410.904
Confidence interval (99.9%): [126.947, 405.665]
# Run complete. Total time: 00:07:17
Benchmark Mode Samples Score Score error Units
c.t.b.MyBenchmark.testGetExistingKey avgt 100 266.306 139.359 ns/op