在这个问题中请忽略代码可读性。
在性能方面,下面的代码应该这样写:
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
是一个字段呢?
优化的法则之一是预先计算,所以我本能地编写了与第一个清单类似的代码,但我很好奇这种事情是否会自动为我完成(再次忽略代码可读性)。
(Note:我对听到“微优化”的论点不感兴趣,这在我发布的特定案例中可能是有效的。我只想知道正在发生或不发生的事情背后的一些理论。
7条答案
按热度按时间vi4fp9gy1#
首先,真正回答性能问题的唯一方法是实际尝试两种方法,并在现实条件下测试结果。
也就是说,其他的答案说“编译器”不做这种优化,因为属性可能有副作用,这是正确的也是错误的。这个问题的问题(除了基本问题,即如果不实际尝试并测量结果,就无法回答)是,“编译器”实际上是两个编译器:C#编译器(编译为MSIL)和JIT编译器(将IL编译为机器码)。
C#编译器从来没有做过这种优化;如上所述,这样做将要求编译器对等到被调用的代码中,并验证其计算的结果在被调用者代码的生命周期内没有改变。C#编译器不会这样做。
JIT编译器可能。没有理由不能。所有的代码都在那里。内联属性getter是完全自由的,如果抖动确定内联属性getter返回一个可以缓存在寄存器中并重用的值,那么它可以自由地这样做。(如果你不希望它这样做,因为这个值可能会在另一个线程上被修改,那么你已经有了竞争条件bug;在你担心性能之前先修复bug。)
我不知道jitter是否真的 * 做了 * 内联属性获取,然后注册值。我对抖动一无所知。但如果它认为合适,它是允许这样做的。如果你想知道它是否这样做,你可以(1)询问编写jitter的团队中的某个人,或者(2)在调试器中检查jitter代码。
最后,让我借此机会指出,计算一次结果,存储结果并重复使用它 * 并不总是一个优化 *。这是一个令人惊讶的复杂问题。有各种各样的事情需要优化:
简而言之,您不可能知道编写代码来缓存结果而不是重新计算结果实际上是(1)更快,还是(2)更好的性能。* 更好的性能并不总是意味着使特定例程的执行更快。* 更好的性能是弄清楚哪些资源对用户是重要的-执行时间,内存,工作集,启动时间,等等-并优化这些事情。如果没有(1)与客户交谈,了解他们关心的是什么,(2)实际测量,看看您的变化是否在预期的方向上产生了可衡量的效果,就无法做到这一点。
wz1wpwve2#
如果
MaxResults
是一个属性,那么它不会优化它,因为getter可能有复杂的逻辑,比如:看看如果它嵌入到代码中,行为会如何改变?
如果没有逻辑……你写的两种方法都很好,这是一个非常微小的差异,所有关于它的可读性对你(或你的团队)…你是看它的人。
l2osamch3#
您的两个代码示例只能保证在单线程环境中具有相同的结果,而.Net则不是,并且如果
MaxResults
是一个字段(不是属性)。除非使用同步特性,否则编译器不能假设criteria.MaxResults
在循环过程中不会改变。如果它是一个属性,它不能假设使用该属性没有副作用。Eric Lippert非常正确地指出,这在很大程度上取决于你所说的“编译器”是什么意思。C# -> IL编译器?还是IL ->机器码(JIT)编译器?而且他正确地指出,JIT很可能能够优化属性getter,因为它拥有所有的信息(而C# -> IL编译器不一定)。它不会改变多线程的情况,但这是一个很好的观点。
mwecs4sa4#
每次都会调用和评估它。编译器无法确定一个方法(或getter)是否是确定性的和纯的(没有副作用)。
请注意,属性的实际计算可能由JIT编译器内联,使其与简单字段一样快。
使资产评估成为一项廉价的操作是一种好的做法。如果在getter中进行了一些繁重的计算,请考虑手动缓存结果,或者将其更改为方法。
rwqw0loc5#
为什么不测试一下呢?
只要设置2个控制台应用程序,让它看起来1000万次,并比较结果...请记住,将它们作为正确发布的应用程序运行,这些应用程序已经正确安装,否则您无法保证您不仅仅是在运行MSIL。
实际上,你可能会得到大约5个答案,说“你不应该担心优化”。它们显然不会编写在可读之前需要尽可能快的例程(例如游戏)。
如果这段代码是执行数十亿次的循环的一部分,那么这种优化可能是值得的。例如,max结果可能是一个重写的方法,因此您可能需要讨论虚方法调用。
回答这些问题的唯一方法是弄清楚这是一段将从优化中受益的代码。然后你需要知道哪些事情会增加执行时间。实际上,我们这些凡人无法先验地做到这一点,所以必须简单地尝试2-3个不同版本的代码,然后测试它。
lokaqttq6#
如果
criteria
是一个类类型,我怀疑它是否会被优化,因为在此期间,另一个线程总是可以更改该值。对于struct
,我不确定,但我的直觉是它不会被优化,但我认为无论如何,在这种情况下,它不会在性能上有太大的差异。3wabscal7#
每个人都给出了非常学术和理论的答案。更糟糕的是,有些人声称的东西,我真的很想看到一个现实生活的例子。(比如说有时候第二个代码会运行得更快。
这是你从我在C#中的类似经历中得到的真实答案。第一个代码每次测试这种类型的示例时都跑得更快,这让我认为C#很少或从未优化过这种类型的东西。即使在不使用getter时也是如此;即直接访问成员时。(我经常测试这类东西,因为我喜欢性能调整)。获得会员越困难,就越重要。例如:
一般来说,根据我的经验,如果你不确定C#编译器会优化某些东西,它不会。这与C++(非托管)编译器相反,它可以优化真正疯狂的东西。
***旁注:***在我看来,第一个选项在其他方面也更好,比如代码大小、寄存器缓存和代码可读性。所以这真的很简单。