Excel VBA 逆引き集 | 遅い原因の切り分け

Excel VBA
スポンサーリンク

ねらい:Excel VBAの「遅い原因」を素早く特定して、的確に直す

遅さの犯人は、ほぼ「セルへの往復」「描画・再計算」「イベント」「型変換」「文字列連結」のどれかです。感覚ではなく、計測→切り分け→A/B比較で原因を特定しましょう。初心者向けに、その場で貼って使える計測テンプレと、よくあるボトルネックの見つけ方を深掘りして解説します。


計測テンプレート:どこが遅いかを数値で掴む

区間計測(Timerでミリ秒級の測定)

Sub MeasureSections()
    Dim t As Double
    
    t = Timer
    Call ReadData ' 例:入力読み込み
    Debug.Print "ReadData:", Format(Timer - t, "0.000"), "s"
    
    t = Timer
    Call ProcessData ' 例:配列処理
    Debug.Print "ProcessData:", Format(Timer - t, "0.000"), "s"
    
    t = Timer
    Call WriteData ' 例:出力書き戻し
    Debug.Print "WriteData:", Format(Timer - t, "0.000"), "s"
End Sub
VB
  • 重要ポイント(深掘り):
    • 区間ごとに時間を出すと「読み」「処理」「書き戻し」のどこが遅いかが一目で分かる。
    • Debug.Printで即時ウィンドウに表示。継続運用ならログシートへ記録も有効。

反復ベンチ(同じ処理を回して分散を見る)

Sub Benchmark(ByVal runner As String, ByVal rounds As Long)
    Dim i As Long, t0 As Double, t1 As Double, sum As Double
    For i = 1 To rounds
        t0 = Timer
        Application.Run runner
        t1 = Timer
        sum = sum + (t1 - t0)
        Debug.Print runner & " round " & i & ": " & Format(t1 - t0, "0.000") & "s"
    Next
    Debug.Print "Avg:", Format(sum / rounds, "0.000"), "s"
End Sub
VB
  • 重要ポイント(深掘り):
    • 平均時間で“たまたま”を排除。最適化効果の有無が明確になる。
    • Application.RunならSub名を渡して汎用に使える。

よくあるボトルネックの特定と対策(深掘り)

セルへの往復(1セルずつの読み書き)

  • 症状: ループが終わらない/CPUが高止まり。
  • 見つけ方: セル参照を数える(Cells(r, c).Valueがループ内に多用)。
  • 対策: Range→配列で一括読み、配列内で処理、一括書き戻し。
' 悪い例(1セルずつ)
For r = 2 To last
    Cells(r, "E").Value = Cells(r, "A").Value & Cells(r, "B").Value
Next

' 良い例(配列)
Dim arr As Variant: arr = Range("A1").CurrentRegion.Value
' ...配列処理...
Range("E1").Resize(UBound(arr,1), 1).Value = resultCol
VB

描画と再計算(ScreenUpdating/Calculation)

  • 症状: スクロールのように画面がチラつく/数式が多いブックで激遅。
  • 見つけ方: 画面更新ON/計算自動のまま長処理。
  • 対策: 開始時にOFF、終了時に必ずONへ戻す。
Sub AppEnter()
    Application.ScreenUpdating = False
    Application.EnableEvents = False
    Application.Calculation = xlCalculationManual
End Sub

Sub AppLeave()
    Application.Calculation = xlCalculationAutomatic
    Application.EnableEvents = True
    Application.ScreenUpdating = True
End Sub
VB

イベント多発(Change/Calculateの再帰)

  • 症状: 何か書くたびに遅くなる/無限にイベントが走る。
  • 見つけ方: Worksheet_ChangeやCalculateが存在、値書き込みがその中でも行われている。
  • 対策: 処理中はEnableEvents=False、フォームや進捗も必要時のみ更新。

WorksheetFunctionと型の暗黙変換

  • 症状: なぜか数値処理が遅い/計算結果が不正。
  • 見つけ方: Variantだらけ/WorksheetFunction多用。
  • 対策: VBA内蔵関数と正しい型(Double/Long)へ切替。型は明示宣言。
Dim total As Double ' 型を明示
Dim i As Long
For i = 1 To n
    total = total + CDbl(arr(i, 1))
Next
VB

文字列連結の多用

  • 症状: 長い文字列生成で遅い。
  • 見つけ方: s = s & "..."が大ループに存在。
  • 対策: 配列へ入れて最後にJoin。
Dim buf() As String: ReDim buf(1 To rows)
For r = 1 To rows
    buf(r) = CStr(arr(r, 1))
Next
Dim all As String: all = Join(buf, vbCrLf)
VB

Select/Activateの氾濫

  • 症状: 画面がバタつく/余計な待ちが発生。
  • 見つけ方: .Select.Activateが多用されている。
  • 対策: 参照はWithやオブジェクト変数で直接指定。
' 悪い例
Range("A1").Select
Selection.Value = "x"

' 良い例
Worksheets("Sheet1").Range("A1").Value = "x"
VB

