문제

바이너리 데이터를 읽어야 하는 매우 큰 파일이 있는 상황을 발견했습니다.

결과적으로 .NET의 기본 BinaryReader 구현이 꽤 느리다는 것을 깨달았습니다.그걸로 보면 .NET 리플렉터 나는 이것을 발견했습니다 :

public virtual int ReadInt32()
{
    if (this.m_isMemoryStream)
    {
        MemoryStream stream = this.m_stream as MemoryStream;
        return stream.InternalReadInt32();
    }
    this.FillBuffer(4);
    return (((this.m_buffer[0] | (this.m_buffer[1] << 8)) | (this.m_buffer[2] << 0x10)) | (this.m_buffer[3] << 0x18));
}

32비트 CPU가 발명된 이후 컴퓨터가 32비트 값으로 작동하도록 설계되었다는 점을 생각하면 매우 비효율적이라고 생각됩니다.

그래서 대신 다음과 같은 코드를 사용하여 (안전하지 않은) FastBinaryReader 클래스를 만들었습니다.

public unsafe class FastBinaryReader :IDisposable
{
    private static byte[] buffer = new byte[50];
    //private Stream baseStream;

    public Stream BaseStream { get; private set; }
    public FastBinaryReader(Stream input)
    {
        BaseStream = input;
    }


    public int ReadInt32()
    {
        BaseStream.Read(buffer, 0, 4);

        fixed (byte* numRef = &(buffer[0]))
        {
            return *(((int*)numRef));
        }
    }
...
}

훨씬 더 빠릅니다. 500MB 파일을 읽는 데 걸리는 시간을 5~7초 단축할 수 있었지만 전체적으로 여전히 꽤 느립니다(처음에는 29초, 지금은 22초 정도). FastBinaryReader).

상대적으로 작은 파일을 읽는 데 왜 그렇게 오랜 시간이 걸리는지 여전히 당황스럽습니다.한 디스크에서 다른 디스크로 파일을 복사하는 데는 몇 초 밖에 걸리지 않으므로 디스크 처리량은 문제가 되지 않습니다.

ReadInt32 등을 추가로 인라인했습니다.전화를 걸었고 결국 다음 코드를 얻었습니다.

using (var br = new FastBinaryReader(new FileStream(cacheFilePath, FileMode.Open, FileAccess.Read, FileShare.Read, 0x10000, FileOptions.SequentialScan)))

  while (br.BaseStream.Position < br.BaseStream.Length)
  {
      var doc = DocumentData.Deserialize(br);
      docData[doc.InternalId] = doc;
  }
}

   public static DocumentData Deserialize(FastBinaryReader reader)
   {
       byte[] buffer = new byte[4 + 4 + 8 + 4 + 4 + 1 + 4];
       reader.BaseStream.Read(buffer, 0, buffer.Length);

       DocumentData data = new DocumentData();
       fixed (byte* numRef = &(buffer[0]))
       {
           data.InternalId = *((int*)&(numRef[0]));
           data.b = *((int*)&(numRef[4]));
           data.c = *((long*)&(numRef[8]));
           data.d = *((float*)&(numRef[16]));
           data.e = *((float*)&(numRef[20]));
           data.f = numRef[24];
           data.g = *((int*)&(numRef[25]));
       }
       return data;
   }

이를 더욱 빠르게 만드는 방법에 대한 추가 아이디어가 있습니까?데이터가 선형적이고 고정된 크기이며 순차적이기 때문에 마샬링을 사용하여 일부 사용자 정의 구조 위에 전체 파일을 메모리에 직접 매핑할 수 있을 것이라고 생각했습니다.

해결됨: FileStream의 버퍼링/BufferedStream에 결함이 있다는 결론에 도달했습니다.아래에서 허용되는 답변과 내 답변(솔루션 포함)을 참조하세요.

도움이 되었습니까?

해결책

파일 복사를 수행하면 대량의 데이터가 디스크에 읽혀지고 기록됩니다.

전체 파일을 한 번에 4바이트씩 읽고 있습니다.이것은 속도가 느려질 수밖에 없습니다.스트림 구현이 버퍼링할 만큼 똑똑하더라도 여전히 최소 500MB/4 = 131072000개의 API 호출이 있습니다.

그냥 큰 덩어리의 데이터를 읽은 다음 순차적으로 살펴보고 파일이 처리될 때까지 반복하는 것이 더 현명하지 않습니까?

다른 팁

BinaryReader/FileStream에서 비슷한 성능 문제가 발생했으며 프로파일링 후에 문제가 FileStream 버퍼링 대신 다음 줄을 사용하세요.

while (br.BaseStream.Position < br.BaseStream.Length) {

구체적으로, 부동산 br.BaseStream.LengthFileStream 각 루프에서 파일 크기를 가져오기 위해 (상대적으로) 느린 시스템 호출을 수행합니다.코드를 다음과 같이 변경한 후:

long length = br.BaseStream.Length;
while (br.BaseStream.Position < length) {

그리고 적절한 버퍼 크기를 사용하여 FileStream, 나는 비슷한 성과를 거두었습니다. MemoryStream 예.

흥미롭게도 전체 파일을 버퍼로 읽고 메모리에서 처리하는 것이 큰 차이를 만들었습니다.이것은 메모리를 희생하지만 우리에게는 충분합니다.

이로 인해 FileStream(또는 해당 문제에 대한 BufferedStream) 버퍼 구현에 결함이 있다고 생각됩니다. 왜냐하면 어떤 크기의 버퍼를 시도했든 성능이 여전히 좋지 않기 때문입니다.

  using (var br = new FileStream(cacheFilePath, FileMode.Open, FileAccess.Read, FileShare.Read, 0x10000, FileOptions.SequentialScan))
  {
      byte[] buffer = new byte[br.Length];
      br.Read(buffer, 0, buffer.Length);
      using (var memoryStream = new MemoryStream(buffer))
      {
          while (memoryStream.Position < memoryStream.Length)
          {
              var doc = DocumentData.Deserialize(memoryStream);
              docData[doc.InternalId] = doc;
          }
      }
  }

이제 22초에서 2~5초로 단축되었습니다(디스크 캐시에 따라 다름).지금은 충분합니다.

한 가지 주의사항;다시 한 번 확인해 보세요. CPU의 엔디안...리틀 엔디안이 아니라고 가정 상당히 안전하다(생각해 보세요:아이테니엄 등).

당신은 또한 BufferedStream 차이가 있습니다(그렇게 될지 모르겠습니다).

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