Cでプログラミングする場合、メモリを手動で割り当てたり、割り当てを解除したりする必要があります。これはエラーが発生しやすいプロセスです。対照的に、Java のような新しい言語は、多くの場合、メモリを自動的に管理します。Javaはガベージコレクションに依存しています。事実上、メモリはプログラマが必要に応じて割り当てられ、Javaはデータの一部が不要になったと判断し、対応するメモリを取得します。ガベージコレクションプロセスは高速で安全ですが、無料ではありません。何十年にもわたる最適化にもかかわらず、開発者にとって大きな頭痛の種となる可能性があります。
Javaにはネイティブ配列(たとえば、int []型)があります。これらの配列は通常、「Javaヒープ」に割り当てられます。つまり、それらはJavaによって動的データとして割り当てられ、管理され、ガベージコレクションの対象となります。
Javaには、IntBufferなどのバッファタイプもあります。これらは、ネイティブJava配列だけでなく、Javaヒープの外部にあるデータを含む他のデータソースによってもサポートできる高レベルの抽象化です。したがって、バッファタイプを使用して、Javaヒープにあまり依存しないようにすることができます。
しかし、私の経験では、ネイティブアレイと比較してパフォーマンスが低下します。バッファが遅いとは言えません。実際、バッファとストリーム(DataInputStream)のどちらかを選択する場合は、バッファタイプを強く優先する必要があります。ただし、私の経験では、ネイティブアレイほど高速ではありません。
「new int [50000]
」または「IntBuffer.allocate(50000)
」のいずれかを使用して、50,000個の整数の配列を作成できます。後者は基本的に(Javaヒープ上に)配列を作成する必要がありますが、IntBuffer「インターフェース」でラップします。
考えられる直感は、高レベルのインターフェイスで配列をラップすることは自由であるべきだということです。高レベルの抽象化にはパフォーマンスの低下(場合によってはパフォーマンスの向上)がないことは事実ですが、そうするかどうかは経験的な問題です。抽象化が無料で行われると思い込んではいけません。
私は経験的な声明を出しているので、想像できる最も簡単なテストでそれを経験的にテストしてみましょう。array / IntBufferのすべての要素に1を加算します。
for(int k = 0; k < s.array.length; k++) {
s.array[k] += 1;
}
for(int k = 0; k < s.buffer.limit(); k++) {
s.buffer.put(k, s.buffer.get(k) + 1);
}
デスクトップ(OpenJDK 14、4.2 GHz Intelプロセッサ)で次の結果が得られます。
| タイプ | タイム |
|::|::|
|int []|2.5 mus|
|IntBuffer|12 mus|
つまり、このテストでは、配列はIntBuffersよりも4倍以上高速です。
必要に応じて、ベンチマークを自分で実行できます。
https://github.com/lemire/Code-used-on-Daniel-Lemire-s-blog/tree/master/2020/11/30
私の期待は、Javaが配列に適用する多くの最適化がバッファー型に適用されないことです。
もちろん、これは、Javaヒープの外部から値をマップするためにバッファーが使用されたときに何が起こるかについてはほとんど教えてくれません。私の経験では、事態はさらに悪化する可能性があることが示唆されています。
少なくともパフォーマンスに関する限り、バッファタイプによってネイティブ配列が廃止されることはありません。
英語原稿: https://lemire.me/blog/2020/11/30/java-buffer-types-versus-native-arrays-which-is-faster/