#17 For〜NextとDo〜Loopを比較する

結論
単純なループ処理においても、テーブルのレコードの読み込み(移動)ループにおいても、「For〜Nextステートメント」の方が「Do〜Loopステートメント」よりも高速である。

VBAで同じ処理を繰り返すには"ループステートメント"を使用します。ループステートメントには、同じ処理を指定した回数繰り返す「For〜Nextステートメント」と、指定した条件が真または偽になるまで繰り返す「Do〜Loopステートメント」があります。一般的には繰り返す回数が決まっているかどうかで使い分けますが、コードを工夫することによって、For〜Next でも指定した条件が真または偽になるまで繰り返す処理を行えますし、またDo〜Loopでも指定回数の繰り返しを行うことができます。

例えば、ある処理を100回繰り返す処理では次のような2つの記述方法が可能です。


  Dim iintLoop As Integer

  For iintLoop = 1 To 100
    Debug.Print iintLoop
  Next iintLoop

  iintLoop = 1
  Do Until iintLoop > 100
    Debug.Print iintLoop
    iintLoop = iintLoop + 1
  Loop

また、条件が真(True)になるまで繰り返す処理でも次のような2つの記述方法が可能です。(現在時刻の秒数が5の倍数になったらループを抜ける例)

  Dim iintLoop As Integer

  Do
    Debug.Print Second(Now)
  Loop Until (Second(Now) Mod 5 = 0)

  For iintLoop = 1 To 32000
    '32000の部分にはExit Forが発生し得るまでの十分な大きさの値を設定します
    Debug.Print Second(Now)
    If (Second(Now) Mod 5 = 0) Then Exit For
  Next iintLoop


そこでここでは、上記のように For〜Next と Do〜Loop どちらのケースでも適用できそうな場合に、処理時間の観点からはどちらを使うべきかを判断する基準を知るために、それらの処理時間を測定・比較してみました。テストコードとその結果は次の通りです。ここでは単純に30000回ループするだけの処理をそれぞれについて1000回繰り返す時間を測定しています。

  Dim intLoopCnt As Integer
  Dim iintLoop As Integer
  Const clngTestCnt As Long = 1000  'テスト回数のループカウンタ

  ts_watch "テスト開始", True
  
  For iintLoop = 1 To clngTestCnt
    intLoopCnt = 1
    Do Until intLoopCnt > 30000
      intLoopCnt = intLoopCnt + 1
    Loop
  Next iintLoop
  ts_watch "Do〜Loop"
  
  For iintLoop = 1 To clngTestCnt
    For intLoopCnt = 1 To 30000
    Next intLoopCnt
  Next iintLoop
  ts_watch "For〜Next"

Do〜Loop 22.30 Sec
For〜Next 8.13 Sec
テスト結果からはあきらかに For〜Next の方が高速であることが分かります。これは、For〜Next の場合、指定の回数のループが完了したかの判断が"Next"の行において内部的に行われているのに対して、Do〜Loop の場合には「intLoopCnt > 30000」というプログラマによって記述された条件式を評価しているため、その分の時間が余分にかかっているものと考えられます。しかし、今回の場合はループカウンタを"intLoopCnt = intLoopCnt + 1"のようにインクリメントすることによってループの回数を設定していますので、それに要する時間も考えると処理時間を単純に比較してしまうにはかなり無理があるようにも思えます。そこで次に、Accessでよく使われるレコード読み込みの処理について同様に実験を試みたいと思います。

テストコードは次のようなもので、テーブル"tblTest"の全レコードを2つの方法で読み込みます。フィールド内容の取得操作などはこのテストの目的とはあまり関係がありませんので、実際には「レコード移動に要する時間だけを比較する」ものとなっています。このコードを使って、テーブルに格納されたレコード数が1000、1万、10万の3パターンについてテストしてみます。

まず、Do〜Loop のコードはRecordsetの[EOF]プロパティを使ったもので、サンプルコードではよく出てくるものですので特に説明は要らないでしょう。一方For〜Nextのコードでは、いったん最後のレコードに移動することによってあらかじめRecordsetのレコード数を調べています。これによって For〜Next を使って全レコードの読み込みができるようになります。Do〜Loop を使えば事前にこのような操作を行わずに済みますのでその分 For〜Next の方が時間的には不利になるはずです(特にレコード数が多い場合)。しかし、上記のテストのように単純にループだけを考えると For〜Next の方が高速ですので、いかにレコード数取得のために要した時間をそれがカバーできるかが今回のテストの着目点となります。

  Dim dbs As Database
  Dim rst As Recordset
  Dim lngRecMax As Long
  Dim ilngLoop As Long

  Set dbs = CurrentDb
  
  Set rst = dbs.OpenRecordset("tblTest")
  ts_watch "テスト開始1", True
  With rst
    '読み込みループ
    Do Until .EOF
      .MoveNext
    Loop
  End With
  ts_watch "Do〜Loop"
  rst.Close
  
  Set rst = dbs.OpenRecordset("tblTest")
  ts_watch "テスト開始2", True
  With rst
    'テーブルのレコード数を取得する
    .MoveLast
    lngRecMax = .RecordCount
    .MoveFirst
    '読み込みループ
    For ilngLoop = 1 To lngRecMax
      .MoveNext
    Next ilngLoop
  End With
  ts_watch "For〜Next"
  rst.Close



そして、テスト結果はつぎのようなものになりました。なお時間の単位は"秒"です。

1000レコード 1万レコード 10万レコード
Do〜Loop 0.05 0.08 0.66
For〜Next 0.05 0.05 0.39


ご覧のようにレコード移動におけるループでも For〜Next の方が高速であることが分かりました。特にレコード数が大きくなるとループ回数も増えますので、その差も大きくなることが分かります。レコード数を取得するためにいったん最後のレコードまで移動しまた先頭レコードに戻るという操作にかかった時間は、[MoveNext]メソッドによる個々のレコード移動の時間差によって十分に吸収されています。

しかし、実際のプログラムでは各レコードのフィールド値を取得したり、レコードごとに何らかの処理を施したりすることがほとんどですので、そちらの方に相当の割合の時間がかかるはずです。それに比べれば上記の時間差は微々たるものかも知れません。しかし For〜Next を使うメリットは他にもあります。For〜Next にはそのループに使うカウンタ用の変数がありますので、それを使うことによって「レコード数」あるいは「レコード番号」を管理できるという点です。例えば、Accessのウィンドウのステータスバーに"Syscmd関数"を使って処理の進行状況を示すインジケータを表示したりすることも簡単にできます。Do〜Loopでこれを行うには別途カウンタ変数を用意しなくてはなりませんし、インクリメントする時間も余分にかかってしまいます。

なお、上記の For〜Next のテストではあらかじめレコード数を調べていますが、ここで使用したテスト用のテーブルはオートナンバー型の主キーフィールドとその他の2つのフィールドから成るシンプルなもので、主キーの有無によってはこの「レコード数を取得する」操作に伴う時間も大幅に変わってくる可能性がありますので、Do〜Loop と For〜Next の違いを実際の適用において判断する際には注意が必要です。
| Index | Prev | Next |

 

Copyright © T'sWare All rights reserved