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_*
やbipush
やsipush
やldc
(またはldc_w
)を使い分けるので、命令長はまちまちですが。
Table.class
のファイルサイズは、データの内容により前後しますが、この例では100kBを超えました。32kBのデータを生成(初期化)するために3倍以上もの容量が使われていることになります。
(データ自体はまた別に(実行時)32kB以上のメモリを消費するでしょう。)
そして、C言語の場合、もっと大きい配列も同じ要領で作れますが、Javaの場合、この例の初期化命令列だけで65,145バイト使っていて、そろそろ1メソッドの限界 (64kB) に到達してしまいますので、もっと大きい配列は違う方法で初期化するしかありません。
Javaの場合、大きいデータは、プロパティとかリソースとか呼ばれる外部のファイルから読み込むべきなのでしょう。