巨大な配列の初期化とJava

Javaの大きな配列をC言語の感覚で作ってはいけないという話。

C言語の場合

int const table[] = {
    1207959552, 603979776, …中略…, 160468992
};

のように(不規則な)要素がたくさん(ここでは8192個)並んだ配列をコンパイルすると、

        .file   "table.c"
        .globl  table
        .section        .rodata
        .align 32
        .type   table, @object
        .size   table, 32768
table:
        .long   1207959552
        .long   603979776
        …中略…
        .long   160468992

のようにほぼ純粋にデータが並んでいるだけのものが出来上がります。

sizeコマンドでオブジェクトを調べても、

   text    data     bss     dec     hex filename
  32768       0       0   32768    8000 table.o

.rodata (text) が 4 (sizeof int) × 8,192 = 32,768 丁度ですし、オブジェクトのファイルサイズもそれより少し大きい(+1000にも満たない)程度です。期待通りの結果と言えるでしょう。

Javaの場合:

public class Table {
    public static final int table[] = {
            1207959552, 603979776, …中略…, 160468992
    };
}

という、C言語の例と同じような配列をコンパイルすると、
(配列の要素に対してconstとかfinalに類する指定はできないので同じにはなりませんが、それはまた別の話。)

  static {};
    Code:
       0: sipush        8192
       3: newarray       int
       5: dup
       6: iconst_0
       7: ldc           #2                  // int 1207959552
       9: iastore
      10: dup
      11: iconst_1
      12: ldc           #3                  // int 603979776
      14: iastore
          …中略…
    65134: dup
    65135: sipush        8191
    65138: ldc_w         #8075              // int 160468992
    65141: iastore
    65142: putstatic     #8076              // Field table:[I
    65145: return

のように、int配列のインスタンスを作った後、ひたすら初期値を代入するコードが生成されています。

5:からの

  • dup(既にスタックに積んであるarrayrefを複製)
  • iconst_0(index: 0 をスタックに積む)
  • ldc #2(value: constant pool の #2 をスタックに積む)
  • iastore(arrayref->[index] = value の代入を実行)

の4命令が1セットで、以下もだいたいその繰り返しです。スタックに積む値によってiconst_*bipushsipushldc(またはldc_w)を使い分けるので、命令長はまちまちですが。

Table.classのファイルサイズは、データの内容により前後しますが、この例では100kBを超えました。32kBのデータを生成(初期化)するために3倍以上もの容量が使われていることになります。

(データ自体はまた別に(実行時)32kB以上のメモリを消費するでしょう。)

そして、C言語の場合、もっと大きい配列も同じ要領で作れますが、Javaの場合、この例の初期化命令列だけで65,145バイト使っていて、そろそろ1メソッドの限界 (64kB) に到達してしまいますので、もっと大きい配列は違う方法で初期化するしかありません。

Javaの場合、大きいデータは、プロパティとかリソースとか呼ばれる外部のファイルから読み込むべきなのでしょう。