マクロの処理時間を計測する方法を解説します。
現在時刻を1/100秒単位で取得するTimer関数を使えば、
簡単に取得することができます。
基本形
ソースコード
Sub マクロの実行時間を計測する() Dim 開始時刻 As Double, 終了時刻 As Double 開始時刻 = Timer ' ここに時間のかかる処理 終了時刻 = Timer Debug.Print "このマクロの実行時間:" & (終了時刻 - 開始時刻) & "秒" End Sub ' 実行例 このマクロの実行時間:1.65625秒
解説
見ての通りとても簡単にかけるコードです。
開始時刻と終了時刻を変数として持っておき、
経過時間を終了-開始で計算しただけですね。
Timer関数は現在の時刻を秒単位で返す関数です。
(正確には本日0時未明からの経過時間を秒単位で返します。)
小数点以下も計算しており1.65625秒と見えるため、
すごく細かく出せるように見えますが、実際には約1/100秒の精度です。
(2.00000の次が2.015625といったように結果は飛び飛びになります)
実務のマクロを測る分には全く問題のない精度ですね。
そこまでの精度はいらないから[時分秒]を表示したいという場合は、
計測時間をDate型に計算してからFormat関数で表示してください。
Debug.Print "このマクロの実行時間:" _ & Format((終了時刻 - 開始時刻) / 60 / 60 / 24, "h:mm:ss")
またTimer関数ではなく、初めからDate型を返すTime関数を使えば、
計算をすることなく表示させることもできます。(Formatは必要)
開始時刻 = Time 終了時刻 = Time Debug.Print "このマクロの実行時間:" _ & Format(終了時刻 - 開始時刻, "h:mm:ss")
Time関数は表示が楽な分、精度が秒単位になってしまいます。
マクロの実行時間によって使い分けてください。
使用例:どの処理が早いか調べる
実際にTimerを利用して処理を比較してみましょう。
B列とC列を足した値をA列に出力する処理を考えます。
Sub セルの足し算() Dim 開始時刻 As Double, 終了時刻 As Double 開始時刻 = Timer Dim i As Long For i = 1 To 100000 Cells(i, 1) = Cells(i, 2) + Cells(i, 3) Next 終了時刻 = Timer Debug.Print "このマクロの実行時間:" & (終了時刻 - 開始時刻) & "秒" End Sub
' 実行結果 このマクロの実行時間:4.3828125秒
10万行もやると、こんな単純な処理でも4秒を要してしまいますね。
ではこれを高速化してみましょう。まずは描画を停止してみます。
Sub セルの足し算_描画停止() Application.ScreenUpdating = False ' ←これをいれただけ Dim 開始時刻 As Double, 終了時刻 As Double 開始時刻 = Timer Dim i As Long For i = 1 To 100000 Cells(i, 1) = Cells(i, 2) + Cells(i, 3) Next 終了時刻 = Timer Debug.Print "このマクロの実行時間:" & (終了時刻 - 開始時刻) & "秒" End Sub
' 実行結果 このマクロの実行時間:4.03125秒
イマイチ効果はなかったですね。
シート1枚の処理で描画を停止する効果は、実はあまりありません。
では同じ処理を配列を使ってやってみましょう。
Sub セルの足し算_配列() Dim 開始時刻 As Double, 終了時刻 As Double 開始時刻 = Timer Dim ArrA列(1 To 100000, 1 To 1) Dim ArrB列: ArrB列 = Range("B1:B100000").Value Dim ArrC列: ArrC列 = Range("C1:C100000").Value Dim i As Long For i = 1 To 100000 ArrA列(i, 1) = ArrB列(i, 1) + ArrC列(i, 1) Next Range("A1:A100000").Value = ArrA列 終了時刻 = Timer Debug.Print "このマクロの実行時間:" & (終了時刻 - 開始時刻) & "秒" End Sub
' 実行結果 このマクロの実行時間:0.12890625秒
だいぶ高速化しましたね!
配列は少しコードが難しいですが、十分それに見合った効果があります。
また「コードを簡単にしたまま高速化する」方法として、
ワークシートに計算させる方法もあります。
Sub セルの足し算_シート数式() Dim 開始時刻 As Double, 終了時刻 As Double 開始時刻 = Timer Range("A1:A100000").Formula = "=B1+C1" Range("A1:A100000").Value = Range("A1:A100000").Value 終了時刻 = Timer Debug.Print "このマクロの実行時間:" & (終了時刻 - 開始時刻) & "秒" End Sub
' 実行結果 このマクロの実行時間:0.1875秒
配列には一歩及びませんでしたがこれでも相当高速ですね。
そして特筆すべきは、なんとFor文が消えています。
こんな風に、Formulaプロパティを使ってシートで計算させると
配列クラスの速度が出る上に、For文すらない簡単なコードにできます。
下手すると元のコードより簡単になりますからね。
非常に便利な手法ですので、マクロ高速化の際は、必ず意識しておきましょう。
と、少し話が逸れましたが、
このようにTimer関数を使うことで、どの処理が早いかを比べることができます。
遅い処理の代替手段を検討する際に使用してみてください。
使用例:どの処理に時間がかかっているか調べる
速くしなければいけない箇所に見当がついていれば、
上記のようにどの処理が早いかを検証できますが、
どこが遅いかわからない場合はまずそれを調べる必要があります。
その場合は以下のコードの通り、各処理ごとにTimer関数を設置します。
Sub 各処理の時間を調べる() Dim 開始時刻 As Double, 終了時刻 As Double ' 処理A 開始時刻 = Timer 処理A1 処理A2 処理A3 処理A4 処理A5 終了時刻 = Timer Debug.Print "処理Aの実行時間:" & (終了時刻 - 開始時刻) & "秒" ' 処理B 開始時刻 = Timer 処理B1 処理B2 処理B3 処理B4 処理B5 終了時刻 = Timer Debug.Print "処理Bの実行時間:" & (終了時刻 - 開始時刻) & "秒" End Sub
' 実行結果 処理Aの実行時間:1.890625秒 処理Bの実行時間:4.99609375秒
このように各処理ごとにTimer関数を調べることで、
どこの処理に時間をかかっているかを調べることができます。
また、処理がループ内にまとまっていて処理ごとの時間を計算しにくい場合は、
開始時刻 = Timer Dim i As Long For i = 1 To 1000 処理A1 処理A2 処理A3 処理A4 処理A5 ' 処理B1 ' ← 一時的にコメントアウトしてみる ' 処理B2 ' 処理B3 ' 処理B4 ' 処理B5 Next 終了時刻 = Timer Debug.Print "この処理の実行時間:" & (終了時刻 - 開始時刻) & "秒"
こんな風に「調べたい箇所以外をコメントアウトする」ことで、
疑似的にある処理にかかる時間を調べることができます。
もちろんマクロは正しく動きませんし下手するとデータが破損しますので、
バックアップは必ず取ってから行いましょう。
あくまで参考値ですが、遅い箇所の検討が簡単にできますので、
「検証箇所以外をコメントアウト」という手法は覚えておきましょう。
以上でTimer関数の解説を終了します。
プログラムには、
「すべての処理を少しずつ早くするよりも、
最も遅い処理をガッツリ早くした方が全体の処理速度は早くなる」
という性質があります。
手あたり次第高速化してもあまり効果は得られませんので、
まず一番遅い部分を特定することから始めるよう心がけましょう。
こちらの記事も参考にどうぞ
おまけ:For文の分割について
先ほど掲載したこのコード↓
Dim i As Long For i = 1 To 1000 処理A1 処理A2 処理A3 処理A4 処理A5 処理B1 処理B2 処理B3 処理B4 処理B5 Next
AとBのどちらが遅いかを検証するために、
コメントアウトを活用していました。
ただ、このコードはそもそも、
Dim i As Long For i = 1 To 1000 処理A1 処理A2 処理A3 処理A4 処理A5 Next For i = 1 To 1000 処理B1 処理B2 処理B3 処理B4 処理B5 Next
このようにFor文を2つにしても処理速度が変わりません。
人間感覚では「なるべく1つのForにまとめると速くなる」と錯覚しやすいのですが、
よーく考えてみると、iに1を足すという処理1000回分しか違いがありません。
もちろんAB同時に処理していく必要があればこれはできませんが、
別個に処理できる場合は、For文は別に分けてもよいのです。
For文を分割すると、処理時間の計測が楽になるだけでなく、
マクロの修正や改修も格段に楽になります。
ひとつのループでいろんな処理をやらないよう気を付けましょう。
詳しくはこちらをどうぞ。
www.limecode.jp