ENUMERABLE. 직접 수익률 루프보다 빠르게 정리할 수있는 이유는 무엇입니까?
-
03-07-2019 - |
문제
아래 코드는 동일한 솔루션을 수행하는 세 가지 방법의 성능을 확인하는 것입니다.
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;
}
}
}
결과는 다음과 같습니다.
time = 306; result = 987459712
time = 1301; result = 987459712
time = 2860; result = 987459712
열거 가능이기 때문에 "루프 용"이 다른 두 솔루션보다 빠르다는 것은 놀라운 일이 아닙니다. 그러나 "열거 가능한"이 "자체 제작자"보다 빠르다는 것은 정말 놀랍습니다. 나는 열거 할 수 있다고 생각했다. 정열은 간단한 getIntrange 방법보다 더 많은 오버 헤드를 가질 것이라고 생각했다.
이에 대한 가능한 이유는 무엇입니까?
해결책
왜 Enumerable.Range
자기 만든 것보다 느리게하십시오 GetIntRange
? 사실, if Enumerable.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;
}
}
}
그렇다면 그것은 당신의 스스로 만든 것만 큼 빠르야합니다. GetIntRange
. 이것은 실제로 참조 구현입니다 Enumerable.Range
, 컴파일러 또는 프로그래머의 일부에 트릭이 없습니다.
당신은 당신의 비교하고 싶을 수도 있습니다 GetIntRange
그리고 System.Linq.Enumerable.Range
다음과 같은 구현으로 (물론 Rob가 지적한대로 릴리스 모드로 컴파일). 이 구현은 반복자 블록에서 컴파일러가 생성하는 내용에 대해 약간 최적화 될 수 있습니다.
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; } }
}
}
다른 팁
내 생각에 당신은 디버거에서 실행하고 있다는 것입니다. 다음은 " /o+ /debug-"로 명령 줄에서 구축 한 내 결과입니다.
time = 142; result = 987459712
time = 1590; result = 987459712
time = 1792; result = 987459712
여전히 약간의 차이가 있지만 뚜렷한 것은 아닙니다. 반복자 블록 구현은 맞춤형 솔루션만큼 효율적이지는 않지만 꽤 좋습니다.
이것이 릴리스 빌드 실행이라고 가정하면 JIT가 평평하게 작동하지 않기 때문에 모든 비교가 꺼져 있습니다.
당신은 어셈블리를 볼 수 있습니다 반사기 그리고 '수율'진술이 무엇인지 확인하십시오. 컴파일러는 반복자를 캡슐화하기위한 클래스를 만들 것입니다. 생성 된 코드에서 열거 가능한 구현보다 더 많은 하우스 키핑이 진행되고있을 수 있습니다.
반사기 출력의 약간의 차이 (여기서는 인수 점검 및 추가 수준의 내재화 수준이 여기서는 관련이 없습니다). 필수 코드는 다음과 같습니다.
public static IEnumerable<int> Range(int start, int count) {
for(int current = 0; current < count; ++current) {
yield return start + current;
}
}
즉, 다른 로컬 변수 대신 모든 수익률에 추가를 추가합니다.
나는 이것을 벤치마킹하려고 노력했지만 이해할 수있는 결과를 얻기 위해 충분한 외부 프로세스를 멈출 수는 없습니다. 또한 JIT 컴파일러의 효과를 무시하기 위해 각 테스트를 두 번 시도했지만 '흥미로운'결과조차도 있습니다.
내 결과 샘플은 다음과 같습니다.
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