C# – For-loop内部
一个关于for循环的快速,简单的问题。
情况当我突然想知道for循环实际上是如何表现时,我正在编写一些高性能代码。 我知道我之前偶然发现了这一点,但不能为我的生活再次找到这个信息:/
不过,我主要担心的是限制器。 说我们有:
for(int i = 0; i < something.awesome; i++) { // Do cool stuff }
问题是something.awesome存储为内部变量或循环不断检索something.awesome进行逻辑检查? 我问的原因当然是因为我需要遍历很多索引的东西而且我真的不希望每次传递都有额外的函数调用开销。
然而,如果something.awesome只被召唤一次,那么我会回到我快乐的摇滚之下! 🙂
您可以使用简单的示例程序来检查行为:
using System; class Program { static int GetUpperBound() { Console.WriteLine("GetUpperBound called."); return 5; } static void Main(string[] args) { for (int i = 0; i < GetUpperBound(); i++) { Console.WriteLine("Loop iteration {0}.", i); } } }
输出如下:
GetUpperBound called. Loop iteration 0. GetUpperBound called. Loop iteration 1. GetUpperBound called. Loop iteration 2. GetUpperBound called. Loop iteration 3. GetUpperBound called. Loop iteration 4. GetUpperBound called.
有关此行为的详细信息,请参阅C#4.0语言规范,第8.3.3节(您将在C:\ Program Files \ Microsoft Visual Studio 10.0 \ VC#\ Specifications \ 1033中找到规范 ):
for语句执行如下:
如果存在for-initializer,则变量初始值设定项或语句表达式按其写入顺序执行。 此步骤仅执行一次。
如果存在for条件,则对其进行评估。
如果for条件不存在或者评估结果为true,则控制转移到嵌入语句。 当控制到达嵌入语句的结束点时(可能来自执行continue语句),for-iterator的表达式(如果有的话)按顺序计算,然后执行另一次迭代,从评估在上面的步骤中的条件。
如果for条件存在且评估结果为false,则控制转移到for语句的结束点。
如果something.awesome是一个字段,那么每次循环时都可能访问它,因为循环体中的某些内容可能会更新它。 如果循环的主体足够简单并且不调用任何方法(除了编译器内联的方法),那么编译器可能能够certificate将something.awesome的值放在寄存器中是安全的。 编译器编写者过去经常做很多事情。
但是现在从主内存访问一个值需要很长时间,但是一旦第一次读取该值,它就会被CPU辅助。 从CPU缓存中第二次读取值的速度要快得多,从寄存器读取它然后从主存储器读取它。 CPU缓存比主内存快几百倍并不罕见。
现在,如果something.awesome是一个属性 ,那么它实际上是一个方法调用。 编译器将在每次循环时调用该方法。 但是,如果属性/方法只有几行代码,则它可能由编译器内联。 内联是指编译器直接放入方法代码的副本而不是调用方法,因此只返回字段值的属性将与上面的字段示例相同。
当Evan没有内联属性时,它将在第一次被调用之后在CPU缓存中 。 所以,它很复杂,或者循环很多次,第一次循环调用需要花费更长的时间,可能超过10倍。
在过去 ,它过去很容易,因为所有内存访问和CPU操作大约需要同一时间。 目前,处理器缓存可以轻松地将某些内存访问和方法调用的时间更改为100倍 。 Profilers倾向于仍然认为所有内存访问都需要相同的时间! 因此,如果您描述,您将被告知进行可能对现实世界没有任何影响的更改。
将代码更改为:
int limit = something.awesome; for(int i = 0; i < limit; i++) { // Do cool stuff }
在某些情况下会扩散它,但也会使它更复杂。 然而
int limit = myArray.length; for(int i = 0; i < limit; i++) { myArray[i[ = xyn; }
比那慢
for(int i = 0; i < myArray.length; i++) { myArray[i[ = xyn; }
as .net每次访问时检查数组的范围,并且当循环足够简单时,有逻辑删除检查。
因此,最好保持代码简单明了,直到您可以certificate存在问题。 通过花时间改进系统的整体设计,您可以获得更好的收益,如果您开始使用的代码很简单,这很容易做到。
它每次评估。 在一个简单的控制台应用程序中试试
public class MyClass { public int Value { get { Console.WriteLine("Value called"); return 3; } } }
以这种方式使用:
MyClass myClass = new MyClass(); for (int i = 0; i < myClass.Value; i++) { }
将导致三行打印到屏幕上。
更新
所以为了避免这种情况,你可以这样:
int awesome = something.awesome; for(int i = 0; i < awesome; i++) { // Do cool stuff }
每次循环时都会重新评估something.awesome
。
这样做会更好:
int limit = something.awesome; for(int i = 0; i < limit; i++) { // Do cool stuff }
每次编译器都会检索something.awesome的值并进行评估
每次评估条件,包括检索something.awesome
的值。 如果要避免这种情况,请将temp变量设置为something.awesome
然后将其与temp变量进行比较。
它将存储一些变量并在使用后清除它。
使用(int limit = something.awesome)
{
for(int i = 0; i
//码。
}
}
这样每次都不会检查。