请忽略此问题中的代码可读性。
就性能而言,下列代码应如下所示:
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
是class
,MaxResults
是一个简单的整数属性(即public int MaxResults { get { return _maxResults; } }
。
C#编译器是否将MaxResults
视为黑盒并每次都进行评估?或者它是否足够聪明,可以发现我有3次调用同一个属性而没有在调用之间修改该属性?如果MaxResults
是一个字段怎么办?
优化法则之一就是预先计算,所以我本能地像第一个列表那样编写这段代码,但我很好奇是否会自动为我做这种事情(再次忽略代码可读性)。
(注意:我对听到'微优化'论证不感兴趣,这个论点可能在我发布的具体案例中有效。我只是想了解正在发生或未发生的一些理论。 )
答案 0 :(得分:59)
首先,实际回答性能问题的唯一方法是实际尝试两种方式并在实际条件下测试结果。
那就是说,其他答案说“编译器”没有进行这种优化,因为属性可能有副作用是对还是错。问题的问题(除了在没有实际尝试和测量结果的情况下根本无法回答的基本问题)是“编译器”实际上是两个编译器:编译为MSIL的C#编译器和JIT编译器,它将IL编译为机器代码。
C#编译器从未进行过这种优化;如上所述,这样做将要求编译器对应于被调用的代码,并验证其计算的结果在被调用者代码的生命周期内不会发生变化。 C#编译器没有这样做。
JIT编译器可能会。没有理由不能。它的所有代码就在那里。内联属性getter是完全自由的,如果抖动确定内联属性getter返回一个可以缓存在寄存器中并重新使用的值,那么它可以自由地执行。 (如果您不希望它这样做,因为可以在另一个线程上修改该值,那么您已经遇到了竞争条件错误;在您担心性能之前修复该错误。)
实际上的抖动是否内联属性获取然后注册该值,我不知道。我对抖动几乎一无所知。但如果认为合适的话,允许这样做。如果您对它是否这样做感到好奇,您可以(1)询问团队中编写抖动的人,或者(2)检查调试器中的jitted代码。
最后,让我借此机会注意计算结果一次,存储结果并重新使用它并不总是优化。这是一个非常复杂的问题。有各种各样的东西需要优化:
执行时间
可执行代码大小 - 这对可执行时间有重大影响,因为大代码加载时间较长,工作集大小增加,对处理器缓存,RAM和页面文件造成压力。在重要的指标(如启动时间和缓存位置)中,小的慢速代码通常是更快,而不是大型快速代码。
寄存器分配 - 这也会对执行时间产生重大影响,特别是在x86等具有少量可用寄存器的体系结构中。注册一个值以便快速重用可能意味着可用于需要优化的其他操作的寄存器更少;或许优化这些操作将是一场净胜利。
等等。它变得非常复杂,快速。
简而言之,您不可能知道编写代码来缓存结果而不是重新计算它实际上是(1)更快,或(2)更好的执行。 更好的性能并不总是意味着更快地执行特定的例程。更好的性能是确定哪些资源对用户很重要 - 执行时间,内存,工作集,启动时间等等 - 并优化这些事情。如果没有(1)与客户交谈以了解他们关心的内容,以及(2)实际测量以确定您的更改是否在所需方向上产生可衡量的影响,则无法做到这一点。
答案 1 :(得分:7)
如果MaxResults
是属性,那么不会,它不会优化它,因为getter可能有复杂的逻辑,比如说:
private int _maxResults;
public int MaxReuslts {
get { return _maxResults++; }
set { _maxResults = value; }
}
了解如果内联代码行为会如何改变?
如果没有逻辑......你写的任何一种方法都很好,这是一个非常小的差异,所有关于它的可读性对你(或你的团队) ......你是一看着它。
答案 2 :(得分:6)
只保证两个代码示例在单线程环境中具有相同的结果,而.Net不是,如果MaxResults
是字段(不是属性)。除非您使用同步功能,否则编译器不能假定criteria.MaxResults
在循环过程中不会更改。如果它是一个属性,它不能假设使用该属性没有副作用。
Eric Lippert非常正确地指出,它很大程度上取决于“编译器”的含义。 C# - > IL编译器?或IL - >机器码(JIT)编译器?并且他指出JIT可能能够优化属性getter是正确的,因为它具有所有信息(而C# - > IL编译器则不一定)。它不会改变多线程的情况,但它仍然是一个好点。
答案 3 :(得分:4)
每次都会调用和评估它。编译器无法确定方法(或getter)是否具有确定性和纯粹(无副作用)。
请注意,JIT编译器可能会对内容的实际评估进行内联,使其有效地与简单字段一样快。
将财产评估作为廉价的操作是一种很好的做法。如果在getter中进行了大量计算,请考虑手动缓存结果,或将其更改为方法。
答案 4 :(得分:2)
为什么不测试它?
只需设置2个控制台应用程序,使其看起来有1000万次并比较结果......记得将它们作为已正确安装的正确发布的应用程序运行,否则您无法保证不只是运行msil。 / p>
真的,你可能会得到大约5个答案,说'你不应该担心优化'。他们显然不会在可读之前编写需要尽可能快的例程(例如游戏)。
如果这段代码是执行数十亿次的循环的一部分,那么这种优化可能是值得的。例如,max结果可能是一个重写方法,因此您可能需要讨论虚方法调用。
真的 ONLY 回答这些问题的方法是弄清楚这是一段将从优化中受益的代码。然后你需要知道增加执行时间的事情。真的,我们凡人不能事先做到这一点,所以必须简单地尝试2-3个不同版本的代码然后测试它。
答案 5 :(得分:0)
如果criteria
是类类型,我怀疑它会被优化,因为另一个线程在此期间总是可以更改该值。对于struct
我不确定,但我的直觉是它不会被优化,但我认为无论如何在这种情况下表现不会有太大差异。