#17 | For〜NextとDo〜Loopを比較する | |||||||||||||||||
VBAで同じ処理を繰り返すには"ループステートメント"を使用します。ループステートメントには、同じ処理を指定した回数繰り返す「For〜Nextステートメント」と、指定した条件が真または偽になるまで繰り返す「Do〜Loopステートメント」があります。一般的には繰り返す回数が決まっているかどうかで使い分けますが、コードを工夫することによって、For〜Next でも指定した条件が真または偽になるまで繰り返す処理を行えますし、またDo〜Loopでも指定回数の繰り返しを行うことができます。 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" テスト結果からはあきらかに 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 そして、テスト結果はつぎのようなものになりました。なお時間の単位は"秒"です。 ご覧のようにレコード移動におけるループでも For〜Next の方が高速であることが分かりました。特にレコード数が大きくなるとループ回数も増えますので、その差も大きくなることが分かります。レコード数を取得するためにいったん最後のレコードまで移動しまた先頭レコードに戻るという操作にかかった時間は、[MoveNext]メソッドによる個々のレコード移動の時間差によって十分に吸収されています。 しかし、実際のプログラムでは各レコードのフィールド値を取得したり、レコードごとに何らかの処理を施したりすることがほとんどですので、そちらの方に相当の割合の時間がかかるはずです。それに比べれば上記の時間差は微々たるものかも知れません。しかし For〜Next を使うメリットは他にもあります。For〜Next にはそのループに使うカウンタ用の変数がありますので、それを使うことによって「レコード数」あるいは「レコード番号」を管理できるという点です。例えば、Accessのウィンドウのステータスバーに"Syscmd関数"を使って処理の進行状況を示すインジケータを表示したりすることも簡単にできます。Do〜Loopでこれを行うには別途カウンタ変数を用意しなくてはなりませんし、インクリメントする時間も余分にかかってしまいます。 なお、上記の For〜Next のテストではあらかじめレコード数を調べていますが、ここで使用したテスト用のテーブルはオートナンバー型の主キーフィールドとその他の2つのフィールドから成るシンプルなもので、主キーの有無によってはこの「レコード数を取得する」操作に伴う時間も大幅に変わってくる可能性がありますので、Do〜Loop と For〜Next の違いを実際の適用において判断する際には注意が必要です。 |
||||||||||||||||||
|
Copyright © T'sWare All rights reserved |