Xia*_* Ge 47 c# performance delegates closures garbage-collection
我有以下程序,它从两个静态方法构造一个本地Func.但奇怪的是,当我分析程序时,它分配了近百万个Func对象.为什么调用Func对象也在创建Func实例?
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);
}
}
}
Run Code Online (Sandbox Code Playgroud)
Jon*_*eet 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));
Run Code Online (Sandbox Code Playgroud)
另一种选择是分配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);
}
Run Code Online (Sandbox Code Playgroud)
所有这些让我很难过,在最新版的ECMA C#标准中,编译器将被允许缓存方法组转换的结果.我不知道什么时候/它是否会这么做,但.