ケント・ベックの実装パターンの付録にある、パフォーマンス測定フレームワークのMethodTimerを写経した。
MethodTimerは以下のように使うと、
MethodsTimer tester = new MethodsTimer(SetVsArrayList.class.getDeclaredMethods()); tester.report();
コレクションクラスなどの要素数の増加に伴うパフォーマンス劣化状況をレポートする。以下のような出力が得られ、setMembershipメソッドに対して、要素数が1,10,100・・と増加していった場合の所要時間が分かる。
setMembership 50.12 75.12 50.42 64.30 70.05 79.49
MethodTimerは以下
package etc9; import java.lang.reflect.Method; public class MethodsTimer { private static final int MAXIMUM_SIZE = 100000; static final int ONE_SECOND = 1000000000; private final Method[] methods; public MethodsTimer(final Method[] methods) { this.methods = methods; } public void report() throws Exception { for (Method each : methods) { System.out.print(each.getName() + "\t"); for (int size = 1; size <= MAXIMUM_SIZE; size *= 10) { MethodTimer r = new MethodTimer(size, each); r.run(); System.out.print(String.format("%.2f\t", r.getMethodTime())); } System.out.println(); } } }
MethodTimerは以下
package etc9; import java.lang.reflect.Constructor; import java.lang.reflect.Method; public class MethodTimer { private final int size; private final Method method; private final Object instance; private long totalTime; private int iterations; private long overhead; public MethodTimer(final int size, final Method method) throws Exception { this.size = size; this.method = method; this.instance = createInstance(); } private Object createInstance() throws Exception { Constructor<?> constructor = method.getDeclaringClass().getConstructor( new Class[] { int.class }); return constructor.newInstance(new Object[] { size }); } double getMethodTime() { return (double) (totalTime - overhead) / (double) iterations; } void run() throws Exception { iterations = 1; while (true) { totalTime = computeTotalTime(); if (totalTime > MethodsTimer.ONE_SECOND) break; iterations *= 2; } overhead = overheadTimer(iterations).computeTotalTime(); } private long computeTotalTime() throws Exception { long start = System.nanoTime(); for (int i = 0; i < iterations; i++) { method.invoke(instance, new Object[0]); } return System.nanoTime() - start; } private static MethodTimer overheadTimer(final int iterations) throws Exception { return new MethodTimer(iterations); } private MethodTimer(final int iterations) throws Exception { this(0, MethodTimer.Overhead.class.getMethod("nothing", new Class[0])); this.iterations = iterations; } public static class Overhead { public Overhead(int size) { } public void nothing() { } } }
計測対象のクラス
package etc9.target; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; public class SetVsArrayList { private Collection<String> set; private Collection<String> arrayList; private String probe; public SetVsArrayList(final int size) { set = new HashSet<String>(size); arrayList = new ArrayList<String>(size); for (int i = 0; i < size; i++) { String element = String.format("a%d", i); set.add(element); arrayList.add(element); } probe = String.format("a%d", size / 2); } public void setMembership() { set.contains(probe); } public void arrayListMembership() { arrayList.contains(probe); } }
実行結果は以下のようになり、setはcontaisの処理が要素数の増加により大きな影響を受けないのに対して、arrayListは要素数の増加による影響が大きいことが分かる。
setMembership 64.18 99.68 81.40 111.47 108.80 193.36 arrayListMembership 60.81 398.99 2257.44 20430.27 207143.09 2206581.18
MethodTimerのiterationsの使い方がちょっと気になる・・