和風スパゲティのレシピ

日本語でコーディングするExcelVBA

マクロの処理時間を計測する-Timer関数の使い方

マクロの処理時間を計測する方法を解説します。

現在時刻を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関数の解説を終了します。


プログラムには、

すべての処理を少しずつ早くするよりも、
最も遅い処理をガッツリ早くした方が全体の処理速度は早くなる

という性質があります。


手あたり次第高速化してもあまり効果は得られませんので、
まず一番遅い部分を特定することから始めるよう心がけましょう。


こちらの記事も参考にどうぞ

www.limecode.jp

おまけ: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