スタイル

For Each 構文でワードVBAを高速化する-速度比較ワード版

エクセルVBAの高速化については、さまざまな記事がネット上に公開されています。

エクセルVBAについては、条件にもよりますが、「Do While」も「For Next」も「For Each」も大差はなく、より高速化を図りたければ「配列」を使うのが良いようです。

しかし、ワードVBAについては、「Do While」や「For Next」よりも「For Each」の方がずっと速く、場合によっては「配列」よりも高速です。
このため、VBAアセットの「VA公用文」では、「For Each」を多用して、処理の高速化を図っています。

具体的な速度の差異については、次の2つのテストの結果をご確認ください。

テスト1:文字の挿入

まずは、はじめに紹介した「速度比較決定版」(エクセルを対象にしたもの)にできるだけ近いコードで比較します。

ただし、ワードVBAでは段落のTextに改行記号まで含まれてしまうことや、「For Each」と共通の処理を行う必要があることなどから、あらかじめ「X」という文字が入力されている500個の段落に、1から500までの数字を入力しています。

Sub test1()    'テスト全体の制御
    Const lngMax As Long = 500
    Application.ScreenUpdating = False
    Call rstPars1(lngMax)
    Call test1_1(lngMax)
    Call rstPars1(lngMax)
    Call test1_2(lngMax)
    Call rstPars1(lngMax)
    Call test1_3(lngMax)
    Call rstPars1(lngMax)
    Call test1_4(lngMax)
    Call rstPars1(lngMax)
    Call test1_5(lngMax)
    Application.ScreenUpdating = True
End Sub

Sub rstPars1(max As Long)   '段落の初期化
    Dim par As Paragraph
    For Each par In Paragraphs
        par.Range.Delete
    Next
    Dim i As Long
    For i = 1 To max
        Paragraphs(i).Range.Text = "x" + vbCr
    Next i
    Paragraphs(max + 1).Range.Delete    '最後の余分な改行を削除しています。
End Sub

Sub test1_1(max As Long)    'テスト1「Select」
    Dim sngTime As Single
    sngTime = Timer
    Dim i As Long
    For i = 1 To max
        Paragraphs(i).Range.Select
        Selection.Range.Characters.First = i
    Next i
    Debug.Print "test1_1(Select):" & Timer - sngTime
End Sub

Sub test1_2(max As Long)    'テスト2「For Next」
    Dim sngTime As Single
    sngTime = Timer
    Dim i As Long
    For i = 1 To max
        Paragraphs(i).Range.Characters.First = i
    Next i
    Debug.Print "test1_2(ForNext):" & Timer - sngTime
End Sub

Sub test1_3(max As Long)    'テスト3「Do While」
    Dim sngTime As Single
    sngTime = Timer
    Dim i As Long
    i = 1
    Do While i <= max
        Paragraphs(i).Range.Characters.First = i
        i = i + 1
    Loop
    Debug.Print "test1_3(DoLoop):" & Timer - sngTime
End Sub

Sub test1_4(max As Long)    'テスト4「For Each」
    Dim sngTime As Single
    sngTime = Timer
    Dim i As Long
    Dim par As Paragraph
    For Each par In Paragraphs
        i = i + 1
        par.Range.Characters.First = i
    Next
    Debug.Print "test1_4(ForEach):" & Timer - sngTime
End Sub

Sub test1_5(max As Long)    'テスト5「配列」
    Dim sngTime As Single
    sngTime = Timer
    Dim i As Long
    Dim MyAry() As Paragraph
    ReDim MyAry(1 To max)
    For i = 1 To max
        Set MyAry(i) = Paragraphs(i)
    Next i
    For i = 1 To max
        MyAry(i).Range.Characters.First = i
    Next i
    Debug.Print "test1_5(Array):" & Timer - sngTime
End Sub

結果は、次のとおりです(単位:秒)。

test1_1(Select):    5.625
test1_2(ForNext):   2.320313
test1_3(DoLoop):    2.382813
test1_4(ForEach):   0.515625
test1_5(Array):     2.21875

「Select」が遅いのはエクセルと同じですが、「For Next」や「Do Loop」に比べて「For Each」が圧倒的に速いのが分かります。
また、「配列」(オブジェクト配列)は、「For Next」や「Do Loop」よりも、やや速い程度にとどまります。

テスト2:インデントの設定

次にVBAアセットが公開している「VA公用文」で主体となるインデントの設定について、同じように比較します。
1から500までの番号が入力された500個の段落に、10ポイントの左インデントを設定しています。

