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