はじめに
Javaプログラミング言語の初期のころから、Javaはインタープリタ言語なのでパフォーマンスの点でCやC++に劣る、と主張している人たちがいました。もちろん、C++の信奉者たちは、そもそもJavaを「真の」言語だと思っていないでしょうし、Javaの連中はC++プログラマに向かっていつも「一度書けば、どこでも実行できる」と唱えています。
まず重要なことから取り上げましょう。Javaは基本的な整数演算をどれほどうまくやってのけるでしょうか。私が誰かに「2×3は?」と尋ねたら、おそらくすぐに答が返ってくることでしょう。では、相手がプログラムならどうなるでしょうか。これを調べるために、基本的なテストを行ってみましょう。テストの内容は次のとおりです。
- 最初にX個のランダムな整数を生成する
- それらの数に、2からYまでのすべての数を掛ける
- 全体集合の計算に要する時間を計算する
乱数の生成に要する時間が知りたいわけではないので、測定を始める前に乱数を生成しておくことが大事です。
Javaのテスト
Javaでは、ごく簡単に乱数を生成できます。
private void generateRandoms() { randoms = new int[N_GENERATED]; for( int i = 0; i < N_GENERATED; i++) { randoms[i] = (int)(i * Math.random()); } }
同様に、計算処理も簡単です。
private void javaCompute() { int result = 0; for(int i = 2; i < N_MULTIPLY; i++) { for( int j = 0; j < N_GENERATED; j++ ) { result = randoms[j] * i; result++; } } }
上記のjavaCompute
メソッドの実行に要する時間を単純に測定し、それをテスト結果と見なすこともできます。しかし、これはいくつかの理由で公正ではありません。第一に、JVMはプログラムの実行中に必要に応じてクラスローダーでクラスをロードします。OS自身も、JVMから要求されるさまざまなデータ構造についての準備を行います。そのため、上記の関数を最初に実行するときは、実際には実行の準備にかなりの時間が費やされると考えられます。従って、1回のテストで時間を測定するのは不適当でしょう。
代わりに、このプログラムを何回か実行して、結果の平均をとることにしましょう。ただし、関数の初回実行時には、実行時間の点でやはり同じ問題が生じるので、正確な時間測定ができません。その一方で、上記のメソッドの実行を繰り返すほど、JVMやOSによってデータがキャッシュされる可能性が高くなります。そのため、正確な実行時間を反映したデータではなく、OSによるコード実行最適化の結果を反映したデータが一部に含まれてしまう可能性もあり、やはり平均値の算出に影響が出ます。
こうした問題を相殺するため、上記のメソッドを数回実行し、「最も異常な」ケース、すなわち最短時間と最長時間を除外します。そうすれば、真の平均に近い値が得られるはずです。さらに、上記のjavaCompute
メソッドを実行した各時間の長さを配列に格納するものとしましょう。これは次のような単純なメソッドで実現されます。
private static long testTime( long diffs[] ) { long shortest = Long.MAX_VALUE; long longest = 0; long total = 0; for( int i = 0; i < diffs.length; i++ ) { if( shortest > diffs[i] ) shortest = diffs[i]; if( longest < diffs[i] ) longest = diffs[i]; total += diffs[i]; } total -= shortest; total -= longest; total /= ( diffs.length - 2 ); return total; }
これを全部まとめると、次のコードができあがります(このコードはIntMaths.javaにも入っています)。
public static void main(String[] args) { IntMaths maths = new IntMaths(); maths.generateRandoms(); //compute in Java long timeJava[] = new long[N_ITERATIONS]; long start, end; for( int i = 0; i < N_ITERATIONS; i++ ) { start = System.currentTimeMillis(); maths.javaCompute(); end = System.currentTimeMillis(); timeJava[i] = (end - start); } System.out.println( "Java computing took " + testTime(timeJava) ); }
ここでは時間をミリ秒単位で測定していることに注意してください。
筆者のラップトップ(それほどハイスペックではないものの、現在の一般的なマシンを代表するものと見てよいでしょう)で上記のコードを実行すると、平均値が約25ミリ秒になります。つまり、筆者のラップトップでは、10,000個のランダムな整数と2から1,000までの各数とを掛け合わせるのに、約25ミリ秒かかるということです。これは基本的に、約10,000,000回の整数演算に25msかかることを意味します。