2011年3月24日木曜日

VBA 超高精度タイマークラス

VBAのパフォーマンス測るのにDateで引き算して...とかだと分解能は1sです。
APIのGetTickCountも分解能はmsです。

数分のかかる処理の時間を測るのにはこれでもいいんですが、
演算を多様するコードだともっと細かい時間を測りたくなります。

そこでQueryPerformanceCounterというAPIをつかってμsまで計測してみます。
この関数は環境依存の部分が多いので正確に数値がでない場合もあります。注意してください。

・クラス化したものです[Cl_QueryPerformance.cls]

Option Explicit

Private Declare Function QueryPerformanceCounter Lib "kernel32" (X As Currency) As Boolean
Private Declare Function QueryPerformanceFrequency Lib "kernel32" (X As Currency) As Boolean

Private Ctr1 As Currency
Private Ctr2 As Currency
Private Freq As Currency
Private Overhead As Currency

Public Sub TimeClear()

QueryPerformanceFrequency Freq
QueryPerformanceCounter Ctr1
QueryPerformanceCounter Ctr2

Overhead = Ctr2 - Ctr1 ' determine API overhead

QueryPerformanceCounter Ctr1 ' time loop

End Sub

Public Function GetQueryPerformanceTime(Optional vFormat As String = "0.0000") As Double

Dim result As Currency

QueryPerformanceCounter Ctr2

result = (Ctr2 - Ctr1 - Overhead) / Freq 'APIオーバーヘッド分削除とか単位合わせたりとか

GetQueryPerformanceTime = Format(CDbl(result), vFormat) '指定フォーマットで返す

Call TimeClear 'タイマー初期化
End Function

Private Sub Class_Initialize()
Call TimeClear 'タイマー初期化
End Sub


・クラスを動かすサンプルモジュールです

Option Explicit

Private Declare Sub Sleep Lib "kernel32" (ByVal sec As Long)

Public Sub QueryPerformanceTest()

Dim cls_QueryPerformance As New Cl_QueryPerformance

cls_QueryPerformance.TimeClear 'タイマー初期化

Call Sleep(500)

Debug.Print Format(cls_QueryPerformance.GetQueryPerformanceTime) '前回タイマー初期化時からの時刻。以降この関数実行時に同時にタイマー初期化がおこなわれる。

Call Sleep(750)

Debug.Print Format(cls_QueryPerformance.GetQueryPerformanceTime, "0.00")

cls_QueryPerformance.TimeClear '明示的にタイマーを初期化したい場合

Call Sleep(1000)

Debug.Print Format(cls_QueryPerformance.GetQueryPerformanceTime, "0.000000")

Call Sleep(1111)

Debug.Print Format(cls_QueryPerformance.GetQueryPerformanceTime, "0.00000000")

Set cls_QueryPerformance = Nothing

End Sub


結果は以下のようになりました。
0.5
0.75
1.000000
1.111300000

このようにかなり細かい数値まで取得できます。
VBAの浮動少数の扱いがよくわからないので数値は四捨五入されているかもしれません
が、少なくとも分解能は1ms以上ありそうなのでGetTickCountなんかを使うよりかはいいと思います。

クラスにしてるぶんそれだけで処理に時間を食います。
厳密に時刻を計算しなければいけないときはこのクラスを使わないか、計測したいコードに直接組み込んでください。

0 件のコメント:

コメントを投稿