C#编译器或JIT能否优化lambda表达式中的方法调用?

C#编译器或JIT能否优化lambda表达式中的方法调用?,c#,optimization,lambda,jit,constant-expression,C#,Optimization,Lambda,Jit,Constant Expression,我是在讨论另一个StackOverflow问题()之后开始这个问题的,我很想知道答案。考虑到以下表述: var objects = RequestObjects.Where(r => r.RequestDate > ListOfDates.Max()); 在本例中,将ListOfDates.Max()的求值从Where子句中移出会有任何(性能)优势,还是会有1。编译器或2。JIT优化这个 我相信C#只会在编译时执行常量折叠,并且有人可能会说,除非ListOfDates本身是常量,否

我是在讨论另一个StackOverflow问题()之后开始这个问题的,我很想知道答案。考虑到以下表述:

var objects = RequestObjects.Where(r => r.RequestDate > ListOfDates.Max());
在本例中,将
ListOfDates.Max()
的求值从Where子句中移出会有任何(性能)优势,还是会有1。编译器或2。JIT优化这个

我相信C#只会在编译时执行常量折叠,并且有人可能会说,除非ListOfDates本身是常量,否则在编译时无法知道ListOfDates.Max()


也许还有另一种编译器(或JIT)优化可以确保只对其进行一次计算?

好吧,这是一个有点复杂的答案

这里涉及两件事。(1) 编译器和(2)JIT

编译器

简单地说,编译器只是把你的C代码翻译成IL代码。对于大多数情况来说,这是一个相当简单的翻译,而.NET的核心思想之一就是将每个函数编译为一个独立的IL代码块

因此,不要对C#->IL编译器期望过高

准时制

那是。。。有点复杂

JIT编译器基本上将您的IL代码翻译成汇编程序。JIT编译器还包含一个基于SSA的优化器。但是,有一个时间限制,因为我们不想在代码开始运行之前等待太久。基本上,这意味着JIT编译器不会做所有超酷的事情,这将使您的代码运行得非常快,因为这将花费太多的时间

当然,我们可以对它进行测试:)确保VS在运行时会进行优化(选项->调试器->取消选中抑制[…]和我的代码),在x64发布模式下编译,设置断点,然后查看切换到汇编程序视图时会发生什么

但是,嘿,只有理论有什么乐趣;让我们来测试一下

结果:

Vtable call took 2714 ms, result = -1243309312
Non-vtable call took 2558 ms, result = -1243309312
Delegate call took 1904 ms, result = -1243309312
Static call took 324 ms, result = -1243309312
这里有趣的是最新的测试结果。记住,静态调用(IL
call
)是完全确定的。这意味着对编译器进行优化是一件相对简单的事情。如果检查汇编程序的输出,您会发现对Sum的调用实际上是内联的。这是有道理的。实际上,如果您要测试它,只需将代码放入方法中,速度就和静态调用一样快

关于平等的一点小评论

若你们衡量哈希表的性能,我的解释似乎有些可疑。似乎
IEquatable
使事情进展得更快

嗯,事实上是这样。:-)散列容器使用
IEquatable
调用
Equals
。现在,我们都知道,对象都实现了
等于(objecto)
。因此,容器可以调用
Equals(object)
Equals(T)
。调用本身的性能是相同的

但是,如果还实现了
IEquatable
,则实现通常如下所示:

bool Equals(object o)
{
    var obj = o as MyType;
    return obj != null && this.Equals(obj);
}
此外,如果
MyType
是一个结构,则运行时还需要应用装箱和取消装箱。如果它只调用
IEquatable
,那么这些步骤都不是必需的。因此,尽管它看起来慢了一些,但这与呼叫本身无关

您的问题

在本例中,将ListOfDates.Max()的计算从Where子句中移出是否有任何(性能)优势,或者1。编译器或2。JIT优化这个

是的,会有优势。编译器/JIT不会优化它

我相信C#只会在编译时执行常量折叠,并且有人可能会说,除非ListOfDates本身是常量,否则在编译时无法知道ListOfDates.Max()

实际上,如果您将静态调用更改为
n=2+Sum(n,2)
,您将注意到汇编程序输出将包含
4
。这证明了JIT优化器确实执行了常量折叠。(如果您知道SSA优化器是如何工作的,这一点非常明显……const折叠和简化被称为多次)

函数指针本身未优化。不过,这可能是在将来

也许还有另一个编译器(或JIT)优化可以确保只计算一次

至于“另一个编译器”,如果你愿意添加另一种语言,你可以使用C++。在C++中,这些调用有时被优化掉。


更有趣的是,Clang基于LLVM,也有一些用于LLVM的C#编译器。我相信Mono有一个选项可以优化到LLVM,CoreCLR正在研究LLILC。虽然我还没有测试过这一点,但LLVM肯定可以进行此类优化。

编译器可能不能,因为它无法证明访问
r.RequestDate
属性不会修改
listofdate
。JIT可能会认识到这一点,但可能不会。“没有这样做的要求。”雷蒙森我同意,但我认为更简单;只要
ListOfDates
不知道是不可变的,常数折叠就不能安全地完成。我认为重要的是要提到ListOfDates是List(或DateTime[]),RequestObject.RequestDate是DateTime类型的变量。证明
ListOfDates.Max()
是常数是非常复杂的。例如,它需要证明属性调用
r.RequestDate
不会更改列表。考虑到当前的JIT在优化任何非基本的东西方面非常差,这将不会发生。当前JIT从
a.x+a.x
中的内存中加载x两次。就是这么简单。是的,JIT对预热时间(它执行方法所需的时间)进行了优化。因此,更复杂的优化根本无法执行。。。我同意这是一个非常复杂的问题
Vtable call took 2714 ms, result = -1243309312
Non-vtable call took 2558 ms, result = -1243309312
Delegate call took 1904 ms, result = -1243309312
Static call took 324 ms, result = -1243309312
bool Equals(object o)
{
    var obj = o as MyType;
    return obj != null && this.Equals(obj);
}