C# ラムダ式のパフォーマンス考慮点
ラムダ式は便利な機能ですが、使い方によってはパフォーマンスに影響を与えることがあります。特にホットパスでは、いくつかの点に注意が必要です。
クロージャによるアロケーション
外部変数をキャプチャするラムダ式は、内部的にクロージャクラスが生成されます。これはヒープへのメモリ割り当てを伴います。
void ProcessWithClosure(List<int> numbers)
{
int threshold = 10;
// threshold をキャプチャ → クロージャが生成される
var filtered = numbers.Where(n => n > threshold);
}頻繁に呼ばれるメソッドでは、このアロケーションが積み重なってガベージコレクションの負荷になります。
静的ラムダで回避
キャプチャが不要な場合は、静的ラムダを使うことでアロケーションを防げます。
var numbers = new List<int> { 1, 2, 3, 4, 5 };
// 静的ラムダ:キャプチャなし、アロケーションなし
var doubled = numbers.Select(static x => x * 2);キャプチャありラムダ
呼び出しごとにクロージャオブジェクトが生成される可能性
静的ラムダ / キャプチャなし
デリゲートインスタンスがキャッシュされ、再利用される
デリゲートのキャッシュ
キャプチャのないラムダ式は、コンパイラが自動的にデリゲートをキャッシュしてくれます。
// キャプチャなし → デリゲートがキャッシュされる
Func<int, int> square = x => x * x;何度呼び出しても同じデリゲートインスタンスが再利用されます。
LINQ のパフォーマンス
LINQ は便利ですが、パフォーマンスが重要な場面では従来のループのほうが効率的なこともあります。
var numbers = Enumerable.Range(1, 1000000).ToList();
// LINQ:簡潔だがオーバーヘッドあり
var sumLinq = numbers.Where(n => n % 2 == 0).Sum();
// ループ:冗長だが高速
int sumLoop = 0;
foreach (var n in numbers)
{
if (n % 2 == 0)
sumLoop += n;
}大量のデータを処理する場合は、実測して判断することが重要です。
ボックス化に注意
ジェネリックでないデリゲートや、値型をキャプチャする際にボックス化が発生することがあります。
ボックス化とは
値型(int, struct など)をオブジェクト型として扱う際に発生するヒープ割り当てのこと。
回避策
可能な限りジェネリックなデリゲート(Func<T> など)を使用する。
実用的なガイドライン
パフォーマンスを意識する場合の指針をまとめます。
ホットパスではキャプチャを避けるか、静的ラムダを使う
大量データの処理では LINQ と手動ループを比較検討する
実測なしに最適化しない(プロファイリングが重要)
可読性とのバランスを考慮する
ほとんどのケースでは、ラムダ式のオーバーヘッドは問題になりません。パフォーマンスが本当に問題になった箇所だけを最適化するのが、現実的なアプローチです。