시스템/mscorlib 코드가 훨씬 더 빠른 이유는 무엇입니까? 특히 루프의 경우?

StackOverflow https://stackoverflow.com/questions/891835

문제

이것은 제가 파고 들었던 개인 프로젝트 일뿐입니다. 기본적으로 StreamReader를 사용하여 텍스트 파일 (예 : 20MB에서 약 1GB까지)을 구문 분석합니다. 성능은 꽤 견고하지만 여전히 ... 이진으로 구문 분석하면 어떻게 될지 알기 위해 가려움증을 겪고 있습니다. 오해하지 말고 조기 최적화가 아닙니다. 나는 의도적으로 "보는 것"에 대해 의도적으로 미세한 최적화입니다.

그래서 바이트 배열을 사용하여 텍스트 파일을 읽고 있습니다. 새로운 라인은 (Windows) 표준 CR/LF 또는 CR 또는 LF가 될 수 있습니다. CR에서 Array.Indexof를 사용할 수 있기를 바랐고 LF를 건너 뛰었습니다. 대신 나는 INDEXOF와 매우 유사한 코드를 작성하지만 필요에 따라 배열을 반환하고 있습니다.

따라서 Crux : indexof와 매우 유사한 코드를 사용하여 내 코드는 여전히 느리게 진행됩니다. 800MB 파일을 사용하여 원근법으로 넣으려면 :

  • indexof 사용 및 CR을 찾고 : ~ 320MB/s
  • StreamReader 및 Readline 사용 : ~ 180MB/s
  • 루프 복제 인덱스의 경우 : ~ 150MB/s

다음은 for 루프 (~ 150MB/s)가있는 코드입니다.

IEnumerator<byte[]> IEnumerable<byte[]>.GetEnumerator() {
    using(FileStream fs = new FileStream(_path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, _bufferSize)) {
        byte[] buffer = new byte[_bufferSize];
        int bytesRead;
        int overflowCount = 0;
        while((bytesRead = fs.Read(buffer, overflowCount, buffer.Length - overflowCount)) > 0) {
            int bufferLength = bytesRead + overflowCount;
            int lastPos = 0;
            for(int i = 0; i < bufferLength; i++) {
                if(buffer[i] == 13 || buffer[i] == 10) {
                    int length = i - lastPos;
                    if(length > 0) {
                        byte[] line = new byte[length];
                        Array.Copy(buffer, lastPos, line, 0, length);
                        yield return line;
                    }
                    lastPos = i + 1;
                }
            }
            if(lastPos > 0) {
                overflowCount = bufferLength - lastPos;
                Array.Copy(buffer, lastPos, buffer, 0, overflowCount);
            }
        }
    }
}

더 빠른 코드 블록 (~ 320MB/s)입니다.

while((bytesRead = fs.Read(buffer, overflowCount, buffer.Length - overflowCount)) > 0) {
    int bufferLength = bytesRead + overflowCount;
    int pos = 0;
    int lastPos = 0;
    while(pos < bufferLength && (pos = Array.IndexOf<byte>(buffer, 13, pos)) != -1) {
        int length = pos - lastPos;
        if(length > 0) {
            byte[] line = new byte[length];
            Array.Copy(buffer, lastPos, line, 0, length);
            yield return line;
        }
        if(pos < bufferLength - 1 && buffer[pos + 1] == 10)
            pos++;
        lastPos = ++pos;

    }
    if(lastPos > 0) {
        overflowCount = bufferLength - lastPos;
        Array.Copy(buffer, lastPos, buffer, 0, overflowCount);
    }
}

(아니요, 생산 준비가되어 있지 않습니다. 어떤 경우에는 날려 버릴 것입니다. 128kb 크기 버퍼를 사용하여 대부분의 사람들을 무시합니다.)

그래서 내 큰 질문은 ... 왜 Array.indexof가 훨씬 더 빠르게 작동합니까? 배열을 걷는 루프 용은 본질적으로 동일합니다. mscorlib 코드가 실행되는 방식에 관한 것이 있습니까? 위의 코드를 실제로 색인을 복제하고 CR을 찾은 다음 Indexof를 사용하는 것이 도움이되지 않는 것처럼 LF를 건너 뛰도록 변경하는 것도 도움이되지 않습니다. Errr ... 나는 다양한 순열을 겪어 왔고 아마도 내가 놓친 눈부신 버그가있을 것입니까?

BTW, 나는 Readline을 살펴보고 IF 블록 대신 스위치 블록을 사용하는 것을 알았습니다. 그것은 또 다른 시간에 대한 또 다른 질문입니다 (왜 스위치가 더 빠른가?) 그러나 나는 그것을 보았다고 지적 할 것이라고 생각했습니다.

또한 VS 이외의 릴리스 빌드를 테스트하고 있으므로 디버거링이 진행되지 않습니다.

도움이 되었습니까?

해결책

그건 좋은 질문이야. 짧은 버전은 모든 것이 Indexof가 사용할 iqueality comparer의 구현으로 요약된다는 것입니다. 다음 코드를 보자.

using System;
using System.Collections.Generic;
using System.Diagnostics;

class Program {

    static int [] buffer = new int [1024];
    const byte mark = 42;
    const int iterations = 10000;

    static void Main ()
    {
        buffer [buffer.Length -1] = mark;

        Console.WriteLine (EqualityComparer<int>.Default.GetType ());

        Console.WriteLine ("Custom:  {0}", Time (CustomIndexOf));
        Console.WriteLine ("Builtin: {0}", Time (ArrayIndexOf));
    }

    static TimeSpan Time (Action action)
    {
        var watch = new Stopwatch ();
        watch.Start ();
        for (int i = 0; i < iterations; i++)
            action ();
        watch.Stop ();
        return watch.Elapsed;
    }

    static void CustomIndexOf ()
    {
        for (int i = 0; i < buffer.Length; i++)
            if (buffer [i] == mark)
                break;
    }

    static void ArrayIndexOf ()
    {
        Array.IndexOf (buffer, mark);
    }
}

컴파일해야합니다 CSC /최적화+.

다음은 다음과 같습니다.

C:\Tmp>test
System.Collections.Generic.GenericEqualityComparer`1[System.Int32]
Custom:  00:00:00.0386403
Builtin: 00:00:00.0427903

이제 배열의 유형과 평등 구성 자의 유형을 바이트로 변경하십시오. 결과는 다음과 같습니다.

C:\Tmp>test
System.Collections.Generic.ByteEqualityComparer
Custom:  00:00:00.0387158
Builtin: 00:00:00.0165881

보시다시피, 바이트 어레이는 특수 케이스이며 바이트 어레이에서 바이트를 찾도록 최적화 될 수 있습니다. .NET 프레임 워크를 디 컴파일 할 수 없으므로 여기서 분석을 중단했지만 꽤 좋은 단서라고 생각합니다.

다른 팁

mscorlib 파일은 설치 중에 ngen'd입니다. ngen.exe 유틸리티를 사용하여 파일을 사용해보십시오 (.net framwork와 함께 제공) ... 그런 다음 벤치 마크를 확인하십시오. 약간 더 빠를 수 있습니다.

Microsoft는 .net 코드를 거의 기본 속도로 실행하기 위해 앱 설치 중에 코드를 "NGEN"할 것을 권장합니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top