アノテーションと継承のコスト差
ふと、特定の処理の前後に差し込む処理を継承などを用いて実現する場合と@Beforeなどのアノテーションやリフレクションを用いて実現する際のパフォーマンス差を測定したのでメモ。たぶん、こんなベンチマークは何年も前にみんな見たことあるだろうし今さら書いても特に意味は無いのだけれど、せっかくなので。
Core2Duo メモリ3Gのマシンでそれぞれ100万回実行を10回繰り返した結果です。
No | アノテーション(ms) | アノテーション(キャッシュあり)(ms) | 継承(ms) |
---|---|---|---|
1 | 17605 | 294 | 10 |
2 | 17312 | 257 | 9 |
3 | 17496 | 256 | 9 |
4 | 17270 | 255 | 9 |
5 | 17332 | 255 | 10 |
6 | 17372 | 255 | 8 |
7 | 17362 | 256 | 9 |
8 | 17385 | 257 | 9 |
9 | 17265 | 257 | 9 |
10 | 17215 | 256 | 10 |
平均 | 17361.4 | 259.8 | 9.2 |
とまぁ、継承で実装する場合を1とするとアノテーションを使って実現すると1890倍、キャッシュを用いても70倍と、かなりの差がある。
ただし、一回当たりの実行時間はアノテーションを用いても 0.01736 ms。キャッシュを用いると 0.00025 ms。
使う場所を十分検討すれば、パフォーマンスに大きな影響を与えないレベルである事も分かる。
早すぎる最適化を避け、可読性を重視するのであればアノテーションを使うほうが理にかなっているような気もします。
以下、ソースコード。
何のベンチーマークかよく分からないなどのつっこみどころがあると思いますが、ひとまずこれで。
アノテーションクラス(RetentionPolicy.RUNTIMEを忘れないように)
package test; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @Retention(RetentionPolicy.RUNTIME) public @interface Before { }
ベンチマークのベースとなるクラス(コメントアウトを外すとキャッシュを使った実装になります)
package test; import java.lang.annotation.Annotation; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; public abstract class BaseClass { // private Map<String, List<Method>> cache = new HashMap<String, List<Method>>(); protected void setUp() throws IllegalArgumentException, IllegalAccessException, InvocationTargetException { // if (cache.containsKey(this.getClass().getName())) { // List<Method> list = cache.get(this.getClass().getName()); // for (Method method : list) { // method.invoke(this); // } // return; // } Method[] methods = getClass().getMethods(); List<Method> beforeMethods = new ArrayList<Method>(); for (Method method : methods) { Annotation[] annotations = method.getAnnotations(); for (Annotation annotation : annotations) { if (annotation.annotationType().equals(Before.class)) { beforeMethods.add(method); method.invoke(this); } } } // cache.put(this.getClass().getName(), beforeMethods); } public void execute() throws IllegalArgumentException, IllegalAccessException, InvocationTargetException { for (int x = 0; x < 10; x++) { long start = System.currentTimeMillis(); int loopCount = 1000000; for (int i = 0; i < loopCount; i++) { setUp(); } System.out.println(System.currentTimeMillis() - start); exec(); start = System.currentTimeMillis(); for (int i = 0; i < loopCount; i++) { tearDown(); } System.out.println(System.currentTimeMillis() - start); } } public abstract void exec(); public abstract void tearDown(); }
ベンチマークするクラス
package test; import java.lang.reflect.InvocationTargetException; public class TestClass extends BaseClass { public int i = 0; @Override public void exec() { } @Before public void beforeMethod() { i++; } public static void main(String[] args) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException { new TestClass().execute(); } @Override public void tearDown() { i++; } }