【テクニカル・上級編】VB.NETの配列とList(Of T)の性能比較:実務で選ぶべきコレクションとメモリ効率化の鉄則 – Visual Basic (VB / VB.NET)解析バイブル

スポンサーリンク

配列か、List(Of T)か。VB.NETにおけるメモリの深淵と「極限」の選択基準

かつてVBAで泥臭いセル操作を行っていた諸君、そして今、.NETの海で大規模なデータ処理と対峙している諸君へ。

「とりあえずList(Of T)を使っておけばいい」

もしあなたがそう考えているなら、それはエンジニアとしての怠慢だ。パフォーマンスが要求されるシステムにおいて、コレクションの選択は、メモリのライフサイクルとGC(ガベージコレクション)の挙動を支配する「戦略的決断」に他ならない。

今日は、VB.NETにおける配列とList(Of T)の真実を、アーキテクチャの観点から紐解く。

1. 配列(Array)の静寂とList(Of T)の動的な喧騒

まず本質を理解せよ。配列はメモリ上の「連続した静的な空間」であり、List(Of T)は「内部に配列を抱え、必要に応じて拡張する動的なラッパー」である。

配列が選ばれるべき極限の局面

配列は、要素数が確定している、あるいはメモリを1バイトでも無駄にできない「低レイヤーに近い処理」において最強の武器となる。

  • 構造体(Value Type)の配列: メモリ上に一塊として確保されるため、キャッシュヒット率が劇的に高い。
  • API連携: Windows APIへデータを渡す際、マーシャリングを回避してポインタを渡すには配列が不可欠だ。

List(Of T)の隠れたコスト

List(Of T)は便利だ。しかし、`Add`メソッドを呼ぶたびに、内部の配列が上限に達すれば「新しい配列を確保し、既存の全要素をコピーする」というコストが発生する。この際、捨てられた古い配列はGCのゴミとなり、メモリの断片化を加速させる。

2. パフォーマンスを極限まで引き出すための「実装の鉄則」

現場で生き残るエンジニアは、コレクションを「適材適所」で使い分ける。

実践:動的データ処理における「事前割当」の技術

もし要素数が概ね予測できるなら、List(Of T)のコンストラクタで初期容量を指定せよ。これにより、再確保のオーバーヘッドをゼロにできる。

‘ 悪い例:再確保が頻発する
Dim list As New List(Of Integer)()
For i As Integer = 0 To 10000
list.Add(i) ‘ 何度も内部配列のコピーが発生する
Next

‘ 良い例:容量を事前に確保(予測値が立てられる場合)
Dim estimatedSize As Integer = 10000
Dim optimizedList As New List(Of Integer)(estimatedSize)
For i As Integer = 0 To estimatedSize – 1
optimizedList.Add(i) ‘ 再確保は一度も発生しない
Next

Windows API呼び出し時の「配列の固定」

VB.NETからアンマネージコードを呼び出す際、GCによるメモリ移動を阻止するために`GCHandle`を用いる必要がある。List(Of T)ではこれができない(Listそのものではなく、内部の配列を固定しなければならないからだ)。

Imports System.Runtime.InteropServices

‘ API呼び出し用のデータ構造
Dim data As Integer() = {1, 2, 3}
Dim handle As GCHandle = GCHandle.Alloc(data, GCHandleType.Pinned)

Try
‘ ここで安全にネイティブ関数へポインタを渡す
Dim ptr As IntPtr = handle.AddrOfPinnedObject()
‘ NativeApiCall(ptr)
Finally
‘ 処理終了後は必ず解放する。ここを忘れるとメモリリークの温床となる
If handle.IsAllocated Then handle.Free()
End Try

3. レガシーシステム保守における「メモリ解放」の極意

VBA時代の「Set Nothing」の呪縛に囚われている諸君に告ぐ。.NETにおいて、参照型の変数にNothingを代入しても、GCが即座に動くわけではない。

しかし、大規模なデータ処理を行った後、「明示的に参照を切り、生存期間を縮める」ことは重要だ。特に、生存期間の長いオブジェクトが配列を保持している場合、`Array.Clear`を使って参照を消去し、GCがオブジェクトを回収できるように手助けする配慮が必要だ。

‘ 大規模データ処理の終了時
Public Sub ProcessLargeData()
Dim buffer As Object() = New Object(1000000) {}

‘ … 重い処理 …

‘ 処理終了後、配列の要素が参照型なら、参照をクリアする
‘ これによりGCが各オブジェクトを迅速に回収できる
Array.Clear(buffer, 0, buffer.Length)
buffer = Nothing ‘ 最後に参照を破棄
End Sub

結論:エンジニアの美学

  • 要素数が固定、または極限の速度が必要な場合: 配列を迷わず選べ。
  • 要素数が変動し、コードの保守性が優先される場合: List(Of T)を選べ。ただし、初期容量の指定を忘れるな。
  • API連携やメモリレイアウトの制御が必要な場合: `GCHandle`と配列の組み合わせが唯一の正解だ。

技術とは、単に動くコードを書くことではない。「なぜそのデータ構造を選んだのか」をCPUのクロックとメモリの断片化の視点で説明できること、それがプロフェッショナルの条件だ。

さあ、次のコミットでは、メモリの断片化を意識した美しいコードを書いてくれ。現場からは以上だ。

タイトルとURLをコピーしました