Sub test2()    'テスト全体の制御
    Const lngMax As Long = 500
    Application.ScreenUpdating = False
    Call rstPars2(lngMax)
    Call test2_1(lngMax)
    Call rstPars2(lngMax)
    Call test2_2(lngMax)
    Call rstPars2(lngMax)
    Call test2_3(lngMax)
    Call rstPars2(lngMax)
    Call test2_4(lngMax)
    Call rstPars2(lngMax)
    Call test2_5(lngMax)
    Application.ScreenUpdating = True
End Sub

Sub rstPars2(max As Long)    '段落の初期化
    Dim par As Paragraph
    For Each par In Paragraphs
        par.Range.Delete
    Next
    Dim i As Long
    For i = 1 To max
        Paragraphs(i).Range.Text = i & vbCr
    Next i
    Paragraphs(max + 1).Range.Delete    '最後の余分な改行を削除しています。
End Sub

Sub test2_1(max As Long)    'テスト1「Select」
    Dim sngTime As Single
    sngTime = Timer
    Dim i As Long
    For i = 1 To max
        Paragraphs(i).Range.Select
        Selection.Paragraphs.LeftIndent = 10
    Next i
    Debug.Print "test2_1(Select):" & Timer - sngTime
End Sub

Sub test2_2(max As Long)    'テスト2「For Next」
    Dim sngTime As Single
    sngTime = Timer
    Dim i As Long
    For i = 1 To max
        Paragraphs(i).LeftIndent = 10
    Next i
    Debug.Print "test2_2(ForNext):" & Timer - sngTime
End Sub

Sub test2_3(max As Long)    'テスト3「Do While」
    Dim sngTime As Single
    sngTime = Timer
    Dim i As Long
    i = 1
    Do While i <= max
        Paragraphs(i).LeftIndent = 10
        i = i + 1
    Loop
    Debug.Print "test2_3(DoLoop):" & Timer - sngTime
End Sub

Sub test2_4(max As Long)    'テスト4「For Each」
    Dim sngTime As Single
    sngTime = Timer
    Dim i As Long
    Dim par As Paragraph
    For Each par In Paragraphs
        par.LeftIndent = 10
    Next
    Debug.Print "test2_4(ForEach):" & Timer - sngTime
End Sub

Sub test2_5(max As Long)    ''テスト5「配列」
    Dim sngTime As Single
    sngTime = Timer
    Dim i As Long
    Dim MyAry() As Paragraph
    ReDim MyAry(1 To max)
    For i = 1 To max
        Set MyAry(i) = Paragraphs(i)
    Next i
    For i = 1 To max
        MyAry(i).LeftIndent = 10
    Next i
    Debug.Print "test2_5(Array):" & Timer - sngTime
End Sub

結果は、次のとおりです(単位:秒)。

test2_1(Select):    6.210938
test2_2(ForNext):   2.859375
test2_3(DoLoop):    2.820313
test2_4(ForEach):   0.8828125
test2_5(Array):     2.40625

テスト1と同じく、「For Each」が一番速いのが分かります。

この記事は、ノンプロ研(https://nonproken.shop)の「VBA研究会」での研究成果に基づくものです。
研究にご参加いただけた会員の皆さん、ありがとうございました。

コメント

  1. 管理人 より:

    配列のやり方は、他にもっと良い方法があるかも知れません。
    今回行った5つのパターン以外にも、比較テストを行うべきものがあるかも知れません。
    なにかアイデアがありましたら、ぜひ、教えてください。

  2. 管理人 より:

    ForEachを使うことではなく段落を変数に入れたことが影響している可能性もあるため、こちらも試してみましたが、テスト5のFor Nextの結果とほどんど変化がありませんでした。
    Sub test1_6(max As Long) ‘テスト6「For Next-2」
    Dim sngTime As Single
    sngTime = Timer
    Dim i As Long
    Dim pars As Paragraphs
    Set pars = ActiveDocument.Paragraphs
    For i = 1 To max
    pars(i).Range.Characters.First = i
    Next
    Debug.Print “test1_6(ForNext2):” & Timer – sngTime
    End Sub

  3. 管理人 より:

    なぜ、ForEachがForNextより速いのかは分かりません。
    ワードはエクセルと違って、マクロで行った操作も一つずつCtrl+Zでアンドゥできるので、そういったところが影響しているのかもしれませんが、ForEachでもForNextと同じようにアンドゥできてしまいます。
    不思議です。

タイトルとURLをコピーしました