Frage

Ich kam in einer Situation, wo ich eine ziemlich große Datei, die ich von binären Daten lesen muß.

Folglich wurde mir klar, dass die Standard-Implementierung in Binary .NET ziemlich langsam ist. Nach dem es bei der Suche mit .NET Reflector stieß ich auf diese:

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));
}

Was mich als äußerst ineffizient hält sofort an zu denken, wie Computer entworfen wurden, mit 32-Bit-Werten zu arbeiten, da die 32-Bit-CPU erfunden wurden.

Also habe ich meine eigene (unsicher) FastBinaryReader Klasse mit dem Code gemacht wie diese statt:

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));
        }
    }
...
}

Welche ist viel schneller -. Ich schaffte es abrasieren 5-7 Sekunden hinter der Zeit dauerte es eine 500 MB-Datei zu lesen, aber es ist immer noch ziemlich langsam insgesamt (29 Sekunden am Anfang und ~ 22 Sekunden jetzt mit meinem FastBinaryReader)

Es ist noch Art von verwirrt mich, warum es dauert noch so lange eine solche relativ kleinen Datei zu lesen. Wenn ich die Datei von einem Datenträger auf einem anderen dauert es nur ein paar Sekunden zu kopieren, so Plattendurchsatz ist kein Problem.

I inlined weiter die ReadInt32 usw. Anrufe, und ich endete mit diesem Code auf:

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;
   }

Jede weitere Ideen, wie dies noch schneller zu machen? Ich dachte, vielleicht habe ich Rangier verwenden könnte die gesamte Datei direkt in den Speicher oben auf einige benutzerdefinierte Struktur abzubilden, da die Daten linear, feste Größe und sequentiell ist.

GELöST: Ich kam zu dem Schluss, dass die Filestream Pufferung / BufferedStream fehlerhaft ist. Bitte beachten Sie die akzeptierte Antwort und meine eigene Antwort (mit Lösung) unter.

War es hilfreich?

Lösung

Wenn Sie eine Filecopy tun, große Datenmengen gelesen und auf die Platte geschrieben.

Sie lesen die gesamte Datei vier Bytes auf einmal. Dies ist verpflichtet, langsamer sein. Selbst wenn der Strom Implementierung ist intelligent genug, zu puffern, haben Sie immer noch mindestens 500 MB / 4 = 131072000 API-Aufrufe.

Ist es nicht ratsam, nur einen großen Teil der Daten zu lesen, und dann durchlaufen sie nacheinander, und wiederholen, bis die Datei verarbeitet wurde?

Andere Tipps

Ich lief in ein ähnliches Performance-Problem mit Binary / Filestream und nach Profilierung, entdecken ich, dass das Problem nicht mit FileStream Pufferung ist, sondern mit dieser Zeile:

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

Im Einzelnen macht die Eigenschaft br.BaseStream.Length auf einem FileStream einen (relativ) langsam Systemaufruf die Dateigröße auf jeder Schleife zu erhalten. Nach der Änderung des Codes folgt aus:

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

und eine entsprechende Puffergröße für den FileStream verwenden, ich eine ähnliche Leistung auf das MemoryStream Beispiel erreicht.

Interessant, die gesamte Datei in einen Puffer zu lesen und durch sie im Speicher gehen machte einen großen Unterschied. Dies ist auf Kosten der Erinnerung, aber wir haben genug.

Das macht mich denken, dass die Filestream (oder BufferedStream der für diese Angelegenheit) Puffer Implementierung ist fehlerhaft, denn egal, welche Größe Puffer ich versucht, die Leistung noch gesaugt.

  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;
          }
      }
  }

Down to 2-5 Sekunden (abhängig von der Disk-Cache vermute ich) jetzt von 22. Welche jetzt gut genug ist.

Ein Nachteil; Vielleicht möchten Sie Ihre CPUs endianness rel="nofollow ... vorausgesetzt, little-endian ist nicht ganz sicher (man denke: itanium usw.).

Sie können auch sehen wollen, wenn BufferedStream einen Unterschied macht (ich bin nicht sicher, es wird).

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top