#25 ファイルの操作方法を比較する

結論
ファイル情報の取得ではVBAの関数が高速、ファイルのコピーや削除などの操作ではWindowsAPIが高速であり、平均的にはFileSystemObjectのパフォーマンスが高い。

VBAのコーディングに限らず、何かをやろうとしたとき、その方法がいくつもあると、いったいどれが一番良い方法か迷うこともしばしばです。方法によって優劣があったとしても、自分なりのスタイルを決めて、常にそれを基準にコーディングするようにすれば、それはそれでよいと思いますが、新しい関数やメソッドなどが出てくると、やはり一度は違いをチェックしてみたくなります。

今回のテストも、Access2000から利用可能となった「FileSystemObject」の登場がその発端です。ファイルのサイズや作成日時を調べたり、ファイルをコピーや削除したりする手段として、Quick BasicなどのDOSの頃からあった(?)関数やステートメント、Windowsから始まったAPIに、さらにこのFileSystemObjectのプロパティやメソッドが仲間入りしたのです。APIを使うために、わざわざDeclareステートメントを使って関数の宣言を行うのも面倒ですし、FileSystemObjectの使い方を新たに覚えるのも面倒かもしれません。そもそも、VBA固有の関数が用意されているのですから、それを使えばいちばん簡単なのかもしれません。でも、パフォーマンスとしての違いはどうなのか、やはり気になるところで、一度その確認をしてみたいと思います。

まず、テスト内容として、どのようなファイル操作を行うか、その手順を説明します。実際のテストでは、これらの操作を1つのプロシージャとして連続して実行します。

  1. 「My Documents」フォルダにある「Test1.MDB」というファイルの更新日時を取得し、変数にセットします
  2. 「Test1.MDB」のファイルサイズを取得し、変数にセットします
  3. 「Test1.MDB」を「Test2.MDB」という名前でコピーします
  4. 「Test2.MDB」を「Test3.MDB」を名前に変更します
  5. 「Test3.MDB」を削除します
これらの操作を行うため、それぞれの手段ごとに、次のような関数やステートメント、メソッドなどを使うこととします。

VBA関数・ステートメント WindowsAPI FileSystemObject
1 FileDateTime関数 GetFileTime関数 DateLastModifiedプロパティ
2 FileLen関数 GetFileSize関数 Sizeプロパティ
3 FileCopyステートメント CopyFile関数 CopyFileメソッド
4 Nameステートメント MoveFile関数 Nameプロパティ
5 Killステートメント DeleteFile関数 DeleteFileメソッド

テストコードは次のようなものです。APIを使うための宣言だけでかなりの量になってしまいましたが、実際に比較するのは、「TestVBA」「TestAPI」「TestFSO」の3つのプロシージャです。それぞれのプロシージャごとに実行して、その時間を測定しています。ファイル操作ですので、ディスクのキャッシュの影響も考えられます。そのため、事前にこれらのコードを一度走らせてから、実測を行うようにしました。また、手順1・2のファイル情報を取得する処理と、手順3・4・5のファイルを操作する処理の2ヵ所で時間測定を行っています。
なお、TestAPIプロシージャについては、エラー処理は省略してかなり簡単に書いてあります。これを応用して、他の何かのプログラムに使われる際には注意してください。


Option Compare Database
Option Explicit

'テスト回数の定数
Const cintLoopMax As Integer = 100

'テストに使うファイルのパス
Const cstrFilePath1 As String = "c:\My Documents\Test1.MDB"
Const cstrFilePath2 As String = "c:\My Documents\Test2.MDB"
Const cstrFilePath3 As String = "c:\My Documents\Test3.MDB"
Const cstrFileName3 As String = "Test3.MDB"

'WindowsAPIで使う構造体、関数などの宣言
Public Type SECURITY_ATTRIBUTES
  nLength As Long
  lpSecurityDescriptor As Long
  bInheritHandle As Long
End Type
Public Type FILETIME
  dwLowDateTime As Long
  dwHighDateTime As Long
End Type
Public Type SYSTEMTIME
  wYear As Integer
  wMonth As Integer
  wDayOfWeek As Integer
  wDay As Integer
  wHour As Integer
  wMinute As Integer
  wSecond As Integer
  wMilliseconds As Integer
