<p><!--META
{
"title": "VBAにおけるWin32 API(QueryPerformanceCounter)を利用した高精度・長時間処理計測の実装と性能チューニング",
"primary_category": "VBA/Office",
"secondary_categories": ["Windows API","性能計測"],
"tags": ["QueryPerformanceCounter","QueryPerformanceFrequency","PtrSafe","Currency","Excel","Access"],
"summary": "VBA標準タイマーの精度問題を解決するため、QueryPerformanceCounter APIを用いた高精度タイマーの実装と、それを活用したOffice自動化処理の性能チューニング手法を解説します。",
"mermaid": true,
"verify_level": "L0",
"tweet_hint": {"text":"VBAで長時間処理やミリ秒単位の正確な性能計測が必要ですか?Win32 APIのQueryPerformanceCounterを使えば、標準のTimer関数よりはるかに高精度な計測が可能です。64bit対応とCurrency型での実装テクニックを解説。","hashtags":["#VBA","#Excel","#Access","#Win32API"]},
"link_hints": ["https://learn.microsoft.com/en-us/windows/win32/api/profileapi/nf-profileapi-queryperformancecounter"]
}
-->
本記事は<strong>Geminiの出力をプロンプト工学で整理した業務ドラフト(未検証)</strong>です。</p>
<h2 class="wp-block-heading">VBAにおけるWin32 API(QueryPerformanceCounter)を利用した高精度・長時間処理計測の実装と性能チューニング</h2>
<h3 class="wp-block-heading">1. 背景と要件</h3>
<p>VBA(Excel, Access)による定型業務の自動化において、数秒から数時間に及ぶ長時間処理のボトルネック特定や性能比較は不可欠です。しかし、VBA標準の <code>Timer</code> 関数は、精度が秒単位であり、特に高速な処理の差分計測や、数時間レベルの長時間計測には向いていません(約24時間でリセットされるという制限もあります)。</p>
<p>実務レベルでの高精度な計測を実現するためには、Windows OSが提供する高分解能パフォーマンスカウンタ(High-Resolution Performance Counter: HRPC)へのアクセスが必要です。</p>
<p><strong>要件</strong>:</p>
<ol class="wp-block-list">
<li><p>VBA標準機能および外部ライブラリを使用せず、Win32 APIのみで実装する。</p></li>
<li><p>Office 64ビット環境に対応するため、<code>PtrSafe</code> を宣言する。</p></li>
<li><p>高精度タイマー(マイクロ秒オーダー)を実装し、長時間計測にも対応させる。</p></li>
<li><p>ExcelとAccessの具体的な最適化手法(ScreenUpdating, 配列、DAO/ADO)の前後比較を計測する。</p></li>
</ol>
<h3 class="wp-block-heading">2. 設計:QueryPerformanceCounterの利用と64bit対応</h3>
<p>高精度な計測には、Win32 APIの以下の2つの関数を使用します。</p>
<ol class="wp-block-list">
<li><p><code>QueryPerformanceFrequency (QPF)</code>: カウンタが1秒あたりにインクリメントされる回数(周波数 Hz)を取得します。この値はシステム起動後、通常固定です。</p></li>
<li><p><code>QueryPerformanceCounter (QPC)</code>: 現在のカウンタ値(64ビット整数)を取得します。</p></li>
</ol>
<p>経過時間(秒)は <code>(QPC_End - QPC_Start) / QPF</code> で計算されます。</p>
<h4 class="wp-block-heading">64ビットデータ型の取り扱い</h4>
<p>QPCおよびQPFは64ビット整数(Large Integer)を返します。VBAの標準 <code>Long</code> 型(32ビット)ではオーバーフローするため、64ビット値を安全に扱う必要があります。</p>
<p>最も一般的な解決策は、VBAの <code>Currency</code> 型(内部的に64ビット整数として扱われるが、小数点以下4桁固定の固定小数点数)を使用する手法です[4]。この方法を使用すると、ポインタ操作を複雑にせずに高精度カウンタを格納し、VBA内で演算処理を行うことが可能になります。</p>
<h4 class="wp-block-heading">処理の流れ (Mermaid Flowchart)</h4>
<p>長時間にわたる複雑なデータ処理の計測フローは以下のようになります。</p>
<div class="wp-block-merpress-mermaidjs diagram-source-mermaid"><pre class="mermaid">
graph TD
A["開始: 計測対象プロシージャ呼び出し"] --> B{"API宣言と環境確認"};
B -- 周波数取得 --> C["計測開始: QPC_Start"];
C --> D1("チューニング前処理");
D1 --> E1["計測終了1: QPC_End1"];
E1 --> F1{"経過時間計算1: (End1-Start) / Hz"};
F1 --> G("チューニング処理実施");
G --> C;
C --> D2("チューニング後処理");
D2 --> E2["計測終了2: QPC_End2"];
E2 --> F2{"経過時間計算2: (End2-Start) / Hz"};
F2 --> H["結果表示: チューニング効果(秒)"];
H --> I["終了"];
</pre></div>
<h3 class="wp-block-heading">3. 実装:高精度タイマーモジュール</h3>
<p>標準モジュール(例: <code>mod_HighResTimer</code>)に以下のAPI宣言とラッパー関数を記述します。</p>
<pre data-enlighter-language="generic">' // mod_HighResTimer.bas
#If VBA7 Then
' 64bit/32bit 環境対応 (Office 2010以降)
Private Declare PtrSafe Function QueryPerformanceCounter Lib "kernel32" ( _
lpPerformanceCount As Currency) As Long
Private Declare PtrSafe Function QueryPerformanceFrequency Lib "kernel32" ( _
lpFrequency As Currency) As Long
#Else
' 32bit 環境対応 (非推奨だが互換性のために残す)
Private Declare Function QueryPerformanceCounter Lib "kernel32" ( _
lpPerformanceCount As Currency) As Long
Private Declare Function QueryPerformanceFrequency Lib "kernel32" ( _
lpFrequency As Currency) As Long
#End If
' グローバル変数として周波数を保持し、何度もAPIを叩くのを避ける
Private g_lngFrequency As Currency
Private Const PRECISION_FACTOR As Double = 10000# ' Currency型調整係数
' ------------------------------------------------------------------
' 1. カウンタ周波数を取得 (初回起動時に一度だけ実行)
' ------------------------------------------------------------------
Private Function GetFrequency() As Currency
If g_lngFrequency = 0 Then
If QueryPerformanceFrequency(g_lngFrequency) = 0 Then
' 取得失敗時のエラー処理
Err.Raise 5, "QueryPerformanceFrequency", "パフォーマンスカウンタの周波数取得に失敗しました。"
End If
End If
GetFrequency = g_lngFrequency
End Function
' ------------------------------------------------------------------
' 2. 現在のカウンタ値を取得 (計測開始/終了に使用)
' ------------------------------------------------------------------
Public Function GetQPC() As Currency
Dim lngCounter As Currency
If QueryPerformanceCounter(lngCounter) = 0 Then
Err.Raise 5, "QueryPerformanceCounter", "パフォーマンスカウンタの取得に失敗しました。"
End If
GetQPC = lngCounter
End Function
' ------------------------------------------------------------------
' 3. 経過時間を計算 (秒単位)
' StartTime/EndTime は GetQPC() の戻り値
' ------------------------------------------------------------------
Public Function CalculateElapsedTime(StartTime As Currency, EndTime As Currency) As Double
Dim lngFreq As Currency
lngFreq = GetFrequency()
' Currency型で受け取ったカウンタ値は、本来の値の10000倍になっている。
' これを周波数で割り、さらに10000で割って秒に変換する。
' 内部計算: (EndTime * 10000 - StartTime * 10000) / (Freq * 10000)
' 簡略化: (EndTime - StartTime) / Freq
' ※ Currency型同士の減算・除算はVBAが内部で調整するため、このままでよい
CalculateElapsedTime = CDbl((EndTime - StartTime) / lngFreq)
End Function
</pre>
<h3 class="wp-block-heading">4. 検証:Excelでの大量データ処理計測とチューニング</h3>
<p>Excelで、数千行のセルに値を書き込む処理の高速化(配列利用)の効果を計測します。</p>
<pre data-enlighter-language="generic">' // 標準モジュール (例: mod_Main)
Sub Measure_ExcelPerformance()
Const ROWS_COUNT As Long = 50000
Dim ws As Worksheet
Set ws = ThisWorkbook.Sheets(1)
Dim tStart As Currency, tEnd As Currency
Dim dTime1 As Double, dTime2 As Double
Application.ScreenUpdating = False
'--- 処理1: セル直接書き込み (チューニング前) ---
tStart = GetQPC()
Dim i As Long
For i = 1 To ROWS_COUNT
ws.Cells(i, 1).Value = "Value_" & i
Next i
tEnd = GetQPC()
dTime1 = CalculateElapsedTime(tStart, tEnd)
'--- 処理2: 配列による一括書き込み (チューニング後) ---
ws.Columns(1).ClearContents ' リセット
Dim vData(1 To ROWS_COUNT, 1 To 1) As Variant
tStart = GetQPC()
For i = 1 To ROWS_COUNT
vData(i, 1) = "Value_" & i
Next i
ws.Cells(1, 2).Resize(ROWS_COUNT, 1).Value = vData ' 一括書き込み
tEnd = GetQPC()
dTime2 = CalculateElapsedTime(tStart, tEnd)
Application.ScreenUpdating = True
'--- 結果表示 ---
Debug.Print "--- Excel 50,000行書き込み計測 ---"
Debug.Print "1. セル直接書き込み (未チューニング): " & Format$(dTime1, "0.00000") & " 秒"
Debug.Print "2. 配列バッファ一括書き込み (チューニング済): " & Format$(dTime2, "0.00000") & " 秒"
Debug.Print "改善率: " & Format$((dTime1 / dTime2), "0.0") & " 倍"
End Sub
</pre>
<p>(想定される結果:処理1は数秒、処理2は数十ミリ秒となり、改善率は100倍以上になることが多い。)</p>
<h3 class="wp-block-heading">5. 検証:AccessでのDAO処理計測と最適化</h3>
<p>Accessで大量のレコードセットを操作する際の処理時間と、<code>dbUseNoLocks</code> の効果を計測します。</p>
<pre data-enlighter-language="generic">' // 標準モジュール (Access VBA)
Sub Measure_AccessPerformance()
Dim db As DAO.Database
Dim rs As DAO.Recordset
Dim strSQL As String
' ここでは「T_TargetData」テーブルが存在すると仮定
strSQL = "SELECT ID, DataField FROM T_TargetData WHERE DataField IS NULL;"
Set db = CurrentDb
Dim tStart As Currency, tEnd As Currency
Dim dTime1 As Double, dTime2 As Double
Dim lngCount As Long
'--- 処理1: デフォルトロックでループ処理 ---
tStart = GetQPC()
' デフォルトロック (dbOpenDynaset)
Set rs = db.OpenRecordset(strSQL, dbOpenDynaset)
If Not rs.EOF Then
rs.MoveLast
rs.MoveFirst
Do While Not rs.EOF
' 何らかの更新処理をシミュレート
lngCount = lngCount + 1
rs.MoveNext
Loop
End If
rs.Close
tEnd = GetQPC()
dTime1 = CalculateElapsedTime(tStart, tEnd)
'--- 処理2: dbUseNoLocks オプションでループ処理 (チューニング後) ---
lngCount = 0
tStart = GetQPC()
' dbUseNoLocks (読み取り専用または一括処理に適したオプション)
Set rs = db.OpenRecordset(strSQL, dbOpenDynaset, dbUseNoLocks)
If Not rs.EOF Then
rs.MoveLast
rs.MoveFirst
Do While Not rs.EOF
lngCount = lngCount + 1
rs.MoveNext
Loop
End If
rs.Close
tEnd = GetQPC()
dTime2 = CalculateElapsedTime(tStart, tEnd)
'--- 結果表示 ---
Debug.Print "--- Access DAO レコードセット計測 (" & lngCount & "件) ---"
Debug.Print "1. デフォルトロック (未チューニング): " & Format$(dTime1, "0.00000") & " 秒"
Debug.Print "2. dbUseNoLocks 使用 (チューニング済): " & Format$(dTime2, "0.00000") & " 秒"
Debug.Print "改善率 (主にロック待機時間の削減): " & Format$((dTime1 / dTime2), "0.00") & " 倍"
Set rs = Nothing
Set db = Nothing
End Sub
</pre>
<p>(注: <code>dbUseNoLocks</code> の効果は、ネットワーク越しや競合が多い環境で顕著になりますが、高精度タイマーにより僅かな改善でも検出可能になります。)</p>
<h3 class="wp-block-heading">6. 運用、実行手順とロールバック</h3>
<h4 class="wp-block-heading">実行手順</h4>
<ol class="wp-block-list">
<li><p>ExcelまたはAccessを開き、VBAエディタ(Alt + F11)を起動します。</p></li>
<li><p>「挿入」→「標準モジュール」を選択し、<code>mod_HighResTimer</code> と検証コード(<code>Measure_ExcelPerformance</code> または <code>Measure_AccessPerformance</code>)をそれぞれ貼り付けます。</p></li>
<li><p>検証コードを実行します(F5キー)。計測結果はイミディエイトウィンドウ(Ctrl + G)に出力されます。</p></li>
</ol>
<h4 class="wp-block-heading">ロールバック手順</h4>
<ol class="wp-block-list">
<li><p>作成した標準モジュール(<code>mod_HighResTimer</code> および <code>mod_Main</code> など)をプロジェクトから削除します。</p></li>
<li><p>このAPIはOSレベルの標準機能であり、レジストリやシステムファイルへの変更は伴わないため、特別なロールバック作業は不要です。</p></li>
</ol>
<h3 class="wp-block-heading">7. 落とし穴と注意点</h3>
<h4 class="wp-block-heading">PtrSafeと64bit環境</h4>
<p>Office 2010以降の64ビット環境では、API宣言に必ず <code>PtrSafe</code> を使用しなければなりません。これがないとコンパイルエラーまたは実行時エラーが発生します。また、64ビット値を格納するために <code>LongPtr</code> を使うことも可能ですが、VBA内部での演算が必要な場合は本実装のように <code>Currency</code> 型を使う方が便利です[2][4]。</p>
<h4 class="wp-block-heading">精度とオーバーフロー</h4>
<p><code>QueryPerformanceCounter</code> は非常に高精度ですが、カウンタ値自体はシステムの連続稼働時間に比例して増加し続けます。64ビット値(約 $9 \times 10^{18}$)は非常に大きいため、通常の使用においてオーバーフローの心配はありませんが、計測開始と終了の間にシステムが再起動した場合、不正確な結果を返す可能性があります。長時間(数日間)連続稼働させるタスクの場合、必ず計測開始前にOS起動からの経過時間(<code>GetTickCount64</code> など)も併せてチェックすることが推奨されます。</p>
<h4 class="wp-block-heading">性能チューニングの効果</h4>
<p>計測結果から、Excelでは <code>Application.ScreenUpdating = False</code> や<strong>配列バッファ</strong>の使用が、処理時間を数桁短縮する最大の要因であることが分かります。Accessでは、トランザクションの適切な使用や、DAO/ADOでの<strong>カーソルタイプ/ロックオプション</strong> (<code>dbUseNoLocks</code>) の選択が処理時間に大きく影響します。高精度タイマーは、これらの微妙な最適化の効果を数値として明確に把握するために不可欠です。</p>
本記事はGeminiの出力をプロンプト工学で整理した業務ドラフト(未検証)です。
VBAにおけるWin32 API(QueryPerformanceCounter)を利用した高精度・長時間処理計測の実装と性能チューニング
1. 背景と要件
VBA(Excel, Access)による定型業務の自動化において、数秒から数時間に及ぶ長時間処理のボトルネック特定や性能比較は不可欠です。しかし、VBA標準の Timer 関数は、精度が秒単位であり、特に高速な処理の差分計測や、数時間レベルの長時間計測には向いていません(約24時間でリセットされるという制限もあります)。
実務レベルでの高精度な計測を実現するためには、Windows OSが提供する高分解能パフォーマンスカウンタ(High-Resolution Performance Counter: HRPC)へのアクセスが必要です。
要件:
VBA標準機能および外部ライブラリを使用せず、Win32 APIのみで実装する。
Office 64ビット環境に対応するため、PtrSafe を宣言する。
高精度タイマー(マイクロ秒オーダー)を実装し、長時間計測にも対応させる。
ExcelとAccessの具体的な最適化手法(ScreenUpdating, 配列、DAO/ADO)の前後比較を計測する。
2. 設計:QueryPerformanceCounterの利用と64bit対応
高精度な計測には、Win32 APIの以下の2つの関数を使用します。
QueryPerformanceFrequency (QPF): カウンタが1秒あたりにインクリメントされる回数(周波数 Hz)を取得します。この値はシステム起動後、通常固定です。
QueryPerformanceCounter (QPC): 現在のカウンタ値(64ビット整数)を取得します。
経過時間(秒)は (QPC_End - QPC_Start) / QPF で計算されます。
64ビットデータ型の取り扱い
QPCおよびQPFは64ビット整数(Large Integer)を返します。VBAの標準 Long 型(32ビット)ではオーバーフローするため、64ビット値を安全に扱う必要があります。
最も一般的な解決策は、VBAの Currency 型(内部的に64ビット整数として扱われるが、小数点以下4桁固定の固定小数点数)を使用する手法です[4]。この方法を使用すると、ポインタ操作を複雑にせずに高精度カウンタを格納し、VBA内で演算処理を行うことが可能になります。
処理の流れ (Mermaid Flowchart)
長時間にわたる複雑なデータ処理の計測フローは以下のようになります。
graph TD
A["開始: 計測対象プロシージャ呼び出し"] --> B{"API宣言と環境確認"};
B -- 周波数取得 --> C["計測開始: QPC_Start"];
C --> D1("チューニング前処理");
D1 --> E1["計測終了1: QPC_End1"];
E1 --> F1{"経過時間計算1: (End1-Start) / Hz"};
F1 --> G("チューニング処理実施");
G --> C;
C --> D2("チューニング後処理");
D2 --> E2["計測終了2: QPC_End2"];
E2 --> F2{"経過時間計算2: (End2-Start) / Hz"};
F2 --> H["結果表示: チューニング効果(秒)"];
H --> I["終了"];
3. 実装:高精度タイマーモジュール
標準モジュール(例: mod_HighResTimer)に以下のAPI宣言とラッパー関数を記述します。
' // mod_HighResTimer.bas
#If VBA7 Then
' 64bit/32bit 環境対応 (Office 2010以降)
Private Declare PtrSafe Function QueryPerformanceCounter Lib "kernel32" ( _
lpPerformanceCount As Currency) As Long
Private Declare PtrSafe Function QueryPerformanceFrequency Lib "kernel32" ( _
lpFrequency As Currency) As Long
#Else
' 32bit 環境対応 (非推奨だが互換性のために残す)
Private Declare Function QueryPerformanceCounter Lib "kernel32" ( _
lpPerformanceCount As Currency) As Long
Private Declare Function QueryPerformanceFrequency Lib "kernel32" ( _
lpFrequency As Currency) As Long
#End If
' グローバル変数として周波数を保持し、何度もAPIを叩くのを避ける
Private g_lngFrequency As Currency
Private Const PRECISION_FACTOR As Double = 10000# ' Currency型調整係数
' ------------------------------------------------------------------
' 1. カウンタ周波数を取得 (初回起動時に一度だけ実行)
' ------------------------------------------------------------------
Private Function GetFrequency() As Currency
If g_lngFrequency = 0 Then
If QueryPerformanceFrequency(g_lngFrequency) = 0 Then
' 取得失敗時のエラー処理
Err.Raise 5, "QueryPerformanceFrequency", "パフォーマンスカウンタの周波数取得に失敗しました。"
End If
End If
GetFrequency = g_lngFrequency
End Function
' ------------------------------------------------------------------
' 2. 現在のカウンタ値を取得 (計測開始/終了に使用)
' ------------------------------------------------------------------
Public Function GetQPC() As Currency
Dim lngCounter As Currency
If QueryPerformanceCounter(lngCounter) = 0 Then
Err.Raise 5, "QueryPerformanceCounter", "パフォーマンスカウンタの取得に失敗しました。"
End If
GetQPC = lngCounter
End Function
' ------------------------------------------------------------------
' 3. 経過時間を計算 (秒単位)
' StartTime/EndTime は GetQPC() の戻り値
' ------------------------------------------------------------------
Public Function CalculateElapsedTime(StartTime As Currency, EndTime As Currency) As Double
Dim lngFreq As Currency
lngFreq = GetFrequency()
' Currency型で受け取ったカウンタ値は、本来の値の10000倍になっている。
' これを周波数で割り、さらに10000で割って秒に変換する。
' 内部計算: (EndTime * 10000 - StartTime * 10000) / (Freq * 10000)
' 簡略化: (EndTime - StartTime) / Freq
' ※ Currency型同士の減算・除算はVBAが内部で調整するため、このままでよい
CalculateElapsedTime = CDbl((EndTime - StartTime) / lngFreq)
End Function
4. 検証:Excelでの大量データ処理計測とチューニング
Excelで、数千行のセルに値を書き込む処理の高速化(配列利用)の効果を計測します。
' // 標準モジュール (例: mod_Main)
Sub Measure_ExcelPerformance()
Const ROWS_COUNT As Long = 50000
Dim ws As Worksheet
Set ws = ThisWorkbook.Sheets(1)
Dim tStart As Currency, tEnd As Currency
Dim dTime1 As Double, dTime2 As Double
Application.ScreenUpdating = False
'--- 処理1: セル直接書き込み (チューニング前) ---
tStart = GetQPC()
Dim i As Long
For i = 1 To ROWS_COUNT
ws.Cells(i, 1).Value = "Value_" & i
Next i
tEnd = GetQPC()
dTime1 = CalculateElapsedTime(tStart, tEnd)
'--- 処理2: 配列による一括書き込み (チューニング後) ---
ws.Columns(1).ClearContents ' リセット
Dim vData(1 To ROWS_COUNT, 1 To 1) As Variant
tStart = GetQPC()
For i = 1 To ROWS_COUNT
vData(i, 1) = "Value_" & i
Next i
ws.Cells(1, 2).Resize(ROWS_COUNT, 1).Value = vData ' 一括書き込み
tEnd = GetQPC()
dTime2 = CalculateElapsedTime(tStart, tEnd)
Application.ScreenUpdating = True
'--- 結果表示 ---
Debug.Print "--- Excel 50,000行書き込み計測 ---"
Debug.Print "1. セル直接書き込み (未チューニング): " & Format$(dTime1, "0.00000") & " 秒"
Debug.Print "2. 配列バッファ一括書き込み (チューニング済): " & Format$(dTime2, "0.00000") & " 秒"
Debug.Print "改善率: " & Format$((dTime1 / dTime2), "0.0") & " 倍"
End Sub
(想定される結果:処理1は数秒、処理2は数十ミリ秒となり、改善率は100倍以上になることが多い。)
5. 検証:AccessでのDAO処理計測と最適化
Accessで大量のレコードセットを操作する際の処理時間と、dbUseNoLocks の効果を計測します。
' // 標準モジュール (Access VBA)
Sub Measure_AccessPerformance()
Dim db As DAO.Database
Dim rs As DAO.Recordset
Dim strSQL As String
' ここでは「T_TargetData」テーブルが存在すると仮定
strSQL = "SELECT ID, DataField FROM T_TargetData WHERE DataField IS NULL;"
Set db = CurrentDb
Dim tStart As Currency, tEnd As Currency
Dim dTime1 As Double, dTime2 As Double
Dim lngCount As Long
'--- 処理1: デフォルトロックでループ処理 ---
tStart = GetQPC()
' デフォルトロック (dbOpenDynaset)
Set rs = db.OpenRecordset(strSQL, dbOpenDynaset)
If Not rs.EOF Then
rs.MoveLast
rs.MoveFirst
Do While Not rs.EOF
' 何らかの更新処理をシミュレート
lngCount = lngCount + 1
rs.MoveNext
Loop
End If
rs.Close
tEnd = GetQPC()
dTime1 = CalculateElapsedTime(tStart, tEnd)
'--- 処理2: dbUseNoLocks オプションでループ処理 (チューニング後) ---
lngCount = 0
tStart = GetQPC()
' dbUseNoLocks (読み取り専用または一括処理に適したオプション)
Set rs = db.OpenRecordset(strSQL, dbOpenDynaset, dbUseNoLocks)
If Not rs.EOF Then
rs.MoveLast
rs.MoveFirst
Do While Not rs.EOF
lngCount = lngCount + 1
rs.MoveNext
Loop
End If
rs.Close
tEnd = GetQPC()
dTime2 = CalculateElapsedTime(tStart, tEnd)
'--- 結果表示 ---
Debug.Print "--- Access DAO レコードセット計測 (" & lngCount & "件) ---"
Debug.Print "1. デフォルトロック (未チューニング): " & Format$(dTime1, "0.00000") & " 秒"
Debug.Print "2. dbUseNoLocks 使用 (チューニング済): " & Format$(dTime2, "0.00000") & " 秒"
Debug.Print "改善率 (主にロック待機時間の削減): " & Format$((dTime1 / dTime2), "0.00") & " 倍"
Set rs = Nothing
Set db = Nothing
End Sub
(注: dbUseNoLocks の効果は、ネットワーク越しや競合が多い環境で顕著になりますが、高精度タイマーにより僅かな改善でも検出可能になります。)
6. 運用、実行手順とロールバック
実行手順
ExcelまたはAccessを開き、VBAエディタ(Alt + F11)を起動します。
「挿入」→「標準モジュール」を選択し、mod_HighResTimer と検証コード(Measure_ExcelPerformance または Measure_AccessPerformance)をそれぞれ貼り付けます。
検証コードを実行します(F5キー)。計測結果はイミディエイトウィンドウ(Ctrl + G)に出力されます。
ロールバック手順
作成した標準モジュール(mod_HighResTimer および mod_Main など)をプロジェクトから削除します。
このAPIはOSレベルの標準機能であり、レジストリやシステムファイルへの変更は伴わないため、特別なロールバック作業は不要です。
7. 落とし穴と注意点
PtrSafeと64bit環境
Office 2010以降の64ビット環境では、API宣言に必ず PtrSafe を使用しなければなりません。これがないとコンパイルエラーまたは実行時エラーが発生します。また、64ビット値を格納するために LongPtr を使うことも可能ですが、VBA内部での演算が必要な場合は本実装のように Currency 型を使う方が便利です[2][4]。
精度とオーバーフロー
QueryPerformanceCounter は非常に高精度ですが、カウンタ値自体はシステムの連続稼働時間に比例して増加し続けます。64ビット値(約 $9 \times 10^{18}$)は非常に大きいため、通常の使用においてオーバーフローの心配はありませんが、計測開始と終了の間にシステムが再起動した場合、不正確な結果を返す可能性があります。長時間(数日間)連続稼働させるタスクの場合、必ず計測開始前にOS起動からの経過時間(GetTickCount64 など)も併せてチェックすることが推奨されます。
性能チューニングの効果
計測結果から、Excelでは Application.ScreenUpdating = False や配列バッファの使用が、処理時間を数桁短縮する最大の要因であることが分かります。Accessでは、トランザクションの適切な使用や、DAO/ADOでのカーソルタイプ/ロックオプション (dbUseNoLocks) の選択が処理時間に大きく影響します。高精度タイマーは、これらの微妙な最適化の効果を数値として明確に把握するために不可欠です。
コメント