Why are there memory allocations when calling a fu

2019-03-08 23:35发布

I have the following program which construct a local Func from two static methods. But strangely, when I profile the program, it allocated close to a million Func objects. Why invoking Func object is also creating Func instances?

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);
        }
    }
}

1条回答
爷、活的狠高调
2楼-- · 2019-03-08 23:53

You're using a method group conversion to create the Func<long, long, bool> used for the comparerFunc parameter. Unfortunately, the C# 5 specification currently requires that to create a new delegate instance each time it's run. From the C# 5 specification section 6.6, describing the run-time evaluation of a method group conversion:

A new instance of the delegate type D is allocated. If there is not enough memory available to allocate the new instance, a System.OutOfMemoryException is thrown and no further steps are executed.

The section for anonymous function conversions (6.5.1) includes this:

Conversions of semantically identical anonymous functions with the same (possibly empty) set of captured outer variable instances to the same delegate types are permitted (but not required) to return the same delegate instance.

... but there's nothing similar for method group conversions.

That means this code is permitted to be optimized to use a single delegate instance for each of the delegates involved - and Roslyn does.

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

Another option would be to allocate the Func<long, long, bool> once and store it in a local variable. That local variable would need to be captured by the lambda expression, which prevents the Func<Guid[], int> from being cached - meaning that if you executed Main many times, you'd create two new delegates on each call, whereas the earlier solution would cache as far as is reasonable. The code is simpler though:

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);
}

All of this makes me sad, and in the latest edition of the ECMA C# standard, the compiler will be permitted to cache the result of method group conversions. I don't know when/whether it will do so though.

查看更多
登录 后发表回答