我有以下程序,它们从两个静态方法构造一个本地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);
}
}
}
你正在使用方法组转换来创建Func<long, long, bool>
用于comparerFunc
参数的。不幸的是,C#5规范当前要求每次运行时都要创建一个新的委托实例。在C#5规范的6.6节中,描述了方法组转换的运行时评估:
分配了委托类型D的新实例。如果没有足够的内存来分配新实例,则将抛出System.OutOfMemoryException,并且不会执行其他步骤。
匿名函数转换(6.5.1)的部分包括以下内容:
将具有相同(可能为空)的一组已捕获外部变量实例的语义相同的匿名函数转换为相同的委托类型(但不是必需的),以返回相同的委托实例。
...但是方法组转换没有类似之处。
这个代码是手段允许进行优化,以使用单个委托实例对于每个所涉及的代表-和罗斯林一样。
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#标准中,将允许编译器缓存方法组转换的结果。我不知道什么时候/它是否会这么做,但。
@JonSkeet我完全同意;如果发生这种情况,将会带来巨大的好处-在代码审查中,我经常看到(并修复)以分配方式使用方法组,在lambda大小化(这是一个单词吗?)的情况下不这样做的情况-但是:我并不完美,b:很多人对此一无所知(为什么要这么做?)。因此:如果您想一起俱乐部,我们可以向贾里德送一两杯啤酒,“完全不行贿,老实(但请确保它发生在kthxbye上)” :)
@buffjape:“出什么问题了?” 编译器遵守规范,仅此而已。如果规范没有说每次都必须创建一个新的委托,则它可以像对lambda表达式一样轻松地缓存结果。
@buffjape:如果无法检测到优化,那就没问题了。在这种情况下,它是可检测的,因为您可以告知已根据规范创建了多个委托实例。“结果”的那部分不保持相同,因此它不是纯粹的优化。
@JonSkeet在某种程度上,整个程序的优化可以揭示
comparerFunc
在这种情况下,实际上什么都不依赖于identity 。但是我并不奇怪这种情况不会发生。@TavianBarnes:绝对不是C#编译器的工作,IMO。JIT编译器可能会这样做-但我不清楚这是一个经常足以证明JIT时间成本合理的问题。