C# 为什么枚举范围比直接产生循环快?
下面的代码正在检查执行相同解决方案的三种不同方法的性能C# 为什么枚举范围比直接产生循环快?,c#,performance,ienumerable,range,enumerable,C#,Performance,Ienumerable,Range,Enumerable,下面的代码正在检查执行相同解决方案的三种不同方法的性能 public static void Main(string[] args) { // for loop { Stopwatch sw = Stopwatch.StartNew(); int accumulator = 0; for (int i = 1; i <= 100000000; ++i)
public static void Main(string[] args)
{
// for loop
{
Stopwatch sw = Stopwatch.StartNew();
int accumulator = 0;
for (int i = 1; i <= 100000000; ++i)
{
accumulator += i;
}
sw.Stop();
Console.WriteLine("time = {0}; result = {1}", sw.ElapsedMilliseconds, accumulator);
}
//Enumerable.Range
{
Stopwatch sw = Stopwatch.StartNew();
var ret = Enumerable.Range(1, 100000000).Aggregate(0, (accumulator, n) => accumulator + n);
sw.Stop();
Console.WriteLine("time = {0}; result = {1}", sw.ElapsedMilliseconds, ret);
}
//self-made IEnumerable<int>
{
Stopwatch sw = Stopwatch.StartNew();
var ret = GetIntRange(1, 100000000).Aggregate(0, (accumulator, n) => accumulator + n);
sw.Stop();
Console.WriteLine("time = {0}; result = {1}", sw.ElapsedMilliseconds, ret);
}
}
private static IEnumerable<int> GetIntRange(int start, int count)
{
int end = start + count;
for (int i = start; i < end; ++i)
{
yield return i;
}
}
}
毫不奇怪,“for循环”比其他两种解决方案更快,因为Enumerable.Aggregate需要更多的方法调用。然而,“Enumerable.Range”比“自制的IEnumerable”要快,这让我非常惊讶。我认为Enumerable.Range比简单的GetIntRange方法有更多的开销
产生这种情况的可能原因是什么?假设这是一个正在运行的发布版本,否则所有的比较都将关闭,因为JIT将无法完全工作
您可以使用查看程序集,并查看“yield”语句的扩展内容。编译器将创建一个类来封装迭代器。可能生成的代码比Enumerable.Range的实现进行了更多的内务处理,它很可能是手工编码的为什么
Enumerable.Range
要比您自制的GetIntRange
慢?事实上,如果可枚举.Range
被定义为
public static class Enumerable {
public static IEnumerable<int> Range(int start, int count) {
var end = start + count;
for(var current = start; current < end; ++current) {
yield return current;
}
}
}
我猜你正在调试器中运行。以下是我的结果,使用“/o+/debug”从命令行生成--
还有一点不同,但没有那么明显。迭代器块实现的效率不如定制的解决方案,但它们非常好。反射器输出中的细微差别(以及参数检查和额外的内部化级别在这里绝对不相关)。基本代码更像是:
public static IEnumerable<int> Range(int start, int count) {
for(int current = 0; current < count; ++current) {
yield return start + current;
}
}
是的,我是在调试模式下做实验的。因此,自制的方法生成调试代码。发布速度要快得多。这里有两件事:在调试模式下构建,在调试器中运行,而不是在未连接调试器的情况下执行。后者的影响更大。
public static class Enumerable {
public static IEnumerable<int> Range(int start, int count) {
return new RangeEnumerable(start, count);
}
private class RangeEnumerable : IEnumerable<int> {
private int _Start;
private int _Count;
public RangeEnumerable(int start, int count) {
_Start = start;
_Count = count;
}
public virtual IEnumerator<int> GetEnumerator() {
return new RangeEnumerator(_Start, _Count);
}
IEnumerator IEnumerable.GetEnumerator() {
return GetEnumerator();
}
}
private class RangeEnumerator : IEnumerator<int> {
private int _Current;
private int _End;
public RangeEnumerator(int start, int count) {
_Current = start - 1;
_End = start + count;
}
public virtual void Dispose() {
_Current = _End;
}
public virtual void Reset() {
throw new NotImplementedException();
}
public virtual bool MoveNext() {
++_Current;
return _Current < _End;
}
public virtual int Current { get { return _Current; } }
object IEnumerator.Current { get { return Current; } }
}
}
time = 142; result = 987459712
time = 1590; result = 987459712
time = 1792; result = 987459712
public static IEnumerable<int> Range(int start, int count) {
for(int current = 0; current < count; ++current) {
yield return start + current;
}
}
Run 0:
time = 4149; result = 405000000450000000
time = 25645; result = 405000000450000000
time = 39229; result = 405000000450000000
time = 29872; result = 405000000450000000
time = 4277; result = 405000000450000000
time = 26878; result = 405000000450000000
time = 26333; result = 405000000450000000
time = 26684; result = 405000000450000000
Run 1:
time = 4063; result = 405000000450000000
time = 22714; result = 405000000450000000
time = 34744; result = 405000000450000000
time = 26954; result = 405000000450000000
time = 4033; result = 405000000450000000
time = 26657; result = 405000000450000000
time = 25855; result = 405000000450000000
time = 25031; result = 405000000450000000
Run 2:
time = 4021; result = 405000000450000000
time = 21815; result = 405000000450000000
time = 34304; result = 405000000450000000
time = 32040; result = 405000000450000000
time = 3993; result = 405000000450000000
time = 24779; result = 405000000450000000
time = 29275; result = 405000000450000000
time = 32254; result = 405000000450000000
using System;
using System.Linq;
using System.Collections.Generic;
using System.Diagnostics;
namespace RangeTests
{
class TestRange
{
public static void Main(string[] args)
{
for(int l = 1; l <= 2; ++l)
{
const int N = 900000000;
System.GC.Collect(2);
// for loop
{
Stopwatch sw = Stopwatch.StartNew();
long accumulator = 0;
for (int i = 1; i <= N; ++i)
{
accumulator += i;
}
sw.Stop();
Console.WriteLine("time = {0}; result = {1}", sw.ElapsedMilliseconds, accumulator);
}
System.GC.Collect(2);
//Enumerable.Range
{
Stopwatch sw = Stopwatch.StartNew();
var ret = Enumerable.Range(1, N).Aggregate(0, (long accumulator,int n) => accumulator + n);
sw.Stop();
Console.WriteLine("time = {0}; result = {1}", sw.ElapsedMilliseconds, ret);
}
System.GC.Collect(2);
//self-made IEnumerable<int>
{
Stopwatch sw = Stopwatch.StartNew();
var ret = GetIntRange(1, N).Aggregate(0, (long accumulator,int n) => accumulator + n);
sw.Stop();
Console.WriteLine("time = {0}; result = {1}", sw.ElapsedMilliseconds, ret);
}
System.GC.Collect(2);
//self-made adjusted IEnumerable<int>
{
Stopwatch sw = Stopwatch.StartNew();
var ret = GetRange(1, N).Aggregate(0, (long accumulator,int n) => accumulator + n);
sw.Stop();
Console.WriteLine("time = {0}; result = {1}", sw.ElapsedMilliseconds, ret);
}
System.GC.Collect(2);
Console.WriteLine();
} }
private static IEnumerable<int> GetIntRange(int start, int count)
{
int end = start + count;
for (int i = start; i < end; ++i)
{
yield return i;
}
}
private static IEnumerable<int> GetRange(int start, int count)
{
for (int i = 0; i < count; ++i)
{
yield return start + i;
}
}
} }
csc.exe -optimize+ -debug- RangeTests.cs