#12 ループ内ではフィールドを参照しない方がよい?

結論
テーブルのフィールドを参照する場合には、直接的にフィールドを参照するのではなく、Field オブジェクト変数を使うことによって高いパフォーマンスが得られる。

VBAを使うような場面ではしばしばテーブルやクエリーのフィールドを扱うことと思います。この際、「同じフィールドに対してはできる限りその参照回数を少なくする」というのが一般論のようです。簡単な例を示しますと、次のコードでは "rst" というRecordsetオブジェクトから "Field1" という名前のフィールドの値を取得して A、B、C の3つの変数にその値を代入していますが、"Field1"という同じフィールドが3回も直接参照されています。

  A = rst!Field1
  B = rst!Field1
  C = rst!Field1

この例では、次のようなかんじにコードを修正して "Field1"に対する直接の参照回数を1回だけにすれば処理が速くなる、ということを言っているわけです。

  Data = rst!Field1
  A = Data
  B = Data
  C = Data

もちろんさまざまな処理を行うような長いコードの中で1度や2度同じフィールドを参照したからといって大きな差異が出るとは考えられませんが、何千件ものレコードに対してループで処理を繰り返すような場合には、現実的に見逃せないような差異が出ることも考えられます。

というわけで、ここではこのようなフィールドに対する参照回数が具体的にどのくらい処理時間に影響を与えるかを実験を通して確認してみたいと思います。ここではテーブルのフィールドを参照する方法として次の4つを考えてみました。

  1. 毎回Recordsetオブジェクト上のフィールドを直接参照する。
  2. 最初に一度だけRecordsetオブジェクト上のフィールドを参照しそれを変数にセット、以降はその変数を参照する。
  3. あらかじめFieldオブジェクト変数を宣言し、毎回そのオブジェクトを介してフィールドの値を参照する。
  4. あらかじめFieldオブジェクト変数を宣言し、最初に一度だけそのオブジェクトを介してフィールドの値を変数にセット、以降はその変数を参照する。
これらの4つのコードと、それぞれをレコード数1万件のテーブルに対して実行した結果の時間を次に示します。

  1     Dim dbs As Database
  Dim rst As Recordset
  Dim fldData1 As Field, fldData2 As Field
  Dim strData1 As String, sngData2 As Single
  Dim strWrkData1 As String, strWrkData2 As String
  Dim sngWrkData1 As Single, sngWrkData2 As Single
  
  Set dbs = CurrentDb
  Set rst = dbs.OpenRecordset("tblTest")
  ---- 以上は1〜4共通 ----

  With rst
    Do Until .EOF
      'カレントレコードのフィールド値を各変数にセットする
      strWrkData1 = !Data1
      strWrkData2 = !Data1
      sngWrkData1 = Nz(!Data2)
      sngWrkData2 = Nz(!Data2)
      .MoveNext
    Loop
    .MoveFirst
  End With
2.80秒
  2     With rst
    Do Until .EOF
      'カレントレコードのフィールド値を変数にセット
      strData1 = !Data1
      sngData2 = Nz(!Data2)
      'フィールド値の変数を各変数にセット
      strWrkData1 = strData1
      strWrkData2 = strData1
      sngWrkData1 = sngData2
      sngWrkData2 = sngData2
      .MoveNext
    Loop
    .MoveFirst
  End With
1.79秒
  3     With rst
    '各Fieldオブジェクトをセット
    Set fldData1 = .Fields!Data1
    Set fldData2 = .Fields!Data2
    Do Until .EOF
      'Fieldオブジェクトを各変数にセット
      strWrkData1 = fldData1
      strWrkData2 = fldData1
      sngWrkData1 = Nz(fldData2)
      sngWrkData2 = Nz(fldData2)
      .MoveNext
    Loop
    .MoveFirst
  End With
0.86秒
  4     With rst
    '各Fieldオブジェクトをセット
    Set fldData1 = .Fields!Data1
    Set fldData2 = .Fields!Data2
    Do Until .EOF
      'カレントレコードのFieldオブジェクトを変数にセット
      strData1 = fldData1
      sngData2 = Nz(fldData2)
      'フィールド値の変数を各変数にセット
      strWrkData1 = strData1
      strWrkData2 = strData1
      sngWrkData1 = sngData2
      sngWrkData2 = sngData2
      .MoveNext
    Loop
    .MoveFirst
  End With
0.71秒

ご覧のように、明らかにその違いが現れています。特にFieldオブジェクト変数を使うことによって処理時間が大幅に減っているのは特筆すべきことです。テスト1に対してテスト2の時間が1秒も短縮されていることから、Recordsetからフィールドの値を取り出すためにいかに時間がかかっているかが判ると思います。ところがそれ以上に、テスト2とテスト3の時間差は予想以上でした。テスト2の「最初に一度だけRecordsetオブジェクト上のフィールドの値を変数にセットしそれ以降はその変数を参照する」という方法ではそのレコードのフィールドに対しては1度だけしかアクセスしていないことになります。ところが、テスト3ではFieldオブジェクト変数を使っているとはいえ毎回そのオブジェクトを介してフィールド値を参照しているのですから、時間が半減するほどの効果があるとは考えづらいものがあります。Fieldオブジェクトというのは相当最適化されたものであるのでしょう。これは、Fieldオブジェクトを使いさらに参照回数を1回だけにしたテスト4がテスト3より速いとはいえ、テスト2→テスト3ほどは時間が短縮されていないことからも分かります。Fieldオブジェクトを使うだけでかなりのレベルまで最適化されてしまうのです。したがって結論としては、「テーブルのフィールドを参照する場合には、直接的にフィールドを参照するのではなく、Field オブジェクト変数を使うことによって高いパフォーマンスが得られる。」といえるでしょう。
| Index | Prev | Next |

 

Copyright © T'sWare All rights reserved