2012年4月15日日曜日

[ExcelVBA] ベンチマーク


ある方法を実現するために複数のやり方がある場合どっちが早いんだろう?
と、思うことがあると思います。
そんなときのために、Benchmark関数を書いてみました。

以下のシチュエーションを元に実際に使ってみます。

(シチュエーション)
何かしらのドキュメントがExcelで作成されていて、それのチェックプログラムを作成するとします。
1シートには4万行程度記載されていて、それが10シート程度あるとします。
それらのシートから特定の列だけループで走査していき、
複数の条件を全て満たした時他の列の情報も取得していくとします。

(問題点)
複数の条件を全てクリアした時をANDで一行で書いたとします。
ここで疑問が発生します。
ExcelVBAにおけるAND文は途中でFalseになろうが最後の条件まで全て判定します。
つまり、最初の判定でFalseになってしまうと本来最後の条件まで判定する必要が
ないため、速度を気にする場合不要な判定を行い時間がかかりすぎることになります。
では、分けて書いた場合とではどれくらいのインパクトがあるのか知りたい!
と、思うわけです。

(準備)
GoogleDocsにBenchMarkModule.basをアップしておいたのでそちらをダウンロードしてインポートしてください。
https://docs.google.com/open?id=0B_9e7wIj6KvuMU1TNW0tTEF5U3c


(検証するためのコード)
Option Explicit

Sub TestCode1()
    If Rate2 And Rate5 And Rate8 Then
        '何かの処理
    End If
End Sub

Sub TestCode2()
    If Not Rate2 Then GoTo last
    If Not Rate5 Then GoTo last
    If Not Rate8 Then GoTo last
    '何かの処理
last:
End Sub

Function Rate2()
    Rate2 = IIf(Int(Rnd() * 10) < 2, True, False)
End Function

Function Rate5()
    Rate5 = IIf(Int(Rnd() * 10) < 5, True, False)
End Function

Function Rate8()
    Rate8 = IIf(Int(Rnd() * 10) < 8, True, False)
End Function
Rate2関数, Rate5関数, Rate8関数は、それぞれ2割、5割、8割の割合でTrueを返す関数です。
TestCode1では、Andで一行で記載しています。
TestCode2では、1つのIf文に1条件とし、Falseの時はGotoで最後に移動してます。
(If文をネストさせると可読性が悪くなるので、こういう時のみGoto文を使用)
早速Benchmark関数を使ってテストしてみます。 上記のコードに下記コードを追加します。
Sub SampleCode()
    Call BenchMark(10000, "TestCode1", "TestCode2")
End Sub
BenchMark関数は、 第一引数に繰り返しの条件 第二引数に比較する関数名 第三引数に比較する関数名 を指定します。 上記例だと1万回、TestCode1関数とTestCode2関数を実行して比較することになります。
Rate TestCode1 TestCode2
TestCode1 22857/s        --      -68%
TestCode2 71111/s      211%        --
実行結果は、イミディエイトウィンドウに出力されます。
見方は、
1列目に比較した関数名が並んでいます。
TestCode1、TestCode2これは先ほどの第二引数と第三引数です。

2列目に1秒あたりに何回ループしたか表示しています。
1万回ループでは、ANDで一行で表現した時は22857回実行できたが
1つのIf分に1つの条件の場合の方が71111回実行できてるため
より効率的なコードだといえます。

3列目にIf文で分割したケースが、Andで1行で書いたケースを基準に実行回数がどれほど多いか示しています。
Andで1行で書いたケースを基準に2.11倍多く実行できたことを示しています。
71111 = 22857 + 22857 * 2.11

4列目にAndで1行で書いたケースが、
If文で分割したケースを基準に実行回数がどれほど少ないか示しています。
22857 = 71111 - 71111 * 0.68

以上です。

0 件のコメント: