How do you check performance optimized code?
前回、インライン化を確認する為に最初に調べたことは、インライン化されたコードの確認方法でした。調べた範囲ではアセンブリコードで確認するしかなさそうだったので、別の確認方法を探して前回のエントリとなります。
今回は実際にアセンブリコードを確認してみました。
JVM Option and disassemble plugin
JIT-compileされたアセンブリコードの出力は「-XX:+PrintAssembly」を付けることにより出力されます。しかし、debug buildしたOpenJDK7にこのオプションを指定して実行したところ、以下のような出力となりました。
OpenJDK Client VM warning: PrintAssembly is enabled; turning on DebugNonSafepoints to gain additional output
VM option '+PrintAssembly'
Could not load hsdis-i386.so; library not loadable; PrintAssembly is disabled
...
「hsdis-i386.so」というモジュールをロードしようとして失敗しています。調べてみたところ、このモジュールはOpenJDKのJVMのdisassembleプラグインで「-XX:+PrintAssembly」を付けて実行する際に必要になるようです。このモジュールのコードはOpenJDKのソースコードに含まれており、ビルドしてインストールすればPrintAssemblyを有効にすることができます。
Build the OpenJDK disassembly plugin
このdisassemblyプラグインのビルドのインストラクションは以下に記載されています。
(OpenJDK source code folder)/hotspot/src/share/tools/hsdis/README
binutilsのバージョンに指定があったりと面倒そうだったので、以下の記事を参考にしてイントールしました。
java: How to get PrintAssembly
sudo apt-get install texinfo
cd /opt/openjdk/hotspot/src/share/tools/hsdis/
mkdir build && cd build
wget http://ftp.gnu.org/gnu/binutils/binutils-2.20.1.tar.bz2
tar jxf binutils-2.20.1.tar.bz2 && mv binutils-2.20.1 binutils && cd ..
make
最後に、出来上がったhsdis-i386.soをLD_LIBRARY_PATHに含めます。
export LD_LIBRARY_PATH=(openjdk source code folder)/hotspot/src/share/tools/hsdis/build/linux-i586:$LD_LIBRARY_PATH
以上で「-XX:+PrintAssembly」を使用することができます。
Output
「-XX:+PrintAssembly」を付けて実行すると、アセンブリコードがどんどん出力されます。さすがに長いので、出力の一部を貼っておきます。
;; block B105 [10, 15]
0xb50f3391: mov %esi,%ecx ;*invokespecial grow
; - java.io.ByteArrayOutputStream::ensureCapacity@12 (line 93)
; - java.io.ByteArrayOutputStream::write@7 (line 122)
; - net.wrap_trap.example.encode.EncodeUTF8Test::getUtf8@217 (line 85)
0xb50f3393: call 0xb50ad2a0 ; OopMap{[56]=Oop [60]=Oop [136]=Oop off=3432}
;*invokespecial grow
; - java.io.ByteArrayOutputStream::ensureCapacity@12 (line 93)
; - java.io.ByteArrayOutputStream::write@7 (line 122)
; - net.wrap_trap.example.encode.EncodeUTF8Test::getUtf8@217 (line 85)
; {optimized virtual_call}
0xb50f3398: mov 0x74(%esp),%ebx
0xb50f339c: mov 0x3c(%esp),%eax
;; block B106 [15, 223]
0xb50f33a0: mov 0xc(%eax),%edx ;*getfield buf
; - java.io.ByteArrayOutputStream::write@11 (line 123)
; - net.wrap_trap.example.encode.EncodeUTF8Test::getUtf8@217 (line 85)
0xb50f33a3: mov 0x8(%eax),%esi ;*getfield count
; - java.io.ByteArrayOutputStream::write@15 (line 123)
; - net.wrap_trap.example.encode.EncodeUTF8Test::getUtf8@217 (line 85)
0xb50f33a6: cmp 0x8(%edx),%esi ; implicit exception: dispatches to 0xb50f3f9b
;; 824 branch [AE] [RangeCheckStub: 0x8209970] [bci:20]
0xb50f33a9: jae 0xb50f3fc7
0xb50f33af: mov %bl,0xc(%edx,%esi,1) ;*bastore
; - java.io.ByteArrayOutputStream::write@20 (line 123)
; - net.wrap_trap.example.encode.EncodeUTF8Test::getUtf8@217 (line 85)
0xb50f33b3: inc %esi
0xb50f33b4: mov %esi,0x8(%eax) ;*putfield count
; - java.io.ByteArrayOutputStream::write@28 (line 124)
; - net.wrap_trap.example.encode.EncodeUTF8Test::getUtf8@217 (line 85)
0xb50f33b7: mov %eax,%esi
0xb50f33b9: lea 0x84(%esp),%eax
0xb50f33c0: mov 0x4(%eax),%edx
0xb50f33c3: mov (%edx),%ebx
0xb50f33c5: and $0x7,%ebx
0xb50f33c8: cmp $0x5,%ebx
0xb50f33cb: je 0xb50f33e4
0xb50f33d1: mov (%eax),%ebx
0xb50f33d3: test %ebx,%ebx
0xb50f33d5: je 0xb50f33e4
0xb50f33db: cmpxchg %ebx,(%edx)
0xb50f33de: jne 0xb50f3fe0 ;*synchronization entry
; - java.io.ByteArrayOutputStream::write@-1 (line 122)
; - net.wrap_trap.example.encode.EncodeUTF8Test::getUtf8@217 (line 85)
0xb50f33e4: mov 0x40(%esp),%ebx
0xb50f33e8: add $0x4,%ebx
Javaのコードの行番号がコメントとして出力されているので、どこのコードがどう変換されたかを確認することができます。これでJITコンパイラによってどう最適化されているか確認することができそうですが、残念ながら私はアセンブリを読めないので今日のところはここまで。アセンブリの基礎くらいまでは勉強しておきたいと思います。
Conclusion
- JITコンパイルされたアセンブリコードを確認する際には「-XX:+PrintAssembly」を指定する
- OpenJDK7ではdisassembly pluginを別途インストールする必要がある