デシリアライズ速度の比較 ByteBuffer vs DirectBuffer vs Unsafe vs C
OpenJDK や Hotspot VM には sun.misc.Unsafe という内部APIがあり*1、これを使うと ByteBuffer.getInt や ByteBuffer.getLong よりも高速にバイト列から整数値をデコードできるという。これを駆使することで、Cで実装された拡張ライブラリに匹敵する速度を出せるらしい。
それが本当なら、データ圧縮やハッシュ関数、シリアライザ/デシリアライザなどの実装を高速化できる。例えば、lz4 や xxhash のJava実装が Unsafe API を使用している*2:jpountz/lz4-java
Prestoも、中間データのシリアライズ/デシリアライズにはすべて Unsafe API を使っている*3。
そこで、実際にベンチマークしてみた。
ベンチマーク内容
結果
- ByteBuffer heap: new byte[…]で確保したメモリを、ByteBuffer.wrapして整数値をデコード
- ByteBuffer direct: ByteBuffer.allocateDirectで確保したメモリから、整数値をデコード
- Unsafe heap: new byte[…]で確保したメモリから、Unsafe APIを使って整数値をデコード
- Unsafe direct: ByteBuffer.allocateDirectで確保したメモリから、Unsafe APIを使って整数値をデコード
- C shift: ビットシフトを使って、バイト列から整数値をデコード
- C load: memcpyとエンディアン変換関数(bswap_64、__DARWIN_OSSwapInt64、_byteswap_uint64など)を使って、バイト列から整数値をデコード
確かにUnsafeを使うとCに匹敵する性能が出る。これは予想以上に速い。
一方でヒープメモリからのByteBuffer.getIntは、かなり遅いことが分かる。Unsafe APIではヒープメモリでもdirect bufferでも差はないので、ヒープメモリからのByteBufferをUnsafeに変更すれば、2倍近く性能が向上することになる。
Cにはstrict-aliasingルールがあるので、バイト列から整数値をデコードするコードは複雑になる。コンパイラが賢いので冗長に見える割には速度が出るのだが、ポータブルで安全なコードを書くのは結構難しい。strict-aliasingルールについて詳しくは:
実行環境
- OS: Mac OS X 10.8.5
- CPU: Intel Core i7 2.7 GHz
- Memory: 16GB DDR3 1600MHz
- Oracle JDK 1.7.0 u45 b18 Hotspot 64-Bit Server VM
- gcc-llvm 4.2.1 (i686-apple-darwin11-llvm-gcc-4.2 (GCC) 4.2.1 (Based on Apple Inc. build 5658) (LLVM build 2336.11.00))
===
後日追記:あわせて読みたい:何故JVM(HotSpot)のUnsafe APIは速いのか
*1:Dalvikにあるかどうかは調査していない。誰か教えてください。
*2:ちなみにlz4とxxhashの開発者は同一人物。
*3:Prestoが依存している airlift/slice ライブラリがUnsafe APIをラップしたクラスを実装している。airliftを開発しているのはPrestoと同じメンバー。