リフレクションはやっぱり遅いのか?
遅い遅いと敬遠されがちなリフレクションですが、最近のJDKでもやっぱり遅いのか計測してみました。
- setメソッドを呼び出して値を設定する処理を10万回実行した際の所要時間を計測
- 10回試行し平均を取得。
- JRE1.5,1.6でそれぞれ試した
結果
JDK1.5.0_09 | JDK1.6update1 | ||||
---|---|---|---|---|---|
メソッド呼び出し | 1msec | 1msec | |||
リフレクションを使う(メソッド検索時間は含まない) | 87msec | 54msec | |||
リフレクションを使う(メソッド検索時間を含む) | 842msec | 582msec |
やはりそれなりには重いけど、10万回実行してこれだけの差ならよっぽどクリティカルな部分でなければ問題ないのでは、と思いました。
計測で使用したコード
計測タスク。
/** * リフレクションを使って値を設定するタスク * メソッド検索時間を含む */ static class ReflectionTaskWithSearchSetter implements Task { private Kitten mii = new Kitten(); public void execute () throws Throwable { try { Method m = mii.getClass().getDeclaredMethod( "setName", String.class ); m.invoke( mii, "mii" ); } catch ( SecurityException e ) { e.printStackTrace(); } catch ( NoSuchMethodException e ) { e.printStackTrace(); } } public String getName () { return "リフレクション(メソッド検索時間を含む)"; } } /** * リフレクションを使って値を設定するタスク * メソッド検索時間は含まない。 */ static class ReflectionTask implements Task { private Kitten mii = new Kitten(); private Method m; ReflectionTask() { try { m = mii.getClass().getDeclaredMethod( "setName", String.class ); } catch ( SecurityException e ) { e.printStackTrace(); } catch ( NoSuchMethodException e ) { e.printStackTrace(); } } public void execute () throws Throwable { m.invoke( mii, "mii" ); } public String getName () { return "リフレクション(メソッド検索時間を含まない)"; } } /**メソッド呼び出しで値を設定するタスク*/ static class MethodCallTask implements Task { private Kitten mii = new Kitten(); public void execute () throws Throwable { mii.setName( "mii" ); } public String getName () { return "メソッド呼び出し"; } } /** テストクラス **/ static class Kitten { String name; String getName() { return name; } void setName( String name ) { this.name = name; } }
メイン。
Task[] tasks = new Task[] { new MethodCallTask(), new ReflectionTask(), new ReflectionTaskWithSearchSetter() }; try { Measure measure = new Measure(); measure.setCount( 100000 ); // 1タスクを10万回実行する。 measure.run( tasks ); } catch ( Throwable e ) { e.printStackTrace(); }
計測用ユーティリティ
/** * 性能計測クラス。 */ public class Measure { /**実行回数*/ private int count = 1; /**平均をとるための試行回数*/ private int roop = 10; /**事前実行*/ private static final Command PRE = new Command(){ public void execute(Task task, Measure m) throws Throwable { m.measure(task); } }; /**本番*/ private static final Command MAIN = new Command(){ public void execute(Task task, Measure m) throws Throwable { StringBuffer buff = new StringBuffer(); buff.append(task.getName()); buff.append(" : "); buff.append(m.measure(task)); buff.append("msec"); System.out.println(buff.toString()); } }; /** * 実行 * @param tasks * @throws Throwable */ public void run( Task[] tasks) throws Throwable { // 初期実行の際の余分なオーバーヘッドを除くために準備運動を行う。 each(tasks, PRE); // 本番 each(tasks, MAIN); } /** * 計測タスク * * @version $Revision:$ * @author $Author:$ */ public interface Task { /** * タスクの識別子を返す * @return タスクの識別子 */ public String getName(); /** * タスクを実行する。 * @throws Throwable 失敗 */ public void execute() throws Throwable; } /**eachのコマンドインターフェイス*/ private interface Command { /** * コマンドを実行する * @param task タスク * @param m 計測クラス * @throws Throwable 例外 */ void execute(Task task, Measure m) throws Throwable ; } /**タスクをイテレート * @throws Throwable */ private void each (Task[] tasks, Command com) throws Throwable{ for (int i = 0; i < tasks.length; i ++) { com.execute(tasks[i], this); } } /** * 計測を実行する。 * @param task タスク。 * @return 実行に要した時間[msec]。 * @throws Throwable */ public long measure(Task task) throws Throwable { long sum = 0; for (int i = 0 ; i < roop; i++) { sum += _measure(task); } return sum / roop; } /** * 計測を実行する。 * @param task タスク。 * @return 実行に要した時間[msec]。 * @throws Throwable */ public long _measure(Task task) throws Throwable { // 計測開始時刻 final long start; // 計測終了時刻 final long finish; start = System.currentTimeMillis(); for (int i = 0; i < count; i++) { task.execute(); //実行 } finish = System.currentTimeMillis(); return finish - start; } /** * 実行回数を取得します。 * @return 実行回数 */ public int getCount() { return count; } /** * 平均をとるための試行回数を取得します。 * @return 平均をとるための試行回数 */ public int getRoop() { return roop; } /** * 実行回数を設定します * @param i 実行回数 */ public void setCount(int i) { count = i; } /** * 平均をとるための試行回数を設定します。 * @param i 平均をとるための試行回数 */ public void setRoop(int i) { roop = i; } }