End Type
Public Const GENERIC_READ = &H80000000
Public Const OPEN_EXISTING = 3
Public Declare Function CreateFile Lib "kernel32" Alias "CreateFileA" _
  (ByVal lpFileName As String, _
  ByVal dwDesiredAccess As Long, _
  ByVal dwShareMode As Long, _
  lpSecurityAttributes As SECURITY_ATTRIBUTES, _
  ByVal dwCreationDisposition As Long, _
  ByVal dwFlagsAndAttributes As Long, _
  ByVal hTemplateFile As Long) As Long
Public Declare Function GetFileTime Lib "kernel32" _
  (ByVal hFile As Long, _
  lpCreationTime As FILETIME, _
  lpLastAccessTime As FILETIME, _
  lpLastWriteTime As FILETIME) As Long
Public Declare Function FileTimeToLocalFileTime Lib "kernel32" _
  (lpFileTime As FILETIME, _
  lpLocalFileTime As FILETIME) As Long
Public Declare Function FileTimeToSystemTime Lib "kernel32" _
  (lpFileTime As FILETIME, _
  lpSystemTime As SYSTEMTIME) As Long
Public Declare Function CloseHandle Lib "kernel32" _
  (ByVal hObject As Long) As Long
Public Declare Function GetFileSize Lib "kernel32" _
  (ByVal hFile As Long, _
  lpFileSizeHigh As Long) As Long
Public Declare Function CopyFile Lib "kernel32" Alias "CopyFileA" _
  (ByVal lpExistingFileName As String, _
  ByVal lpNewFileName As String, _
  ByVal bFailIfExists As Long) As Long
Public Declare Function MoveFile Lib "kernel32" Alias "MoveFileA" _
  (ByVal lpExistingFileName As String, _
  ByVal lpNewFileName As String) As Long
Public Declare Function DeleteFile Lib "kernel32" Alias "DeleteFileA" _
  (ByVal lpFileName As String) As Long

Sub TestVBA()
'VBA関数およびステートメントによるテストプロシージャ


  Dim dtmFileDate As Date
  Dim lngFileSize As Long
  Dim iintLoop As Integer
  
  ts_Watch "テスト開始", True
  
  For iintLoop = 1 To cintLoopMax
    '更新日時を変数にセット
    dtmFileDate = FileDateTime(cstrFilePath1)
    'ファイルサイズを変数にセット
    lngFileSize = FileLen(cstrFilePath1)
  Next iintLoop
  ts_Watch "VBA関数−ファイル情報取得"
  
  For iintLoop = 1 To cintLoopMax
    'ファイルのコピー
    FileCopy cstrFilePath1, cstrFilePath2
    'ファイルのリネーム
    Name cstrFilePath2 As cstrFilePath3
    'ファイルの削除
    Kill cstrFilePath3
  Next iintLoop
  ts_Watch "VBA関数−ファイル操作"

End Sub

Sub TestAPI()
'Windows API関数によるテストプロシージャ


  Dim dtmFileDate As Date
  Dim lngFileSize As Long
  Dim tSecurityAttributes As SECURITY_ATTRIBUTES
  Dim lngOpenFHwnd As Long
  Dim lngRet As Long
  Dim tCreateTime As FILETIME
  Dim tLastAccessTime As FILETIME
  Dim tLastWriteTime As FILETIME
  Dim tLocalCreateTime As FILETIME
  Dim tLocalCreateSysTime As SYSTEMTIME
  Dim lngFileSizeHigh As Long
  Dim iintLoop As Integer

  ts_Watch "テスト開始", True

  For iintLoop = 1 To cintLoopMax
    'セキュリティ構造体を初期化
    tSecurityAttributes.nLength = Len(tSecurityAttributes)
    'ファイルをオープン
    lngOpenFHwnd = CreateFile(cstrFilePath1, _
                              GENERIC_READ, 0, _
                              tSecurityAttributes, _
                              OPEN_EXISTING, 0, 0)
    'ファイルハンドルが有効なとき
    If lngOpenFHwnd <> -1 Then
      '更新日時の取得
      lngRet = GetFileTime(lngOpenFHwnd, tCreateTime, tLastAccessTime, tLastWriteTime)
      '更新日時をローカルファイル日付へ変換
      lngRet = FileTimeToLocalFileTime(tLastWriteTime, tLocalCreateTime)
      '更新日時をシステムファイル日付へ変換
      lngRet = FileTimeToSystemTime(tLocalCreateTime, tLocalCreateSysTime)
      '更新日時を変数にセット
      With tLocalCreateSysTime
        dtmFileDate = DateSerial(.wYear, .wMonth, .wDay) + _
                        TimeSerial(.wHour, .wMinute, .wSecond)
      End With
      'ファイルサイズを変数にセット
      lngFileSize = GetFileSize(lngOpenFHwnd, lngFileSizeHigh)
      'ファイルをクローズ
      lngRet = CloseHandle(lngOpenFHwnd)
    End If
  Next iintLoop
  ts_Watch "WinAPI−ファイル情報取得"
  
  For iintLoop = 1 To cintLoopMax
    'ファイルのコピー
    lngRet = CopyFile(cstrFilePath1, cstrFilePath2, True)
    'ファイルのリネーム
    lngRet = MoveFile(cstrFilePath2, cstrFilePath3)
    'ファイルの削除
    lngRet = DeleteFile(cstrFilePath3)
  Next iintLoop
  ts_Watch "WinAPI−ファイル操作"
  
