C#编译器是否足够智能以优化此代码?

请忽略此问题中的代码可读性。

在性能方面,如下所示代码应如下所示:

int maxResults = criteria.MaxResults; if (maxResults > 0) { while (accounts.Count > maxResults) accounts.RemoveAt(maxResults); } 

或者像这样:

 if (criteria.MaxResults > 0) { while (accounts.Count > criteria.MaxResults) accounts.RemoveAt(criteria.MaxResults); } 

编辑: criteria是一个classMaxResults是一个简单的整数属性(即public int MaxResults { get { return _maxResults; } }

C#编译器是否将MaxResults视为黑盒并每次评估它? 或者它是否足够聪明,可以发现我有3次调用同一个属性而没有在调用之间修改该属性? 如果MaxResults是一个领域怎么办?

优化法则之一是预先计算,所以我本能地像第一个列表那样编写这个代码,但我很好奇是否自动为我做了这种事情(再次,忽略代码可读性)。

(注意:我对听到“微优化”论证不感兴趣,这个论点可能在我发布的具体案例中有效。我只是想了解正在发生或未发生的事情的一些理论。)

首先,实际回答性能问题的唯一方法是实际尝试两种方式并在实际条件下测试结果。

也就是说,其他答案说“编译器”没有进行这种优化,因为属性可能有副作用是对与错。 这个问题的问题(除了根本没有实际尝试并测量结果就无法回答的基本问题)是“编译器”实际上是两个编译器:C#编译器,它编译成MSIL,以及JIT编译器,将IL编译为机器代码。

C#编译器永远不会进行这种优化; 如上所述,这样做将要求编译器对应于被调用的代码并validation其计算的结果在被调用者代码的生命周期内不会发生变化。 C#编译器不这样做。

JIT编译器可能会。 没有理由不能。 它的所有代码就在那里。 内联属性getter是完全自由的,如果抖动确定内联属性getter返回一个可以缓存在寄存器中并重新使用的值,那么它可以自由地执行。 (如果您不希望它这样做,因为可以在另一个线程上修改该值,那么您已经遇到了竞争条件错误;在您担心性能之前修复该错误。)

无论抖动实际上是否内联属性获取然后注册该值,我都不知道。 我对抖动几乎一无所知。 但如果认为合适的话,可以这样做。 如果您对是否这样做感到好奇,可以(1)询问团队中编写抖动的人,或者(2)检查调试器中的jitted代码。

最后,让我借此机会注意计算结果一次,存储结果并重新使用它并不总是优化 。 这是一个非常复杂的问题。 有各种各样的东西需要优化:

  • 执行时间处理时间

  • 可执行代码大小 – 这对可执行时间有重大影响,因为大代码加载时间较长,工作集大小增加,对处理器缓存,RAM和页面文件施加压力。 在重要的指标(如启动时间和缓存位置)中,小慢速代码通常比快速大代码更快

  • 寄存器分配 – 这也会对执行时间产生重大影响,特别是在x86等具有少量可用寄存器的体系结构中。 注册快速重用的值可能意味着可用于需要优化的其他操作的寄存器更少; 或许优化这些操作将取得净胜利。

  • 等等。 它变得非常复杂,快速。

简而言之,您不可能知道编写代码来缓存结果而不是重新计算它实际上是(1)更快,或(2)更好的执行。 更好的性能并不总是意味着更快地执行特定的例程。 更好的性能是确定哪些资源对用户很重要 – 执行时间,内存,工作集,启动时间等等 – 并针对这些事情进行优化。 如果没有(1)与客户交谈以了解他们关心的内容,以及(2)实际测量以确定您的更改是否在所需方向上具有可衡量的影响,则无法做到这一点。

如果MaxResults是一个属性,那么不会,它不会优化它,因为getter可能有复杂的逻辑,比如说:

 private int _maxResults; public int MaxReuslts { get { return _maxResults++; } set { _maxResults = value; } } 

如果内联代码,请查看行为会如何变化?

如果没有逻辑……你写的任何一种方法都很好,这是一个非常微小的差异,所有关于它对你(或你的团队)的可读性……你是那个看着它的人​​。

您的两个代码示例仅保证在单线程环境中具有相同的结果,而.Net不是,并且如果MaxResults是字段(不是属性)。 除非您使用同步function,否则编译器不能假定criteria.MaxResults在循环过程中不会更改。 如果它是属性,则不能假设使用该属性没有副作用。

Eric Lippert非常正确地指出,它很大程度上取决于“编译器”的含义。 C# – > IL编译器? 还是IL – >机器码(JIT)编译器? 并且他指出JIT可能能够优化属性getter是正确的,因为它具有所有信息(而C# – > IL编译器则不一定)。 它不会改变multithreading的情况,但它仍然是一个好点。

每次都会调用和评估它。 编译器无法确定方法(或getter)是确定性的还是纯粹的(没有副作用)。

请注意,JIT编译器可能会对属性的实际评估进行内联,使其有效地与简单字段一样快。

将财产评估作为廉价的操作是一种很好的做法。 如果在getter中进行了大量计算,请考虑手动缓存结果,或将其更改为方法。

为什么不试试呢?

只需设置2个控制台应用程序,使其看起来像1000万次并比较结果…记得将它们作为正确安装的正确发布的应用程序运行,否则你无法保证你不只是运行msil。

真的,你可能会得到大约5个答案,说’你不应该担心优化’。 他们显然不会在可读之前编写需要尽可能快的例程(例如游戏)。

如果这段代码是执行数十亿次循环的一部分,那么这种优化可能是值得的。 例如,max结果可能是一个重写方法,因此您可能需要讨论虚方法调用。

回答任何这些问题的唯一方法是弄清楚这是一段将从优化中受益的代码。 然后你需要知道增加执行时间的事情。 真的我们凡人不能先验地做到这一点,所以必须简单地尝试2-3个不同版本的代码然后测试它。

如果criteria是类类型,我怀疑它会被优化,因为另一个线程在此期间总是可以改变该值。 对于struct我不确定,但我的直觉是它不会被优化,但我认为无论如何在这种情况下性能不会有太大差异。