문제

아래 코드는 동일한 솔루션을 수행하는 세 가지 방법의 성능을 확인하는 것입니다.

    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
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top