記事

C# LINQ パフォーマンスについて

C# LINQ パフォーマンスについて
TL;DR — 要点まとめ
  • LINQ の Count() は for/foreach に比べて大きなオーバーヘッドがある - 毎フレーム呼び出しでは使用禁止
  • フィルタリング (Where) と変換 (Select) は for/foreach とほぼ同等の性能
  • LINQ は中間バッファ (配列) を生成し GC 圧力を上げる - 頻繁な呼び出しは GC スパイクの原因
  • プロトタイプ・非クリティカルなコードでは LINQ、毎フレームや性能重視箇所では for/foreach
Visitors

目次




LINQ の利点

  • LINQ (Language Integrated Query) は、簡潔で扱いやすいコードを書くための C# 機能セットです。
    クエリ構文を使うことで、最小限のコードでデータソースに対する
    フィルタリング、ソート、グルーピングを実行できます。


LINQ 使用時の注意点

  • LINQ の多くの演算子は中間バッファ (配列のようなもの) を生成し、それがすべてガベージになります。👉 GC 発生
  • 1〜2 個の LINQ 演算子で済む場面でも、可読性目的で細かく分割しすぎると性能低下の原因になります。
  • Average、Count、OrderBy、Reverse などは元シーケンスを反復するため、性能面で不利です。


そのため、LINQ を使う前に以下のルールを考える必要があります。


使用時ルール

  1. すばやい実装が必要か (プロトタイプ開発、TDD)? - LINQ を使う
  2. 性能に敏感でないコードか? - LINQ を使う
  3. LINQ の利便性や可読性が本当に必要か? - LINQ を使う
  4. 毎フレーム呼ばれる、または性能クリティカルな箇所か? - LINQ は使わない



LINQ パフォーマンスベンチマーク



ベンチマーク - 反復処理

年齢 18 歳未満の Customer 数を返す

For / Foreach
// 性能: 高速 (GC なし)
int count = 0;
foreach (var c in customers)
{
    if (c.Age < 18)
        count++;
}
return count;
LINQ Count
// 性能: 低速 (GC 発生)
// Count() は内部で
// シーケンス全体を走査する
return customers
    .Count(c => c.Age < 18);
  1. For / Foreach linq1

  2. LINQ Count / Predicate Count linq2

linq3

for/foreach と比べると、LINQ には若干のオーバーヘッド (機能実行に伴う追加の時間・メモリ・リソース) があります。
フィルタ後のカウントは比較的 for に近いですが、単純に要素数を返す Count は性能低下が大きいです。



ベンチマーク - フィルタリング

年齢 18 歳以上の顧客配列サブセットを返す

  1. For / Foreach linq4

  2. LINQ linq5

linq6

for/foreach 実装と非常に近い性能です。for/foreach より約 4ms 遅い結果でした。



実際に RaycastAll でフィルタ後に tag チェックへ使ったコード

linq7

フィルタリングのようなケースでは、通常の for/foreach よりも簡潔で可読性の高いコードにできます。


ベンチマーク - 変換

年齢を年ではなく月で表現した顧客リストを返す

  1. For / Foreach linq8

  2. LINQ linq9

linq10



LINQ はどれだけ GC を生成するか?

テスト条件は下記リンク参照

結果

  1. GC Alloc Count linq11 linq12

Microsoft Docs の Unity 向けパフォーマンス推奨

Unity に対するパフォーマンス推奨事項

linq13



LINQ vs For - パフォーマンス比較チャート

相対実行時間比較 (低いほど高速、for = 1.0 基準)

結論

  1. LINQ の Count はかなり遅い。
  2. フィルタリング (Where) と変換 (Select) は性能面で for とほぼ同等。
  3. 1〜2 行の非常に簡潔なコードが書ける。
  4. デバッグはやや難しい。
  5. GC 発生があるため、乱用すると厳しくなる。
  6. 毎フレーム呼び出しや性能影響が大きい箇所では使わないこと。
この記事は著者の CC BY 4.0 ライセンスの下で提供されています。