Please ignore code readability in this question.
请忽略这个问题中的代码可读性。
In terms of performance, should the following code be written like this:
在性能方面,以下代码是否应该这样写:
int maxResults = criteria.MaxResults;
if (maxResults > 0)
{
while (accounts.Count > maxResults)
accounts.RemoveAt(maxResults);
}
or like this:
或者像这样:
if (criteria.MaxResults > 0)
{
while (accounts.Count > criteria.MaxResults)
accounts.RemoveAt(criteria.MaxResults);
}
?
吗?
Edit: criteria
is a class
, and MaxResults
is a simple integer property (i.e., public int MaxResults { get { return _maxResults; } }
.
Edit: criteria是一个类,MaxResults是一个简单的整型属性(例如, public int MaxResults {get {return _maxResults;} }。
Does the C# compiler treat MaxResults
as a black box and evaluate it every time? Or is it smart enough to figure out that I've got 3 calls to the same property with no modification of that property between the calls? What if MaxResults
was a field?
c#编译器是否将MaxResults视为一个黑盒并每次都进行评估?还是说我有3次调用同一个属性而调用之间没有对属性进行修改?如果MaxResults是一个字段呢?
One of the laws of optimization is precalculation, so I instinctively wrote this code like the first listing, but I'm curious if this kind of thing is being done for me automatically (again, ignore code readability).
优化的一个法则是预计算,所以我本能地编写了这段代码,就像第一次列表一样,但是我很好奇这种事情是否会自动完成(同样,忽略代码可读性)。
(Note: I'm not interested in hearing the 'micro-optimization' argument, which may be valid in the specific case I've posted. I'd just like some theory behind what's going on or not going on.)
(注意:我对“微优化”的说法不感兴趣,它可能适用于我所发布的特定情况。我只是想知道到底发生了什么,或者没有发生什么。
6 个解决方案
#1
58
First off, the only way to actually answer performance questions is to actually try it both ways and test the results in realistic conditions.
首先,真正回答性能问题的唯一方法是同时尝试这两种方法,并在实际情况下测试结果。
That said, the other answers which say that "the compiler" does not do this optimization because the property might have side effects are both right and wrong. The problem with the question (aside from the fundamental problem that it simply cannot be answered without actually trying it and measuring the result) is that "the compiler" is actually two compilers: the C# compiler, which compiles to MSIL, and the JIT compiler, which compiles IL to machine code.
也就是说,其他回答说“编译器”不做这种优化,因为属性可能有副作用,这是对的也是错的。这个问题的问题(除了根本问题之外,没有实际的尝试和测量结果,它是无法回答的)是“编译器”实际上是两个编译器:编译到MSIL的c#编译器,以及编译IL到机器码的JIT编译器。
The C# compiler never ever does this sort of optimization; as noted, doing so would require that the compiler peer into the code being called and verify that the result it computes does not change over the lifetime of the callee's code. The C# compiler does not do so.
c#编译器从来没有做过这种优化;如前所述,这样做将要求编译器对所调用的代码进行检查,并验证它计算的结果不会在callee的代码的生命周期内发生变化。c#编译器不会这样做。
The JIT compiler might. No reason why it couldn't. It has all the code sitting right there. It is completely free to inline the property getter, and if the jitter determines that the inlined property getter returns a value that can be cached in a register and re-used, then it is free to do so. (If you don't want it to do so because the value could be modified on another thread then you already have a race condition bug; fix the bug before you worry about performance.)
JIT编译器。没有理由它不能。所有的代码都在这里。它可以完全*地内联属性getter,如果抖动器确定内联属性getter返回一个值,该值可以缓存在寄存器中并重新使用,那么它可以*这么做。(如果您不希望它这样做,因为该值可以在另一个线程上修改,那么您已经有了一个竞态条件bug;在您担心性能之前修复bug。
Whether the jitter actually does inline the property fetch and then enregister the value, I have no idea. I know practically nothing about the jitter. But it is allowed to do so if it sees fit. If you are curious about whether it does so or not, you can either (1) ask someone who is on the team that wrote the jitter, or (2) examine the jitted code in the debugger.
jitter实际上是否内联了属性获取,然后注册值,我不知道。我对抖动几乎一无所知。但如果它认为合适,它可以这么做。如果您对它是否这样做感到好奇,您可以(1)询问编写抖动的团队中的某人,或者(2)检查调试器中的j埃弗顿代码。
And finally, let me take this opportunity to note that computing results once, storing the result and re-using it is not always an optimization. This is a surprisingly complicated question. There are all kinds of things to optimize for:
最后,让我借此机会注意一下,计算结果一次,存储结果并重新使用它并不总是一个优化。这是一个非常复杂的问题。有各种各样的东西可以优化:
-
execution time
执行时间
-
executable code size -- this has a major effect on executable time because big code takes longer to load, increases the working set size, puts pressure on processor caches, RAM and the page file. Small slow code is often in the long run faster than big fast code in important metrics like startup time and cache locality.
可执行代码大小——这对可执行时间有很大的影响,因为大的代码需要更长的时间来加载,增加了工作集大小,对处理器缓存、RAM和页面文件造成了压力。在启动时间和缓存位置等重要指标上,小的慢代码在长期运行中通常比大的快代码快。
-
register allocation -- this also has a major effect on execution time, particularly in architectures like x86 which have a small number of available registers. Enregistering a value for fast re-use can mean that there are fewer registers available for other operations that need optimization; perhaps optimizing those operations instead would be a net win.
寄存器分配——这对执行时间也有很大的影响,特别是在x86这样的体系结构中,x86具有少量可用寄存器。为快速重用注册一个值可能意味着需要优化的其他操作可用的寄存器更少;也许优化这些操作将是一个净收益。
-
and so on. It get real complicated real fast.
等等。它变得非常复杂。
In short, you cannot possibly know whether writing the code to cache the result rather than recomputing it is actually (1) faster, or (2) better performing. Better performance does not always mean making execution of a particular routine faster. Better performance is about figuring out what resources are important to the user -- execution time, memory, working set, startup time, and so on -- and optimizing for those things. You cannot do that without (1) talking to your customers to find out what they care about, and (2) actually measuring to see if your changes are having a measurable effect in the desired direction.
简而言之,您不可能知道编写缓存结果而不是重新计算结果的代码实际上(1)更快,还是(2)性能更好。更好的性能并不总是意味着更快地执行特定的例程。更好的性能是找出对用户来说什么资源是重要的——执行时间、内存、工作集、启动时间等等——并对这些资源进行优化。要做到这一点,你必须(1)与客户交谈,了解他们关心什么;(2)实际测量,看看你的变化是否在期望的方向上产生了可衡量的效果。
#2
7
If MaxResults
is a property then no, it will not optimize it, because the getter may have complex logic, say:
如果MaxResults是一个属性,那么它就不会对它进行优化,因为getter可能具有复杂的逻辑,比如:
private int _maxResults;
public int MaxReuslts {
get { return _maxResults++; }
set { _maxResults = value; }
}
See how the behavior would change if it in-lines your code?
看看在代码行中行为会如何变化?
If there's no logic...either method you wrote is fine, it's a very minute difference and all about how readable it is TO YOU (or your team)...you're the one looking at it.
如果没有逻辑……你写的任何一种方法都可以,这是非常细微的差别,都是关于它对你(或你的团队)的可读性。是你在看着它。
#3
6
Your two code samples are only guaranteed to have the same result in single-threaded environments, which .Net isn't, and if MaxResults
is a field (not a property). The compiler can't assume, unless you use the synchronization features, that criteria.MaxResults
won't change during the course of your loop. If it's a property, it can't assume that using the property doesn't have side effects.
您的两个代码示例只在单线程环境中具有相同的结果,而。net没有,如果MaxResults是一个字段(而不是属性)。编译器不能假设,除非您使用同步特性,否则该条件。MaxResults在循环过程中不会改变。如果它是一个属性,它不能假设使用这个属性没有副作用。
Eric Lippert points out quite correctly that it depends a lot on what you mean by "the compiler". The C# -> IL compiler? Or the IL -> machine code (JIT) compiler? And he's right to point out that the JIT may well be able to optimize the property getter, since it has all of the information (whereas the C# -> IL compiler doesn't, necessarily). It won't change the situation with multiple threads, but it's a good point nonetheless.
Eric Lippert非常正确地指出这在很大程度上取决于你所说的“编译器”。c# -> IL编译器?还是IL ->机器代码(JIT)编译器?他指出,JIT很可能能够优化属性getter,因为它包含所有的信息(而c# -> IL编译器不一定)。它不会改变多线程的情况,但这是一个很好的观点。
#4
4
It will be called and evaluated every time. The compiler has no way of determining if a method (or getter) is deterministic and pure (no side effects).
每次都会调用并评估它。编译器无法确定方法(或getter)是否具有确定性和纯粹性(没有副作用)。
Note that actual evaluation of the property may be inlined by the JIT compiler, making it effectively as fast as a simple field.
请注意,对属性的实际评估可以由JIT编译器内联,使其与简单字段一样有效。
It's good practise to make property evaluation an inexpensive operation. If you do some heavy calculation in the getter, consider caching the result manually, or changing it to a method.
将财产评估作为一种廉价的操作是很好的做法。如果在getter中进行了大量计算,可以考虑手动缓存结果,或者将其更改为方法。
#5
2
why not test it?
为什么不测试它呢?
just set up 2 console apps make it look 10 million times and compare the results ... remember to run them as properly released apps that have been installed properly or else you cannot gurantee that you are not just running the msil.
只要设置2个控制台应用,让它看起来1000万次,并比较结果……记住,要将它们作为已正确安装的已正确发布的应用程序来运行,否则您不能保证您不仅仅是在运行msil。
Really you are probably going to get about 5 answers saying 'you shouldn't worry about optimisation'. they clearly do not write routines that need to be as fast as possible before being readable (eg games).
实际上,你可能会得到5个答案,说“你不应该担心优化”。很明显,他们不写那些需要在可读之前尽可能快的例程(如游戏)。
If this piece of code is part of a loop that is executed billions of times then this optimisation could be worthwhile. For instance max results could be an overridden method and so you may need to discuss virtual method calls.
如果这段代码是执行了数十亿次的循环的一部分,那么这种优化是值得的。例如,max results可以是一个重写的方法,因此您可能需要讨论虚拟方法调用。
Really the ONLY way to answer any of these questions is to figure out is this is a piece of code that will benefit from optimisation. Then you need to know the kinds of things that are increasing the time to execute. Really us mere mortals cannot do this a priori and so have to simply try 2-3 different versions of the code and then test it.
实际上,回答这些问题的唯一方法就是找出这是一段可以从优化中获益的代码。然后你需要知道那些增加执行时间的事情。真的,我们凡人不可能先验地做到这一点,所以只能简单地尝试2-3个不同版本的代码,然后进行测试。
#6
0
If criteria
is a class type, I doubt it would be optimized, because another thread could always change that value in the meantime. For struct
s I'm not sure, but my gut feeling is that it won't be optimized, but I think it wouldn't make much difference in performance in that case anyhow.
如果criteria是类类型,我怀疑它是否会得到优化,因为另一个线程总是可以同时更改该值。对于structs,我不确定,但我的直觉是它不会被优化,但我认为在那种情况下它不会对性能产生多大影响。
#1
58
First off, the only way to actually answer performance questions is to actually try it both ways and test the results in realistic conditions.
首先,真正回答性能问题的唯一方法是同时尝试这两种方法,并在实际情况下测试结果。
That said, the other answers which say that "the compiler" does not do this optimization because the property might have side effects are both right and wrong. The problem with the question (aside from the fundamental problem that it simply cannot be answered without actually trying it and measuring the result) is that "the compiler" is actually two compilers: the C# compiler, which compiles to MSIL, and the JIT compiler, which compiles IL to machine code.
也就是说,其他回答说“编译器”不做这种优化,因为属性可能有副作用,这是对的也是错的。这个问题的问题(除了根本问题之外,没有实际的尝试和测量结果,它是无法回答的)是“编译器”实际上是两个编译器:编译到MSIL的c#编译器,以及编译IL到机器码的JIT编译器。
The C# compiler never ever does this sort of optimization; as noted, doing so would require that the compiler peer into the code being called and verify that the result it computes does not change over the lifetime of the callee's code. The C# compiler does not do so.
c#编译器从来没有做过这种优化;如前所述,这样做将要求编译器对所调用的代码进行检查,并验证它计算的结果不会在callee的代码的生命周期内发生变化。c#编译器不会这样做。
The JIT compiler might. No reason why it couldn't. It has all the code sitting right there. It is completely free to inline the property getter, and if the jitter determines that the inlined property getter returns a value that can be cached in a register and re-used, then it is free to do so. (If you don't want it to do so because the value could be modified on another thread then you already have a race condition bug; fix the bug before you worry about performance.)
JIT编译器。没有理由它不能。所有的代码都在这里。它可以完全*地内联属性getter,如果抖动器确定内联属性getter返回一个值,该值可以缓存在寄存器中并重新使用,那么它可以*这么做。(如果您不希望它这样做,因为该值可以在另一个线程上修改,那么您已经有了一个竞态条件bug;在您担心性能之前修复bug。
Whether the jitter actually does inline the property fetch and then enregister the value, I have no idea. I know practically nothing about the jitter. But it is allowed to do so if it sees fit. If you are curious about whether it does so or not, you can either (1) ask someone who is on the team that wrote the jitter, or (2) examine the jitted code in the debugger.
jitter实际上是否内联了属性获取,然后注册值,我不知道。我对抖动几乎一无所知。但如果它认为合适,它可以这么做。如果您对它是否这样做感到好奇,您可以(1)询问编写抖动的团队中的某人,或者(2)检查调试器中的j埃弗顿代码。
And finally, let me take this opportunity to note that computing results once, storing the result and re-using it is not always an optimization. This is a surprisingly complicated question. There are all kinds of things to optimize for:
最后,让我借此机会注意一下,计算结果一次,存储结果并重新使用它并不总是一个优化。这是一个非常复杂的问题。有各种各样的东西可以优化:
-
execution time
执行时间
-
executable code size -- this has a major effect on executable time because big code takes longer to load, increases the working set size, puts pressure on processor caches, RAM and the page file. Small slow code is often in the long run faster than big fast code in important metrics like startup time and cache locality.
可执行代码大小——这对可执行时间有很大的影响,因为大的代码需要更长的时间来加载,增加了工作集大小,对处理器缓存、RAM和页面文件造成了压力。在启动时间和缓存位置等重要指标上,小的慢代码在长期运行中通常比大的快代码快。
-
register allocation -- this also has a major effect on execution time, particularly in architectures like x86 which have a small number of available registers. Enregistering a value for fast re-use can mean that there are fewer registers available for other operations that need optimization; perhaps optimizing those operations instead would be a net win.
寄存器分配——这对执行时间也有很大的影响,特别是在x86这样的体系结构中,x86具有少量可用寄存器。为快速重用注册一个值可能意味着需要优化的其他操作可用的寄存器更少;也许优化这些操作将是一个净收益。
-
and so on. It get real complicated real fast.
等等。它变得非常复杂。
In short, you cannot possibly know whether writing the code to cache the result rather than recomputing it is actually (1) faster, or (2) better performing. Better performance does not always mean making execution of a particular routine faster. Better performance is about figuring out what resources are important to the user -- execution time, memory, working set, startup time, and so on -- and optimizing for those things. You cannot do that without (1) talking to your customers to find out what they care about, and (2) actually measuring to see if your changes are having a measurable effect in the desired direction.
简而言之,您不可能知道编写缓存结果而不是重新计算结果的代码实际上(1)更快,还是(2)性能更好。更好的性能并不总是意味着更快地执行特定的例程。更好的性能是找出对用户来说什么资源是重要的——执行时间、内存、工作集、启动时间等等——并对这些资源进行优化。要做到这一点,你必须(1)与客户交谈,了解他们关心什么;(2)实际测量,看看你的变化是否在期望的方向上产生了可衡量的效果。
#2
7
If MaxResults
is a property then no, it will not optimize it, because the getter may have complex logic, say:
如果MaxResults是一个属性,那么它就不会对它进行优化,因为getter可能具有复杂的逻辑,比如:
private int _maxResults;
public int MaxReuslts {
get { return _maxResults++; }
set { _maxResults = value; }
}
See how the behavior would change if it in-lines your code?
看看在代码行中行为会如何变化?
If there's no logic...either method you wrote is fine, it's a very minute difference and all about how readable it is TO YOU (or your team)...you're the one looking at it.
如果没有逻辑……你写的任何一种方法都可以,这是非常细微的差别,都是关于它对你(或你的团队)的可读性。是你在看着它。
#3
6
Your two code samples are only guaranteed to have the same result in single-threaded environments, which .Net isn't, and if MaxResults
is a field (not a property). The compiler can't assume, unless you use the synchronization features, that criteria.MaxResults
won't change during the course of your loop. If it's a property, it can't assume that using the property doesn't have side effects.
您的两个代码示例只在单线程环境中具有相同的结果,而。net没有,如果MaxResults是一个字段(而不是属性)。编译器不能假设,除非您使用同步特性,否则该条件。MaxResults在循环过程中不会改变。如果它是一个属性,它不能假设使用这个属性没有副作用。
Eric Lippert points out quite correctly that it depends a lot on what you mean by "the compiler". The C# -> IL compiler? Or the IL -> machine code (JIT) compiler? And he's right to point out that the JIT may well be able to optimize the property getter, since it has all of the information (whereas the C# -> IL compiler doesn't, necessarily). It won't change the situation with multiple threads, but it's a good point nonetheless.
Eric Lippert非常正确地指出这在很大程度上取决于你所说的“编译器”。c# -> IL编译器?还是IL ->机器代码(JIT)编译器?他指出,JIT很可能能够优化属性getter,因为它包含所有的信息(而c# -> IL编译器不一定)。它不会改变多线程的情况,但这是一个很好的观点。
#4
4
It will be called and evaluated every time. The compiler has no way of determining if a method (or getter) is deterministic and pure (no side effects).
每次都会调用并评估它。编译器无法确定方法(或getter)是否具有确定性和纯粹性(没有副作用)。
Note that actual evaluation of the property may be inlined by the JIT compiler, making it effectively as fast as a simple field.
请注意,对属性的实际评估可以由JIT编译器内联,使其与简单字段一样有效。
It's good practise to make property evaluation an inexpensive operation. If you do some heavy calculation in the getter, consider caching the result manually, or changing it to a method.
将财产评估作为一种廉价的操作是很好的做法。如果在getter中进行了大量计算,可以考虑手动缓存结果,或者将其更改为方法。
#5
2
why not test it?
为什么不测试它呢?
just set up 2 console apps make it look 10 million times and compare the results ... remember to run them as properly released apps that have been installed properly or else you cannot gurantee that you are not just running the msil.
只要设置2个控制台应用,让它看起来1000万次,并比较结果……记住,要将它们作为已正确安装的已正确发布的应用程序来运行,否则您不能保证您不仅仅是在运行msil。
Really you are probably going to get about 5 answers saying 'you shouldn't worry about optimisation'. they clearly do not write routines that need to be as fast as possible before being readable (eg games).
实际上,你可能会得到5个答案,说“你不应该担心优化”。很明显,他们不写那些需要在可读之前尽可能快的例程(如游戏)。
If this piece of code is part of a loop that is executed billions of times then this optimisation could be worthwhile. For instance max results could be an overridden method and so you may need to discuss virtual method calls.
如果这段代码是执行了数十亿次的循环的一部分,那么这种优化是值得的。例如,max results可以是一个重写的方法,因此您可能需要讨论虚拟方法调用。
Really the ONLY way to answer any of these questions is to figure out is this is a piece of code that will benefit from optimisation. Then you need to know the kinds of things that are increasing the time to execute. Really us mere mortals cannot do this a priori and so have to simply try 2-3 different versions of the code and then test it.
实际上,回答这些问题的唯一方法就是找出这是一段可以从优化中获益的代码。然后你需要知道那些增加执行时间的事情。真的,我们凡人不可能先验地做到这一点,所以只能简单地尝试2-3个不同版本的代码,然后进行测试。
#6
0
If criteria
is a class type, I doubt it would be optimized, because another thread could always change that value in the meantime. For struct
s I'm not sure, but my gut feeling is that it won't be optimized, but I think it wouldn't make much difference in performance in that case anyhow.
如果criteria是类类型,我怀疑它是否会得到优化,因为另一个线程总是可以同时更改该值。对于structs,我不确定,但我的直觉是它不会被优化,但我认为在那种情况下它不会对性能产生多大影响。