切り分けの進め方(順番が肝)

  1. 最適化スイッチON/OFFで比較
    • AppEnter/AppLeave有無で時間差を計測。大差が出るなら描画/再計算が犯人。
  2. I/Oの単位を比較(セルvs配列)
    • 同じ処理をセル往復と配列でA/B比較。桁違いに差が出れば確定。
  3. イベント切断で比較
    • EnableEvents=Falseで速度が上がるならイベントが悪さ。
  4. 型・関数の切替で比較
    • Variant→Double/Long、WorksheetFunction→VBA内蔵へ。数値処理なら効果大。
  5. 進捗・DoEventsの間引き
    • 更新頻度を1〜5%刻みにして比較。呼びすぎなら一気に改善。
  • 重要ポイント(深掘り):
    • 「一度に全部」変えない。1要素ずつ切り替えて差分計測。原因の特定が確実になる。
    • 数値で示せば、改善の説得力が上がる(運用メンバーにも共有しやすい)。

A/B比較の実例テンプレ(そのまま貼って検証)

1セルずつ vs 配列一括

Sub Case_CellByCell()
    Dim ws As Worksheet: Set ws = Worksheets("Input")
    Dim last As Long: last = ws.Cells(ws.Rows.Count, "A").End(xlUp).Row
    Dim t As Double: t = Timer
    Dim r As Long
    For r = 2 To last
        ws.Cells(r, "E").Value = ws.Cells(r, "A").Value & ws.Cells(r, "B").Value
    Next
    Debug.Print "CellByCell:", Format(Timer - t, "0.000"), "s"
End Sub

Sub Case_ArrayIO()
    Dim ws As Worksheet: Set ws = Worksheets("Input")
    Dim arr As Variant: arr = ws.Range("A1").CurrentRegion.Value
    Dim rows As Long: rows = UBound(arr, 1)
    Dim out() As Variant: ReDim out(1 To rows, 1 To 1)
    Dim t As Double: t = Timer
    Dim r As Long
    For r = 2 To rows
        out(r, 1) = CStr(arr(r, 1)) & CStr(arr(r, 2))
    Next
    ws.Range("E1").Resize(rows, 1).Value = out
    Debug.Print "ArrayIO:", Format(Timer - t, "0.000"), "s"
End Sub
VB

進捗更新の頻度比較

Sub Case_Progress_Frequent()
    Dim total As Long: total = 50000
    Dim t As Double: t = Timer
    Dim i As Long
    For i = 1 To total
        Application.StatusBar = "進捗 " & Format(i / total, "0%")
        DoEvents
    Next
    Debug.Print "Frequent:", Format(Timer - t, "0.000"), "s"
End Sub

Sub Case_Progress_Throttled()
    Dim total As Long: total = 50000
    Dim stepN As Long: stepN = Application.WorksheetFunction.Max(1, total \ 100)
    Dim t As Double: t = Timer
    Dim i As Long
    For i = 1 To total
        If i Mod stepN = 0 Then
            Application.StatusBar = "進捗 " & Format(i / total, "0%")
            DoEvents
        End If
    Next
    Application.StatusBar = False
    Debug.Print "Throttled:", Format(Timer - t, "0.000"), "s"
End Sub
VB
  • 重要ポイント(深掘り):
    • 目で見るのではなく秒数で比較。効果が明確なら採用判断が容易。
    • 進捗は「体感の良さ」と「速度」の両立を狙い、刻み幅を調整する。

ログ・可視化で「再現性」を確保する

Sub LogTime(ByVal label As String, ByVal sec As Double)
    Dim ws As Worksheet
    On Error Resume Next
    Set ws = ThisWorkbook.Worksheets("PerfLog")
    If ws Is Nothing Then Set ws = ThisWorkbook.Worksheets.Add: ws.Name = "PerfLog"
    On Error GoTo 0
    Dim r As Long: r = ws.Cells(ws.Rows.Count, "A").End(xlUp).Row + 1
    ws.Cells(r, 1).Value = Format(Now, "yyyy-mm-dd HH:NN:SS")
    ws.Cells(r, 2).Value = label
    ws.Cells(r, 3).Value = sec
End Sub
VB
  • 重要ポイント(深掘り):
    • 計測値をシートに残して、変更前後で比較できるようにする。
    • 時刻・ラベル・秒数の3点記録だけで十分有効。グラフ化すれば傾向も見える。

現場チェックリスト(遅さの犯人を潰す順番)

  • 描画・イベント・計算を止めたか?(AppEnter/AppLeave)
  • セル往復を配列へ置換したか?
  • DoEvents/進捗更新を間引いたか?
  • 型を明示して暗黙変換を減らしたか?
  • Select/Activateを書き換えたか?
  • フィルタやテーブルで対象行を絞ったか?
  • チャンク処理に分割したか?(超大規模時)
  • 計測値を残して効果を確認したか?
  • 重要ポイント(深掘り):
    • 「やったつもり」では原因が残る。チェックリストを順に潰すと漏れがなくなる。
    • 数値を残して改善を“資産化”。次のプロジェクトでも即応用できる。

スターター手順(最短導入)

  1. 区間計測(Timer)を入れて、遅い区間を特定。
  2. AppEnter/AppLeaveで描画・イベント・計算を制御。
  3. セル往復を配列I/Oへ置換し、A/B比較で効果確認。
  4. 進捗・DoEventsを1〜5%刻みに間引く。
  5. 型明示(Double/Long/String)とSelect排除でさらに詰める。
  6. PerfLogへ計測値を記録し、変更前後の差を見える化。

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