2011-10-31 3 views
19

Dans certaines situations, la classe MemoryMappedViewAccessor ne le coupe pas pour la lecture efficace des octets; le mieux que nous obtenons est le ReadArray<byte> générique qui est la route pour toutes les structures et implique plusieurs étapes inutiles lorsque vous avez juste besoin d'octets.Comment puis-je lire rapidement des octets à partir d'un fichier mappé en mémoire dans .NET?

Il est possible d'utiliser un MemoryMappedViewStream, mais comme il est basé sur un Stream, vous devez d'abord rechercher la position correcte, puis l'opération de lecture elle-même comporte de nombreuses étapes inutiles. Existe-t-il un moyen rapide et performant de lire un tableau d'octets à partir d'un fichier mappé en mémoire dans .NET, étant donné qu'il ne doit s'agir que d'une zone particulière de l'espace adresse à lire?

Répondre

27

Cette solution nécessite un code non sécurisé (compiler avec le commutateur /unsafe), mais saisit directement un pointeur sur la mémoire; alors Marshal.Copy peut être utilisé. C'est beaucoup, beaucoup plus rapide que les méthodes fournies par le framework .NET.

// assumes part of a class where _view is a MemoryMappedViewAccessor object 

    public unsafe byte[] ReadBytes(int offset, int num) 
    { 
     byte[] arr = new byte[num]; 
     byte *ptr = (byte*)0; 
     this._view.SafeMemoryMappedViewHandle.AcquirePointer(ref ptr); 
     Marshal.Copy(IntPtr.Add(new IntPtr(ptr), offset), arr, 0, num); 
     this._view.SafeMemoryMappedViewHandle.ReleasePointer(); 
     return arr; 
    } 

    public unsafe void WriteBytes(int offset, byte[] data) 
    { 
     byte* ptr = (byte*)0; 
     this._view.SafeMemoryMappedViewHandle.AcquirePointer(ref ptr); 
     Marshal.Copy(data, 0, IntPtr.Add(new IntPtr(ptr), offset), data.Length); 
     this._view.SafeMemoryMappedViewHandle.ReleasePointer(); 
    } 
+3

Vous devriez utiliser un bloc d'exécution critique et un essai-final pour s'assurer que ReleasePointer s'exécute même si le Marshal.Copy lève une exception. –

+3

Bonne réponse =) En effet, le profilage montre que l'encapsuleur géré est 30 fois plus lent que l'utilisation d'un pointeur non sécurisé pour accéder à la mémoire mappée. –

+2

@MattHowells Je suis d'accord. J'ai lu que CER peut affecter la performance, mais il semble négligeable (dans un test contrôlé au moins). Indépendamment des implications de performance, c'est le modèle d'utilisation correct tel que décrit sous "remarques" ici; https://msdn.microsoft.com/en-us/library/system.runtime.interopservices.safebuffer.acquirepointer(v=vs.110).aspx – LaFleur

2

Voir ce rapport de bogue: No way to determine internal offset used by MemoryMappedViewAccessor - Makes SafeMemoryMappedViewHandle property unusable.

du rapport:

MemoryMappedViewAccessor possède une propriété SafeMemoryMappedViewHandle, qui renvoie le ViewHandle utilisé en interne par le MemoryMappedView, mais n'a pas de propriété pour renvoyer le décalage utilisé par le MemoryMappedView. Comme MemoryMappedView est la page alignant le décalage demandé dans MemoryMappedFile.CreateViewAccessor (décalage, taille) il est impossible d'utiliser le SafeMemoryMappedViewHandle pour tout utile sans connaître le décalage.

Notez que ce que nous voulons vraiment faire est d'utiliser la méthode AcquirePointer (ref byte * pointer) pour permettre l'exécution de code basé sur un pointeur rapide (éventuellement non géré). Nous sommes OK avec le pointeur aligné sur la page, mais il doit être possible de savoir quel est le décalage de l'adresse demandée initialement.

+0

Il semble stupide .. si vous êtes en contrôle de la vue, vous n'avez pas besoin de .NET pour vous indiquer le décalage, puisque vous l'avez spécifié. (C'est ce que je fais: '_view' est un accesseur à l'offset 0) –

+0

Fwiw, ce code a également été soumis à des tests de résistance à la mort [des milliards d'appels, des milliers de MMF différents] sur plusieurs machines –

+0

et des centaines de milliers de MMF. Ce bug ne se produit pas avec mon code;) –

1

Une version sûre de cette solution est:

var file = MemoryMappedFile.CreateFromFile(...); 
var accessor = file.CreateViewAccessor(); 
var bytes = new byte[yourLength]; 

// assuming the string is at the start of the file 
// aka position: 0 
// https://msdn.microsoft.com/en-us/library/dd267761(v=vs.110).aspx 
accessor.ReadArray<byte>(
    position: 0,  // The number of bytes in the accessor at which to begin reading 
    array: bytes,  // The array to contain the structures read from the accessor 
    offset: 0,  // The index in `array` in which to place the first copied structure 
    count: yourLength // The number of structures of type T to read from the accessor. 
); 

var myString = Encoding.UTF8.GetString(bytes); 

J'ai testé, il fonctionne. Je ne peux pas commenter sur ses performances ou si c'est la meilleure solution globale juste que cela fonctionne.

+1

Cool, oui évite définitivement l'utilisation de pointeurs :) 'ReadArray ' est beaucoup, beaucoup plus lent, cependant –

0

Je sais que c'est une question plus ancienne qui a été répondue mais je voulais ajouter mes deux cents.

J'ai exécuté un test avec la réponse acceptée (en utilisant le code non sécurisé) et avec l'approche MemoryMappedViewStream pour lire un tableau de 200 Mo d'octets.

MemoryMappedViewStream

 const int MMF_MAX_SIZE = 209_715_200; 
     var buffer = new byte[ MMF_VIEW_SIZE ]; 

     using(var mmf = MemoryMappedFile.OpenExisting("mmf1")) 
     using(var view = mmf.CreateViewStream(0, buffer.Length, MemoryMappedFileAccess.ReadWrite)) 
     { 
      if(view.CanRead) 
      { 
       Console.WriteLine("Begin read"); 
       sw.Start(); 
       view.Read(buffer, 0, MMF_MAX_SIZE); 
       sw.Stop(); 
       Console.WriteLine($"Read done - {sw.ElapsedMilliseconds}ms"); 
      } 
     } 

j'ai couru le test 3 fois avec chaque approche et reçu les heures suivantes.

MemoryMappedViewStream:

  1. 483ms
  2. 501ms
  3. 490ms

méthode non sécurisée

  1. 531ms
  2. 517ms
  3. 523ms

De la petite quantité de tests il semble que le MemoryMappedViewStream a une très léger avantage. Dans cet esprit pour quiconque lisant ce post sur la route, je voudrais aller avec le MemoryMappedViewStream.

Questions connexes