#21 テキストボックスでの長い文字列を考える

結論
テキストボックスコントロールに長い文字列を代入する場合には、1つずつ直接 Valueプロパティ に代入するのではなく、あらかじめ文字列型変数にそれらをまとめておいて、一回で(なるべく少ない回数で)その変数値を Valueプロパティ に代入するという手順がよい。

すでに「#13 ループ内ではコントロールを参照しない方がよい? 」で試みたように、フォームのコントロールのプロパティ値を参照するには時間がかかります。ところで、プロパティにも変数や定数と同様にいくつかのデータ型があります。例えば、[Caption/標題]プロパティは文字列です。また、[Height/高さ]や[Width/幅]プロパティは数値です。数値の場合、データ型によってメモリサイズは異なるものの、極端に大きなメモリサイズ(数値の大きさではありません)はありませんが、文字列の場合には相当大きなサイズを扱う場合があります。その代表例がテキストボックスコントロールのデフォルトプロパティである[Value]プロパティでしょう。テキストボックスは何文字かのデータを入力したいようなときに多用するコントロールではありますが、テキストファイルの内容をまとめて読み込んで、表示・編集するような用途にも使えます。そしてそれらのデータを代入するのが[Value]プロパティです。

テストに使ったフォーム一般に文字列型のデータ処理には時間がかかります。この処理時間は、コントロールのプロパティ値として文字列を扱う場合にはどうなのか、ここではファイルから読み込んだ長い文字列をテキストボックスに代入するいくつかのテストパターンを通して、そのパフォーマンスについて調べてみたいと思います。

ここでのテスト内容は次の3つです。読み込むテキストファイルにはちょうど100KBのサイズのものを用意しました。

  1. Do〜Loopでファイルを1行ずつ読み込み、1行ずつテキストボックスのValueプロパティの後ろに連結していく。
  2. 上記の処理で画面の再描画を抑止する。
  3. Do〜Loopでファイルを1行ずつ読み込み、1行ずつ文字列型の変数の後ろに連結していく。そして最後にまとめてテキストボックスのValueプロパティにセットする。

これらの3つのテストコードを次に示します。"txtFileData"がテキストボックスの名前です。


Private Sub cmdFileRead_Click()

  Dim lngFileNum  As Long
  Dim strFileData As String
  Dim strTmpData As String
  Const cstrFileName As String = "d:\tmp\test.txt"

  ts_Watch "テスト開始", True

  'Do〜Loopでファイルを1行ずつ読み込み、
  '1行ずつテキストボックスのプロパティの後ろに連結
  Me!txtFileData.Value = ""
  lngFileNum = FreeFile
  Open cstrFileName For Input As #lngFileNum
  Do Until EOF(lngFileNum)
    Line Input #lngFileNum, strFileData
    Me!txtFileData.Value = Me!txtFileData.Value & strFileData & vbCrLf
  Loop
  Close #lngFileNum
  ts_Watch "テスト1"

  '画面再描画を抑止
  Echo False
  Me!txtFileData.Value = ""
  lngFileNum = FreeFile
  Open cstrFileName For Input As #lngFileNum
  Do Until EOF(lngFileNum)
    Line Input #lngFileNum, strFileData
    Me!txtFileData.Value = Me!txtFileData.Value & strFileData & vbCrLf
  Loop
  Close #lngFileNum
  Echo True
  ts_Watch "テスト2"

  'Do〜Loopでファイルを1行ずつ読み込み、
  '1行ずつ文字列型の変数の後ろに連結、
  '最後にまとめてテキストボックスのValueプロパティにセット
  strTmpData = ""
  lngFileNum = FreeFile
  Open cstrFileName For Input As #lngFileNum
  Do Until EOF(lngFileNum)
    Line Input #lngFileNum, strFileData
    strTmpData = strTmpData & strFileData & vbCrLf
  Loop
  Close #lngFileNum
  Me!txtFileData.Value = strTmpData
  ts_Watch "テスト3"

End Sub

※テキストボックスの値を取得する際に[Value]プロパティを明示的に記述していますが、 このプロパティはテキストボックスのデフォルトプロパティですので省略しても問題ありません。

テスト結果は次のようなものになりました。

処理時間(Sec)
テスト1 30.7
テスト2 30.4
テスト3 12.9

数値を見ればお分かりのように、「コントロールのプロパティ値を参照するには時間がかかる」ということを再認識させられる結果となりました。テキストボックスの[Value]プロパティも一つの文字列型変数と考えれば、テストの1と3はまったく同じものと見ることができます。ところがご覧のように大きな時間差が出る結果となりました。このテストではテキストボックスに値をセットしている操作が中心のように思えますが、よく見ると分かりますが、実際には現在のテキストボックスのプロパティ値を参照してそれに文字列を結合、その結果を再セットする、という動作をさせています。つまり代入だけでなくコントロールの"参照"も同じ回数行っているのです。このことから、"コントロールという1つのオブジェクト"を参照する動作、オブジェクトからプロパティ値を取り出したりセットしたりする動作、これらは文字列型変数に対して参照・代入を行う場合と比較して、時間的に非常に大きなコストがかかるということがいえます。

なお、2番目の画面の再描画を抑止したテストは、テキストボックスの内容が更新されるたびにコントロールが再描画されることによるオーバーヘッドをなくしてみたらどうなるか、という考えで行ってみたのですが、実際にはFor〜Nextで高速にループ処理されるため、DoEventsでも入れない限りは再描画が行われることはないようで、時間にはそれほど影響しませんでした(それでも影響がゼロではありませんでしたが)。

今回はあくまでも文字列処理の時間を中心に実験してみましたが、本来は、より速くファイルを読み込む方法も十分検討しなければなりません。例えば上記のテストを次のコードで実行してみました。Input関数を使って、ファイルサイズ分のデータを一度に読み込む方法です。


  lngFileNum = FreeFile
  Open cstrFileName For Binary As #lngFileNum
  Me!txtFileData.Value = Input(LOF(lngFileNum), #lngFileNum)
  Close #lngFileNum
  ts_Watch "テスト4"


その結果は、3番目のテストの実に約10分の1、 1.8 秒でした。このことから、データベースへのアクセスも含めてディスクアクセスがいかなる場合もパフォーマンスのボトルネックになる、という認識が重要です。
| Index | Prev | Next |

 

Copyright © T'sWare All rights reserved