【Java】リフレクションの高速化手法-改訂版

メソッド名やその引数が可変な状況でのリフレクションは非常にパフォーマンスが悪いです。その処理をネイティブでの呼び出しと同レベルまで引き上げるのが今回のお題です。

リフレクションを使う場合、大まかに状況は以下のように分類できると思います。

  1. クラス名とメソッド名が固定
  2. クラス名が可変、メソッド名は固定
  3. クラス名は固定、メソッド名は可変
  4. クラス名とメソッド名が可変

1番、2番に関してはインターフェースを噛ませればネイティブでの呼び出しと大差ない呼び出し時間になります。 クラス名の取得はJARファイルはZIP形式ですので、展開してファイルを列挙すれば分かります。ここではそちらには触れません。

ここでは3番、4番のメソッド呼び出しのパフォーマンス改善を行います。

結論から言いますと、関数を中継して呼び出すクラスのソースコードを出力し内部でコンパイル、そのクラスをインターフェース越しに呼び出すという形になりました。

前出のバイトコードの出力よりはメンテナンスが容易ですが、この高速化手法を行うにはJavaをJDKで実行している必要があります。不特定多数に使われるアプリケーションの場合は使えないので、その場合には冗談で書いた前回のようにプログラム中でバイトコードを生成する必要があります。

実験の為、まずは普通に関数を実行する以下のコードを実行します。

インライン最適化の仕様をそれほど把握できていないので、明らかに無駄なコードがありましたら教えてください。。。

【共通部】

【コード1】

【コード2】

【コード3】

【コード4】(遅いので、ループ回数を1/10にして実行しています)

それぞれの秒数(単位はms)を3回計測すると以下のようになりました。時間がかかっていたため、コード4はループ回数を1/10にして表示された速度に10倍しています。

[コード1] 生成したインスタンスのメソッドを叩く 331,329,331

[コード2] 生成したインスタンスをインターフェース越しに叩く 331,329,330

[コード3] リフレクションで生成したインスタンスをインターフェース越しに叩く 327,334,337

[コード4] リフレクションで生成したインスタンスをリフレクションで叩く 6460,6510,6320

Method.Invokeを使うとかなり遅くなる事が分かります。 反面、リフレクションで動的にインスタンスを生成してもインターフェース越しに呼び出すコストは殆どないようです。

つまり、リフレクションで呼び出したいクラスを呼び出すためのクラスを動的に作成し、それをリフレクションでインターフェース越しに呼べば高速化になる、はずです。

【アプリケーション】
(リフレクションで生成、インターフェースで操作)
【動的に作成した呼び出し用クラス】
(渡されたものをそのまま操作)
【呼び出したいクラス】

前回はバイトコードを出力しましたが、難解ですし実用的ではありません。そのため、ソースコードを出力してコンパイルはJDKに丸投げします。

まず呼び出し用クラスの為のインターフェースを作成します。

次に呼び出すクラスを作成します。

以下のコードでコンパイル、実行することができます。

実行時間は332,328,329と十分なパフォーマンスが出ています。

これでもバイトコードを出力した際よりも長いコードですが、あそこまで難解ではないし意味の分かりやすいコードなのでメンテナンスはしやすいはずです。

Compiler APIの他のサンプルを見る限り、ほぼ全てJavaFileManagerを継承して新しいクラスを作ったりしていましたが実装が長くなるのが辛かったので無理に押し込んでしまいました。

実装にあたってはクラスローダーが曲者でした。 LoadClassFileObject内部のfindClassの実装は間違っているかもしれません。

カテゴリー: Java, プログラミング関連 パーマリンク

【Java】リフレクションの高速化手法-改訂版 への1件のフィードバック

  1. ピンバック: 【Java】リフレクションを限界まで高速化する | Gumu-Lab.

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です