End Sub

Sub TestFSO()
'FileSystemObjectオブジェクトによるテストプロシージャ


  Dim dtmFileDate As Date
  Dim lngFileSize As Long
  Dim Fso As Object
  Dim Fl As Object
  Dim iintLoop As Integer
  
  ts_Watch "テスト開始", True
  
  For iintLoop = 1 To cintLoopMax
    Set Fso = CreateObject("Scripting.FileSystemObject")
    Set Fl = Fso.GetFile(cstrFilePath1)
    With Fl
      '更新日時を変数にセット
      dtmFileDate = .DateLastModified
      'ファイルサイズを変数にセット
      lngFileSize = .Size
    End With
  Next iintLoop
  ts_Watch "FileSystemObject−ファイル情報取得"
  
  For iintLoop = 1 To cintLoopMax
    Set Fso = CreateObject("Scripting.FileSystemObject")
    'ファイルのコピー
    Set Fl = Fso.GetFile(cstrFilePath1)
    Fl.Copy cstrFilePath2
    'ファイルのリネーム
    Set Fl = Fso.GetFile(cstrFilePath2)
    Fl.Name = cstrFileName3
    'ファイルの削除
    Set Fl = Fso.GetFile(cstrFilePath3)
    Fl.Delete
  Next iintLoop
  ts_Watch "FileSystemObject−ファイル操作"

End Sub



テスト結果は次の通りです。時間の単位は"秒"です。

VBA関数・ステートメント WindowsAPI FileSystemObject
1・2
ファイル情報の取得
0.05 0.46 0.11
3・4・5
ファイル操作
5.45 4.65 4.89
合計 5.50 5.11 5.00


全体として顕著に感じられるのは、ファイル情報の取得ではVBAの関数・ステートメントが最も速く、ファイル操作では逆にそれが一番遅いということでしょう。特に、ファイル操作の時間が大きいため、合計時間ではVBAの関数・ステートメントが一番遅いという結果になっています。

WindowsAPIは、それとは逆に、ファイル情報の取得が遅く、ファイル操作が速いという結果になっています。おそらく、OSに近い命令であるWindowsAPIは、命令の処理時間そのものはきっと速いものであろうと思います。しかし、結局はVBAから呼び出していますので、そのオーバーヘッド、あるいは呼び出したあとの日付変換処理などに余分な時間を取られていると考えられます。それは、ローカルファイル日付やシステムファイル日付への変換など、他の手段に比べて、コード量そのものが多いことからも想像できます。一方、ファイル操作ではそのようなことはなく、関数を1つ呼び出すだけですので、実際にディスク操作が開始されるまでの時間はかなり速いものと考えられます。

ファイル操作については、その命令の処理時間よりも、ディスク操作の時間、つまりディスクドライブというハードウェアの動作時間がそのほとんどを占めると考えていました。どんな方法でファイル操作を実行しても、ハードウェアは同じなので、この時間には大きな違いは出ないだろうと想定していました。しかし、この結果を見ると、命令の処理時間が意外と影響していることが分かります。あるいは、WindowsAPIにはディスクの動作そのものを高速にする要因が何かあるのかもしれません。

総合的に判断すると、ファイル情報の取得ではVBAの関数が高速、ファイルのコピーや削除などの操作ではWindowsAPIが高速であり、平均的にはFileSystemObjectのパフォーマンスが高い、ということになるでしょう。
| Index | Prev | Next |

 

Copyright © T'sWare All rights reserved