为什么在调用func时会有内存分配

时间:2018-03-15 12:25:16

标签: c# performance delegates garbage-collection closures

我有以下程序从两个静态方法构造一个本地Func。但奇怪的是,当我分析程序时,它分配了近百万个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);
        }
    }
}

1 个答案:

答案 0 :(得分:52)

您正在使用方法组转换来创建用于Func<long, long, bool>参数的comparerFunc。不幸的是,目前的C#5规范要求每次运行时都要创建一个新的委托实例。从C#5规范部分6.6,描述方法组转换的运行时评估:

  

分配了委托类型D的新实例。如果没有足够的可用内存来分配新实例,则抛出System.OutOfMemoryException,不执行进一步的步骤。

匿名函数转换(6.5.1)部分包括:

  

允许(但不是必需)将具有相同(可能为空)的捕获的外部变量实例集的语义相同的匿名函数转换为相同的委托类型以返回相同的委托实例。

...但方法组转换没有类似内容。

这意味着该代码允许被优化为每个代理使用一个委托实例 - 而Roslyn也是如此。

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

另一种选择是分配Func<long, long, bool>一次并将其存储在局部变量中。 lambda表达式需要捕获该局部变量,这会阻止Func<Guid[], int>被缓存 - 这意味着如果多次执行Main,则每次调用都会创建两个新的委托,而早期的解决方案会尽可能合理地缓存。代码更简单:

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

所有这些让我感到难过,在最新版的ECMA C#标准中,编译器将允许缓存方法组转换的结果。我不知道 何时/是否会这样做。