web-dev-qa-db-ja.com

funcを呼び出すときにメモリ割り当てがあるのはなぜですか

2つの静的メソッドからローカルFuncを構築する次のプログラムがあります。しかし、奇妙なことに、プログラムをプロファイリングすると、100万個近くのFuncオブジェクトが割り当てられました。 Funcオブジェクトを呼び出すとFuncインスタンスも作成されるのはなぜですか?

enter image description here

public static class Utils
{
    public static bool ComparerFunc(long thisTicks, long thatTicks)
    {
        return thisTicks < thatTicks;
    }
    public static int Foo(Guid[] guids, Func<long, long, bool> comparerFunc)
    {
        bool a = comparerFunc(1, 2);
        return 0;
    }
}
class Program
{
    static void Main(string[] args)
    {
        Func<Guid[], int> func = x => Utils.Foo(x, Utils.ComparerFunc);
        var guids = new Guid[10];
        for (int i = 0; i < 1000000; i++)
        {
            int a = func(guids);
        }
    }
}
47
Xiaoguo Ge

メソッドグループ変換を使用して、comparerFuncパラメーターに使用されるFunc<long, long, bool>を作成しています。残念ながら、C#5仕様では現在、実行するたびに新しいデリゲートインスタンスを作成する必要があります。 C#5仕様セクション6.6から、メソッドグループ変換の実行時評価について説明します。

デリゲートタイプDの新しいインスタンスが割り当てられます。新しいインスタンスを割り当てるのに十分なメモリがない場合、System.OutOfMemoryExceptionがスローされ、それ以上のステップは実行されません。

無名関数変換のセクション(6.5.1)には次が含まれます。

同じデリゲートインスタンスを返すために、同じ(おそらく空の)キャプチャされた外部変数インスタンスの同じセットを持つセマンティックに同一の匿名関数の同じデリゲート型への変換が許可されます(必須ではありません)。

...しかし、メソッドグループの変換には類似したものはありません。

つまり、このコードはpermittedであり、関係する各デリゲートに対して単一のデリゲートインスタンスを使用するように最適化されていることを意味します。

Func<Guid[], int> func = x => Utils.Foo(x, (a, b) => Utils.ComparerFunc(a, b));

別のオプションは、Func<long, long, bool>を一度割り当てて、ローカル変数に保存することです。そのローカル変数は、ラムダ式でキャプチャする必要があり、Func<Guid[], int>がキャッシュされないようにします。つまり、Mainを何度も実行すると、呼び出しごとに2つの新しいデリゲートが作成されます。一方、以前のソリューションは合理的な範囲でキャッシュします。ただし、コードは簡単です。

Func<long, long, bool> comparer = Utils.ComparerFunc;
Func<Guid[], int> func = x => Utils.Foo(x, comparer);
var guids = new Guid[10];
for (int i = 0; i < 1000000; i++)
{
    int a = func(guids);
}

このすべてが私を悲しくさせます。ECMAC#標準の最新版では、コンパイラはメソッドグループ変換の結果をキャッシュするためにpermittedになります。 willになるかどうかはわかりません。

52
Jon Skeet