Question

Je suis tombé sur une situation où j'ai un fichier assez grand que je dois lire des données binaires à partir.

Par conséquent, je me suis aperçu que l'implémentation par défaut BinaryReader dans .NET est assez lent. En regardant avec .NET réflecteur je suis tombé sur ceci:

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

Ce qui me paraît extrêmement inefficace, en pensant à la façon dont les ordinateurs ont été conçus pour fonctionner avec des valeurs 32 bits depuis la CPU a été inventé 32 bits.

Alors j'ai fait ma propre classe (dangereux) de FastBinaryReader avec le code tel que ceci:

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

Ce qui est beaucoup plus rapide -. J'ai réussi à raser 5-7 secondes du temps qu'il a fallu pour lire un fichier de 500 Mo, mais il est encore assez lent ensemble (29 secondes au départ et ~ 22 secondes maintenant avec mon FastBinaryReader)

encore un peu me déconcerte la raison pour laquelle il faut encore si longtemps pour lire un tel fichier relativement faible. Si je copie le fichier d'un disque à l'autre, il ne prend que quelques secondes, le débit du disque n'est pas un problème.

De plus, j'inline le ReadInt32, etc. appels, et j'ai fini avec ce code:

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

Les autres idées sur la façon de rendre cela encore plus vite? Je pensais que je pourrais utiliser pour cartographier marshalling tout le fichier directement dans la mémoire au-dessus de une structure personnalisée, puisque les données est linéaire, la taille fixe et séquentielle.

RESOLU: Je suis venu à la conclusion que la mise en mémoire tampon / BufferedStream de FileStream sont défectueux. S'il vous plaît voir la réponse acceptée et ma propre réponse (avec la solution) ci-dessous.

Était-ce utile?

La solution

Quand vous faites une copie de fichier, de gros morceaux de données sont lues et écrites sur le disque.

Vous lisez le fichier entier quatre octets à la fois. Ceci est lié à être plus lent. Même si la mise en œuvre de flux est assez intelligent pour tampon, vous avez encore au moins 500 Mo / 4 = 131072000 appels API.

est-il pas plus sage de lire un gros morceau de données, puis passer par séquentiellement, et répéter jusqu'à ce que le fichier a été traité?

Autres conseils

Je suis tombé sur un problème de performance similaire avec BinaryReader / FileStream, et après le profilage, j'ai découvert que le problème n'est pas mise en mémoire tampon FileStream, mais avec cette ligne:

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

Plus précisément, le br.BaseStream.Length de propriété sur un FileStream fait un appel système lent (relativement) pour obtenir la taille du fichier sur chaque boucle. Après avoir modifié le code à ceci:

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

et en utilisant une taille de tampon appropriée pour la FileStream, j'ai atteint une performance similaire à l'exemple MemoryStream.

Intéressant, lire le fichier entier dans un tampon et en passant par la mémoire en fait une énorme différence. Ceci est au prix de la mémoire, mais nous avons beaucoup.

Cela me fait penser que la mise en œuvre du tampon de FileStream (ou BufferedStream est pour cette question) est erronée, parce que peu importe ce que la taille du tampon j'ai essayé, des performances toujours aspiré.

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

En bas à 2-5 secondes (en fonction du cache disque je devine) maintenant de 22. Ce qui est assez bon pour l'instant.

Une mise en garde; vous pouvez vérifier vos ... en supposant peu endian est pas tout à fait sécurité (pensez: itanium etc).

Vous pouvez également voir si BufferedStream fait une différence (je ne suis pas sûr que ce sera).

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top