Java | 「オートボクシングでのパフォーマンス」や「キャッシュ範囲の仕組み(-128〜127)」をコードで実験

Java Java
スポンサーリンク

では ①キャッシュの仕組みの確認(-128〜127)②オートボクシングのパフォーマンス比較(プリミティブ vs ラッパー) を実験するための Java の実行可能なコード を用意します。手元のJVMでコンパイルして実行すれば、結果を観察できます。各プログラムに解説と注意点も付けました。


1. 整数キャッシュの仕組みを確かめるコード

ファイル名:IntegerCacheDemo.java

public class IntegerCacheDemo {
    public static void main(String[] args) {
        // キャッシュ範囲内のテスト
        Integer a1 = Integer.valueOf(127);
        Integer b1 = Integer.valueOf(127);
        System.out.println("127: a1 == b1 -> " + (a1 == b1));        // 期待: true

        // キャッシュ範囲外のテスト
        Integer a2 = Integer.valueOf(128);
        Integer b2 = Integer.valueOf(128);
        System.out.println("128: a2 == b2 -> " + (a2 == b2));        // 期待: false(通常)

        // new を使うと常に別オブジェクト
        Integer n1 = new Integer(127);
        Integer n2 = new Integer(127);
        System.out.println("new 127: n1 == n2 -> " + (n1 == n2));    // 期待: false

        // equals は値比較
        System.out.println("equals check (128): " + a2.equals(b2)); // true

        // 確認のために範囲を表示
        System.out.println("キャッシュ範囲の一般的な説明: JVM は -128〜127 をキャッシュ(デフォルト)します。");
    }
}
Java

実行方法

javac IntegerCacheDemo.java
java IntegerCacheDemo

期待される出力(例)

127: a1 == b1 -> true
128: a2 == b2 -> false
new 127: n1 == n2 -> false
equals check (128): true
キャッシュ範囲の一般的な説明: JVM は -128〜127 をキャッシュ(デフォルト)します。

解説

  • Integer.valueOf() は -128〜127 の範囲で同じインスタンスを返す(JVMの実装で一般的)。そのため ==(参照比較)が true になります。
  • 128 のような値はキャッシュ範囲外なので、別インスタンスが返され ==false になります。
  • new Integer(...) は常に新しいオブジェクトを作るので ==false。値比較は必ず equals() を使うのが安全。

2. オートボクシングのパフォーマンス実験

ファイル名:BoxingBenchmark.java

public class BoxingBenchmark {
    static final int N = 30_000_000; // 3千万ループ(環境に応じて調整)
    static final int WARMUP = 5;

    public static void main(String[] args) {
        System.out.println("JVM: オートボクシング vs プリミティブの簡易ベンチマーク");
        System.out.println("ループ回数 N = " + N);
        System.out.println();

        // JIT のウォームアップ(簡易)
        for (int i = 0; i < WARMUP; i++) {
            runPrimitive(1_000_000);
            runBoxing(1_000_000);
        }

        // 本計測:プリミティブ
        long t1 = runPrimitive(N);

        // 本計測:ボクシング(Integer を使う)
        long t2 = runBoxing(N);

        System.out.printf("プリミティブ合計時間: %.3f ms%n", t1 / 1_000_000.0);
        System.out.printf("ラッパー(Integer)合計時間: %.3f ms%n", t2 / 1_000_000.0);
        System.out.printf("比率 (wrapper / primitive): %.2f%n", (double) t2 / t1);
    }

    // プリミティブ int を使うループ
    static long runPrimitive(int loops) {
        long start = System.nanoTime();
        int sum = 0;
        for (int i = 0; i < loops; i++) {
            sum += i; // 全てプリミティブ操作
        }
        // sum を使って最適化を防ぐ
        if (sum == -1) System.out.println("never");
        long end = System.nanoTime();
        return end - start;
    }

    // Integer(ラッパー)を使うループ(オートボクシング/アンボクシングが発生)
    static long runBoxing(int loops) {
        long start = System.nanoTime();
        Integer sum = 0; // ボクシングされる(Integer.valueOf(0))
        for (int i = 0; i < loops; i++) {
            sum += i; // sum = Integer.valueOf(sum.intValue() + i) 相当(ボクシングとアンボクシング)
        }
        if (sum == -1) System.out.println("never");
        long end = System.nanoTime();
        return end - start;
    }
}
Java

実行方法

javac BoxingBenchmark.java
java -Xmx1G BoxingBenchmark

(必要なら -Xmx を大きめに。ループ回数 N は実行時間が長すぎる場合に調整してください。)

期待される結果(傾向)

  • プリミティブ合計時間 はかなり短い。
  • ラッパー(Integer)合計時間 はプリミティブより 数倍〜十数倍 遅くなることが多い。
  • 比率 (wrapper / primitive) は環境によって異なるが、1より大きい(つまりラッパーの方が遅い) のが普通。

例(環境により大幅に異なるため参考値)
プリミティブ合計時間: 150 ms
ラッパー合計時間: 1200 ms
比率 (wrapper / primitive): 8.00

解説(なぜ差が出るか)

  • ラッパーを使うと、オートボクシング/アンボクシング によりオブジェクト操作が発生する(Integer.valueOf 呼び出し、場合によっては新しいオブジェクト生成)。これが CPU とメモリの負荷になる。
  • ガーベジコレクションの負荷やキャッシュ効率の低下も影響する。
  • JIT(ホットスポット)による最適化の影響も大きく、短いベンチでは結果がばらつく。だから簡易ベンチでもウォームアップを行っている。

3. 実験時の注意とベンチ改善のコツ

  1. JVMのウォームアップ を忘れない(JIT 最適化の影響が大きい)。上のコードで簡易ウォームアップを入れています。
  2. System.nanoTime() を使うcurrentTimeMillis より精度が高い)。
  3. 最小化されたベンチは誤解を生みやすい:実際のアプリケーションのパターン(メモリ割り当て、GC、スレッド等)により差は変わる。
  4. ループ回数は適切に:短すぎるとノイズが大きい、長すぎると時間がかかる。
  5. 高精度のベンチをしたければ、JMH(Java Microbenchmark Harness) の利用を検討する(本格的なマイクロベンチ用ツール)。

4. 実務上の結論・推奨

  • パフォーマンスが重要なループでは プリミティブ(intlong)を使う
  • コレクション(ArrayList 等)を使わざるを得ない場合は、可能ならプリミティブ配列(int[])を検討したり、性能問題があるなら IntStream やサードパーティのプリミティブ専用コレクション(例:fastutil 等)を検討する。
  • キャッシュ範囲(-128〜127)に依存したコード(== で比較するなど)はバグのもと。値比較は equals() またはアンボクシングして == を使う。
  • null を扱う場合は慎重に(アンボクシングで NPE になる)。
Java
スポンサーリンク
シェアする
@lifehackerをフォローする
スポンサーリンク
タイトルとURLをコピーしました