2009年5月25日月曜日

timeit on java

SpringのAutoWireの有無による速度差と、CGLIBとProxyの速度差を調べたくなった。
AutoWireの有無による速度差はなし。
CGLIBとProxyも十分無視してよい速度差。

上の目的のためにtimeitみたいに、適当な回数分だけループして時間を計測してくれるツールを作った。
一定の時間を越えるまで回数を増やしてループし続けます。
最初は500msにしたら、テストスイートが終わらなくなったので、50ms。
比較用に交互に実行する機能があるといいかも。
  1. public abstract class Timeit {  
  2.  protected String name;  
  3.   
  4.  public Timeit(String name) {  
  5.   this.name = name;  
  6.  }  
  7.   
  8.  public void timeit() {  
  9.   int before = 0;  
  10.   int count = 1;  
  11.   long sum = 0;  
  12.   prepare();  
  13.   while (true) {  
  14.    int loop = count - before;  
  15.    long start = System.nanoTime();  
  16.    for (int i = 0; i < loop; ++i) {  
  17.     this.invoke();  
  18.    }  
  19.    long end = System.nanoTime();  
  20.    long current = end - start;  
  21.    sum += current;  
  22.    if (current > 50 * 1000 * 1000) {  
  23.     break;  
  24.    }  
  25.    before = count;  
  26.    count = count * 2;  
  27.   }  
  28.   long time = sum / count;  
  29.   System.out.print(name + ": ");  
  30.   if (time < 1000) {  
  31.    System.out.printf("%dns\n", time);  
  32.   } else if (time < 1000 * 1000) {  
  33.    System.out.printf("%.3fμs %dtimes\n", time / 1000.0, count);  
  34.   } else if (time < 1000 * 1000 * 1000) {  
  35.    System.out.printf("%.3fms %dtimes\n", time / 1000.0 / 1000, count);  
  36.   } else {  
  37.    System.out.printf("%.3fs %dtimes\n", time / 1000.0 / 1000 / 1000,  
  38.      count);  
  39.   }  
  40.  }  
  41.   
  42.  public void prepare() {  
  43.   
  44.  }  
  45.   
  46.  public abstract void invoke();  
  47.   
  48.  public static class TimeItTest extends TestCase {  
  49.   public void testSimple() throws Exception {  
  50.    new Timeit("simple") {  
  51.     public void invoke() {  
  52.      try {  
  53.       Thread.sleep(2);  
  54.      } catch (InterruptedException e) {  
  55.      }  
  56.     }  
  57.    }.timeit();  
  58.   }  
  59.  }  
  60. }  

Mockito

Mockito便利です。
jMockの変態構文に慣れない方は、EasyMockを使ってらっしゃると思います。
EasyMockのスタイルは素敵ですね。
ただ、人に勧めるときに、replayとかverifyとか、定義以外に文をはさむのってなんかいやですよね。
お勧めしづらいですね。
expectLastCallなんかもみっともない。

そこでMockito

  1.     // モック作成  
  2.     @SuppressWarnings("unchecked")  
  3.     List<string> mockedList = mock(List.class);  
  4.   
  5.     // あらかじめ、何が起こるかを書く  
  6.     when(mockedList.add("one")).thenReturn(true);  
  7.     // voidはwhenの引数として受け取れないのでdo*で書き直す。  
  8.     doNothing().when(mockedList).clear();  
  9.   
  10.     // モックに対して実行  
  11.     assertEquals(true, mockedList.add("one"));  
  12.     mockedList.clear();  
  13. </string>  


事前に定義する方法のほかに、あとからVerifyする方法もあります。
  1. // モック作成  
  2. @SuppressWarnings("unchecked")  
  3. List<string> mockedList = mock(List.class);  
  4.   
  5. // モックに対して実行  
  6. mockedList.add("one");  
  7. mockedList.clear();  
  8.   
  9. // 検証  
  10. verify(mockedList).add("one");  
  11. verify(mockedList).clear();  
  12. ring>  


呼び出し回数も限定しておきます。
  1.     @SuppressWarnings("unchecked")  
  2.     LinkedList<string> mockedList = mock(LinkedList.class);  
  3.     // モックに対してメソッド呼び出し  
  4.     mockedList.add("once");  
  5.   
  6.     mockedList.add("twice");  
  7.     mockedList.add("twice");  
  8.   
  9.     mockedList.add("three times");  
  10.     mockedList.add("three times");  
  11.     mockedList.add("three times");  
  12.   
  13.     // 回数指定で検証開始  
  14.     verify(mockedList).add("once");  
  15.     verify(mockedList, times(1)).add("once");  
  16.     verify(mockedList, times(2)).add("twice");  
  17.     verify(mockedList, times(3)).add("three times");  
  18.   
  19.     // 呼ばれてない確認。times(0)でもOK  
  20.     verify(mockedList, never()).add("never happened");  
  21.   
  22.     // 細かい数は不要。一度でも呼ばれたらOK  
  23.     verify(mockedList, atLeastOnce()).add("three times");  
  24. </string>  


すでに存在するオブジェクトに対して、部分的にモックを割り当てます。
名づけて"spy"
  1. // 部分的に書き換える。  
  2. LinkedList<string> spyList = spy(new LinkedList<string>());  
  3. when(spyList.size()).thenReturn(100);  
  4.   
  5. // addやgetは本来の動き  
  6. spyList.add("one");  
  7. assertEquals("one", spyList.get(0));  
  8.   
  9. // sizeは置き換えました。  
  10. assertEquals(100, spyList.size());  
  11.   
  12. verify(spyList).add("one");  
  13. ring></string>  


Mock作成が楽しくなりました。
Javaの構文による呼び出し順序を利用したマジックですが、
ここまできれいな構文で書けるなら、受け入れるしかないでしょう。
Mock内で引数に加工したり、詳しく検証する場合は、Anythingというインターフェースを使って実装するのですが、そこはまだありがちな構文。

PowerMockもMockitoに対応中。
まあ、PowerMockなんて、一時的に使うことはあっても、すぐ書き直してMockitoだけで通るように変更するのでいらないけど。