for
ループは純粋なwhile
ループほど高速ではなく、内部操作は非常に安価であるため、ここで何を測定しているのかを実際に判断することはできません。whileループを使用してコードを書き直すと、キーの二重反復は
var i = 0
while (i<I) {
var j = 0
while (j<J) {
flt(i * J + j) = v(i)(j)
j += 1
}
i += 1
}
flt
次に、一般的なケースのバイトコードが実際には劇的に異なることがわかります。非ジェネリック:
133: checkcast #174; //class "[D"
136: astore 6
138: iconst_0
139: istore 5
141: iload 5
143: iload_2
144: if_icmpge 191
147: iconst_0
148: istore 4
150: iload 4
152: iload_3
153: if_icmpge 182
// The stuff above implements the loop; now we do the real work
156: aload 6
158: iload 5
160: iload_3
161: imul
162: iload 4
164: iadd
165: aload_1
166: iload 5
168: aaload // v(i)
169: iload 4
171: daload // v(i)(j)
172: dastore // flt(.) = _
173: iload 4
175: iconst_1
176: iadd
177: istore 4
// Okay, done with the inner work, time to jump around
179: goto 150
182: iload 5
184: iconst_1
185: iadd
186: istore 5
188: goto 141
これは、ジャンプと低レベルの操作の集まりにすぎません(daloadとdastoreは、配列からdoubleをロードして格納する重要な操作です)。汎用バイトコードのキー内部を見ると、代わりに次のようになります。
160: getstatic #30; //Field scala/runtime/ScalaRunTime$.MODULE$:Lscala/runtime/ScalaRunTime$;
163: aload 7
165: iload 6
167: iload 4
169: imul
170: iload 5
172: iadd
173: getstatic #30; //Field scala/runtime/ScalaRunTime$.MODULE$:Lscala/runtime/ScalaRunTime$;
176: aload_1
177: iload 6
179: aaload
180: iload 5
182: invokevirtual #107; //Method scala/runtime/ScalaRunTime$.array_apply:(Ljava/lang/Object;I)Ljava/lang/Object;
185: invokevirtual #111; //Method scala/runtime/ScalaRunTime$.array_update:(Ljava/lang/Object;ILjava/lang/Object;)V
188: iload 5
190: iconst_1
191: iadd
192: istore 5
ご覧のとおり、配列の適用と更新を行うにはメソッドを呼び出す必要があります。そのためのバイトコードは、次のようなものの巨大な混乱です
2: aload_3
3: instanceof #98; //class "[Ljava/lang/Object;"
6: ifeq 18
9: aload_3
10: checkcast #98; //class "[Ljava/lang/Object;"
13: iload_2
14: aaload
15: goto 183
18: aload_3
19: instanceof #100; //class "[I"
22: ifeq 37
25: aload_3
26: checkcast #100; //class "[I"
29: iload_2
30: iaload
31: invokestatic #106; //Method scala/runtime/BoxesRunTime.boxToInteger:
34: goto 183
37: aload_3
38: instanceof #108; //class "[D"
41: ifeq 56
44: aload_3
45: checkcast #108; //class "[D"
48: iload_2
49: daload
50: invokestatic #112; //Method scala/runtime/BoxesRunTime.boxToDouble:(
53: goto 183
これは基本的に、各タイプの配列をテストし、それが探しているタイプである場合はボックス化する必要があります。Doubleはかなり前(10の3番目)に近いですが、JVMがコードがボックス/アンボックスになってしまうことを認識でき、実際にメモリを割り当てる必要がない場合でも、それでもかなり大きなオーバーヘッドです。(それができるかどうかはわかりませんが、できたとしても問題は解決しません。)
じゃあ何をすればいいの?[@specialized T]を試すことができます。これにより、各プリミティブ配列操作を自分で作成したかのように、コードが10倍に拡張されます。ただし、特殊化は2.9ではバグが多いため(2.10ではバグが少ないはずです)、期待どおりに機能しない可能性があります。速度が重要な場合は、まず、forループの代わりにwhileループを記述し(または、少なくとも2倍程度のループアウトに役立つ-optimizeを使用してコンパイルします)、次に、特殊化または記述のいずれかを検討します。必要なタイプのコードを手作業で作成します。