为什么第二个 for 循环总是比第一个循环执行得快?
我试图弄清楚 for 循环是否比 foreach 循环更快,并使用 System.Diagnostics 类来计时任务。 在运行测试时,我注意到我放在第一个的循环总是比最后一个循环执行得慢。 有人可以告诉我为什么会发生这种情况吗? 我的代码如下:
using System;
using System.Diagnostics;
namespace cool {
class Program {
static void Main(string[] args) {
int[] x = new int[] { 3, 6, 9, 12 };
int[] y = new int[] { 3, 6, 9, 12 };
DateTime startTime = DateTime.Now;
for (int i = 0; i < 4; i++) {
Console.WriteLine(x[i]);
}
TimeSpan elapsedTime = DateTime.Now - startTime;
DateTime startTime2 = DateTime.Now;
foreach (var item in y) {
Console.WriteLine(item);
}
TimeSpan elapsedTime2 = DateTime.Now - startTime2;
Console.WriteLine("\nSummary");
Console.WriteLine("--------------------------\n");
Console.WriteLine("for:\t{0}\nforeach:\t{1}", elapsedTime, elapsedTime2);
Console.ReadKey();
}
}
}
这是输出:
for: 00:00:00.0175781
foreach: 00:00:00.0009766
I was trying to figure out if a for loop was faster than a foreach loop and was using the System.Diagnostics classes to time the task. While running the test I noticed that which ever loop I put first always executes slower then the last one. Can someone please tell me why this is happening? My code is below:
using System;
using System.Diagnostics;
namespace cool {
class Program {
static void Main(string[] args) {
int[] x = new int[] { 3, 6, 9, 12 };
int[] y = new int[] { 3, 6, 9, 12 };
DateTime startTime = DateTime.Now;
for (int i = 0; i < 4; i++) {
Console.WriteLine(x[i]);
}
TimeSpan elapsedTime = DateTime.Now - startTime;
DateTime startTime2 = DateTime.Now;
foreach (var item in y) {
Console.WriteLine(item);
}
TimeSpan elapsedTime2 = DateTime.Now - startTime2;
Console.WriteLine("\nSummary");
Console.WriteLine("--------------------------\n");
Console.WriteLine("for:\t{0}\nforeach:\t{1}", elapsedTime, elapsedTime2);
Console.ReadKey();
}
}
}
Here is the output:
for: 00:00:00.0175781
foreach: 00:00:00.0009766
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(8)
原因是 foreach 版本中有几种形式的开销,而这些开销在 for 循环
IEnumerator.Current
进行底层访问,这是一个方法调用。 因为它位于接口上,所以无法内联。 这意味着 N 个方法调用,其中 N 是枚举中的元素数量。 for 循环仅使用索引器请注意,我上面列出的东西不一定一定是巨大的成本。 它们通常是非常小的成本,可能会导致很小的性能差异。
另请注意,正如 Mehrdad 指出的那样,编译器和 JIT 可能会选择针对某些已知数据结构(例如数组)优化 foreach 循环。 最终的结果可能只是一个for循环。
注意:您的性能基准通常需要做更多的工作才能准确。
The reason why is there are several forms of overhead in the foreach version that are not present in the for loop
IEnumerator<T>.Current
which is a method call. Because it's on an interface it cannot be inlined. This means N method calls where N is the number of elements in the enumeration. The for loop just uses and indexerPlease note that the things I listed above are not necessarily huge costs. They are typically very small costs that can contribute to a small performance difference.
Also note, as Mehrdad pointed out, the compilers and JIT may choose to optimize a foreach loop for certain known data structures such as an array. The end result may just be a for loop.
Note: Your performance benchmark in general needs a bit more work to be accurate.
您应该使用秒表来计时该行为。
从技术上讲,for 循环更快。 Foreach 在 IEnumerable 的迭代器上调用 MoveNext() 方法(通过调用创建方法堆栈和其他开销),而 for 只需递增一个变量。
You should be using the StopWatch to time the behavior.
Technically the for loop is faster. Foreach calls the MoveNext() method (creating a method stack and other overhead from a call) on the IEnumerable's iterator, when for only has to increment a variable.
我不明白为什么这里的每个人都说在这种特殊情况下
for
会比foreach
更快。 对于List
,通过 Listforeach
比通过List
)。for
慢大约 2 倍;事实上,
foreach
会比这里的for
稍微快。 因为数组上的foreach
本质上会编译为:使用
.Length
作为停止条件允许 JIT 删除对数组访问的边界检查,因为它是一种特殊情况。 使用i < 4
使 JIT 插入额外的指令来检查每次迭代 i 是否超出数组范围,如果超出则抛出异常。 但是,使用.Length
,它可以保证您永远不会超出数组边界,因此边界检查是多余的,从而使其更快。然而,在大多数循环中,与内部完成的工作相比,循环的开销是微不足道的。
我猜您所看到的差异只能由 JIT 来解释。
I don't see why everyone here says that
for
would be faster thanforeach
in this particular case. For aList<T>
, it is (about 2x slower toforeach
through a List than tofor
through aList<T>
).In fact, the
foreach
will be slightly faster than thefor
here. Becauseforeach
on an array essentially compiles to:Using
.Length
as a stop criteria allows the JIT to remove bounds checks on the array access, since it's a special case. Usingi < 4
makes the JIT insert extra instructions to check each iteration whether or noti
is out of bounds of the array, and throw an exception if that is the case. However, with.Length
, it can guarantee you'll never go outside of the array bounds so the bounds checks are redundant, making it faster.However, in most loops, the overhead of the loop is insignificant compared to the work done inside.
The discrepancy you're seeing can only be explained by the JIT I guess.
我不会对此进行太多解读 - 由于以下原因,这不是一个好的分析代码
1. DateTime 不适用于分析。 您应该使用 QueryPerformanceCounter 或 StopWatch,它们使用 CPU 硬件配置文件计数器
2. Console.WriteLine 是一种设备方法,因此可能会有微妙的影响,例如要考虑到的缓冲
3.运行每个代码块的一次迭代永远不会给你准确的结果,因为你的CPU做了很多时髦的动态优化,例如乱序执行和指令调度
4. 两个代码块进行 JIT 处理的代码很可能非常相似,因此很可能位于第二个代码块的指令缓存中
为了更好地了解时序,我执行了以下操作:
当我这样做时,我得到以下结果:
for 循环花费了 0.000676 毫秒
foreach 循环花费了 0.000653 毫秒
所以 foreach 稍微快一点,但也不是快很多,
然后我做了一些进一步的实验,首先运行 foreach 块,然后运行 for 块
当我这样做时,我得到了以下结果:
foreach 循环花费了 0.000702 毫秒
for 循环花费了 0.000691 毫秒
最后我将两个循环一起运行了两次,即 for + foreach 然后再次 for + foreach
当我这样做时,我得到了以下结果:
foreach 循环花费了 0.00140 毫秒
for循环花费了0.001385毫秒
所以基本上在我看来,无论你第二次运行什么代码,运行速度都会稍微快一点,但不会
足以具有任何意义。
--编辑--
这里有一些有用的链接
如何使用 QueryPerformanceCounter 对托管代码进行计时
指令缓存
乱序执行
I wouldn't read too much into this - this isn't good profiling code for the following reasons
1. DateTime isn't meant for profiling. You should use QueryPerformanceCounter or StopWatch which use the CPU hardware profile counters
2. Console.WriteLine is a device method so there may be subtle effects such as buffering to take into account
3. Running one iteration of each code block will never give you accurate results because your CPU does a lot of funky on the fly optimisation such as out of order execution and instruction scheduling
4. Chances are the code that gets JITed for both code blocks is fairly similar so is likely to be in the instruction cache for the second code block
To get a better idea of timing, I did the following
When I did that I got the following results:
The for loop took 0.000676 milliseconds
The foreach loop took 0.000653 milliseconds
So foreach was very slightly faster but not by much
I then did some further experiments and ran the foreach block first and the for block second
When I did that I got the following results:
The foreach loop took 0.000702 milliseconds
The for loop took 0.000691 milliseconds
Finally I ran both loops together twice i.e for + foreach then for + foreach again
When I did that I got the following results:
The foreach loop took 0.00140 milliseconds
The for loop took 0.001385 milliseconds
So basically it looks to me that whatever code you run second, runs very slightly faster but not
enough to be of any significance.
--Edit--
Here are a couple of useful links
How to time managed code using QueryPerformanceCounter
The instruction cache
Out of order execution
可能是因为类(例如 Console)第一次需要进行 JIT 编译。 您将通过首先调用所有方法(对它们进行 JIT(预热然后启动))然后执行测试来获得最佳指标。
正如其他用户所指出的,4 次传递永远不足以向您展示差异。
顺便说一句,for 和 foreach 之间的性能差异可以忽略不计,并且使用 foreach 的可读性优势几乎总是超过任何边际性能优势。
Probably because the classes (e.g. Console) need to be JIT-compiled the first time through. You'll get the best metrics by calling all methods (to JIT them (warm then up)) first, then performing the test.
As other users have indicated, 4 passes is never going to be enough to to show you the difference.
Incidentally, the difference in performance between for and foreach will be negligible and the readability benefits of using foreach almost always outweigh any marginal performance benefit.
Console.WriteLine
。Stopwatch
class.Console.WriteLine
in your loop.我对 C# 不太了解,但当我没记错时,微软正在为 Java 构建“Just in Time”编译器。 当他们在 C# 中使用相同或相似的技术时,“一些其次的结构执行得更快”是很自然的。
例如,JIT 系统可能会看到循环被执行并决定临时编译整个方法。 因此,当到达第二个循环时,它的编译速度和执行速度比第一个循环快得多。 但这是我的一个相当简单的猜测。 当然,您需要对 C# 运行时系统有更深入的了解才能了解正在发生的情况。 也可能是,RAM 页在第一个循环中首先被访问,而在第二个循环中它仍然位于 CPU 高速缓存中。
Addon:另一个评论是:输出模块可以在第一个循环中第一次进行 JITed,这对我来说比我的第一个猜测更有可能。 现代语言非常复杂,要找出其幕后所做的事情是非常复杂的。 我的这个陈述也符合这个猜测:
但是你的循环中也有终端输出。 它们让事情变得更加困难。 也可能是,在程序中首次打开终端需要花费一些时间。
I am not so much in C#, but when I remember right, Microsoft was building "Just in Time" compilers for Java. When they use the same or similar techniques in C#, it would be rather natural that "some constructs coming second perform faster".
For example it could be, that the JIT-System sees that a loop is executed and decides adhoc to compile the whole method. Hence when the second loop is reached, it is yet compiled and performs much faster than the first. But this is a rather simplistic guess of mine. Of course you need a far greater insight in the C# runtime system to understand what is going on. It could also be, that the RAM-Page is accessed first in the first loop and in the second it is still in the CPU-cache.
Addon: The other comment that was made: that the output module can be JITed a first time in the first loop seams to me more likely than my first guess. Modern languages are just very complex to find out what is done under the hood. Also this statement of mine fits into this guess:
But also you have terminal-outputs in your loops. They make things yet more difficult. It could also be, that it costs some time to open the terminal a first time in a program.
我只是进行测试以获得一些真实的数字,但与此同时,Gaz 比我先找到了答案 - 对 Console.Writeline 的调用在第一次调用时就被触发,因此您在第一个循环中支付该成本。
只是为了提供信息 - 使用秒表而不是日期时间并测量刻度数:
在第一个循环之前没有调用 Console.Writeline ,时间是
调用 Console.Writeline 虽然
这些结果并不总是可重复的,因为运行次数有限,但差异的大小总是大致相同。
编辑后的代码供参考:
I was just performing tests to get some real numbers, but in the meantime Gaz beat me to the answer - the call to Console.Writeline is jitted at the first call, so you pay that cost in the first loop.
Just for information though - using a stopwatch rather than the datetime and measuring number of ticks:
Without a call to Console.Writeline before the first loop the times were
with a call to Console.Writeline they were
Though these results were not consistently repeatable because of the limited number of runs, but the magnitude of difference was always roughly the same.
The edited code for reference: