2009-09-10 5 views
1

J'ai créé une classe de gestionnaire de tampons simple à utiliser avec les sockets asynchrones. Cela protégera contre la fragmentation de la mémoire et améliorera les performances. Des suggestions pour d'autres améliorations ou d'autres approches?Suggestions pour un gestionnaire de tampons non bloquants sûrs pour les threads

public class BufferManager 
{ 
    private int[] free; 
    private byte[] buffer; 
    private readonly int blocksize; 

    public BufferManager(int count, int blocksize) 
    { 
     buffer = new byte[count * blocksize]; 
     free = new int[count]; 
     this.blocksize = blocksize; 

     for (int i = 0; i < count; i++) 
      free[i] = 1; 
    } 

    public void SetBuffer(SocketAsyncEventArgs args) 
    { 
     for (int i = 0; i < free.Length; i++) 
     { 
      if (1 == Interlocked.CompareExchange(ref free[i], 0, 1)) 
      { 
       args.SetBuffer(buffer, i * blocksize, blocksize); 
       return; 
      } 
     } 
     args.SetBuffer(new byte[blocksize], 0, blocksize); 
    } 

    public void FreeBuffer(SocketAsyncEventArgs args) 
    { 
     int offset = args.Offset; 
     byte[] buff = args.Buffer; 

     args.SetBuffer(null, 0, 0); 

     if (buffer == buff) 
      free[offset/blocksize] = 1; 
    } 
} 

Répondre

0

Modifier:

La réponse ci-dessous Orignal aborde un problème de construction de code de couplage trop serré. Cependant, en considérant la solution dans son ensemble, j'éviterais d'utiliser un seul grand tampon et de lui en remettre des tranches de cette manière. Vous exposez votre code au dépassement de la mémoire tampon (et devons-nous l'appeler des problèmes "Underrun" du tampon). Au lieu de cela, je voudrais gérer un tableau de tableaux d'octets, chacun étant un tampon discret. Offset remis est toujours 0 et la taille est toujours la longueur du tampon. Tout mauvais code qui tente de lire/écrire des parties au-delà des limites sera intercepté.

réponse originale

Vous avez Couplé la classe à SocketAsyncEventArgs où, en fait, tout ce qu'il a besoin est une fonction d'assigner la mémoire tampon, changer SetBuffer à: -

public void SetBuffer(Action<byte[], int, int> fnSet) 
{ 
    for (int i = 0; i < free.Length; i++) 
    { 
     if (1 == Interlocked.CompareExchange(ref free[i], 0, 1)) 
     { 
      fnSet(buffer, i * blocksize, blocksize); 
      return; 
     } 
    } 
    fnSet(new byte[blocksize], 0, blocksize); 
} 

Maintenant, vous pouvez appeler de consommer du code quelque chose comme ceci: -

myMgr.SetBuffer((buf, offset, size) => myArgs.SetBuffer(buf, offset, size)); 

Je ne suis pas sûr que l'inférence de type est assez intelligent pour résoudre e les types de buf, offset, size dans ce cas. Sinon, vous devrez placer les types dans la liste des arguments: -

myMgr.SetBuffer((byte[] buf, int offset, int size) => myArgs.SetBuffer(buf, offset, size)); 

Mais maintenant, votre classe peut être utilisé pour allouer un tampon pour toutes sortes d'exigences qui utilisent également l'octet [], int, motif int ce qui est très commun.

Bien sûr, vous devez découpler l'opération libre mais c'est: -

public void FreeBuffer(byte[] buff, int offset) 
{ 
    if (buffer == buff) 
     free[offset/blocksize] = 1; 
} 

Cela vous oblige à appeler SetBuffer sur les EventArgs dans la consommation du code dans le cas SocketAsyncEventArgs. Si vous craignez que cette approche réduise l'atomicité de libérer le tampon et le supprime des sockets, alors sous-classez ce gestionnaire de tampon ajusté et incluez le code spécifique SocketAsyncEventArgs dans la sous-classe.

+0

Excellentes suggestions! D'abord, j'ai décidé d'utiliser un tampon large pour protéger contre la fragmentation de la mémoire. Mais peut-être que cela n'a pas d'importance quand tous les octets seront attribués en même temps. – remdao

+0

La fragmentation ne devient vraiment un problème qu'avec des tampons supérieurs à environ 80 Ko puisque seules les allocations de 80 Ko ou plus sont prises dans le tas d'objets Large qui ne reçoit pas de compactage. Toute chose provient moins du tas normal que le GC compactera après la collecte, éliminant ainsi toute fragmentation. – AnthonyWJones

+0

J'ai réalisé qu'il est probablement préférable d'implémenter le gestionnaire de tampons sous la forme d'une pile de tableaux d'octets.Parce que s'il n'a pas à suivre les offsets, il n'y a pas de raison d'avoir une boucle. Il sera plus rapide de pousser et de faire éclater les objets du tableau d'octets. – remdao

0

J'ai créé une nouvelle classe avec une approche complètement différente.

J'ai une classe de serveur qui reçoit des tableaux d'octets. Il appellera alors différents délégués en leur remettant les objets tampons afin que d'autres classes puissent les traiter. Lorsque ces classes sont terminées, ils ont besoin d'un moyen de repousser les tampons dans la pile.

public class SafeBuffer 
{ 
    private static Stack bufferStack; 
    private static byte[][] buffers; 

    private byte[] buffer; 
    private int offset, lenght; 

    private SafeBuffer(byte[] buffer) 
    { 
     this.buffer = buffer; 
     offset = 0; 
     lenght = buffer.Length; 
    } 

    public static void Init(int count, int blocksize) 
    { 
     bufferStack = Stack.Synchronized(new Stack()); 
     buffers = new byte[count][]; 

     for (int i = 0; i < buffers.Length; i++) 
      buffers[i] = new byte[blocksize]; 

     for (int i = 0; i < buffers.Length; i++) 
      bufferStack.Push(new SafeBuffer(buffers[i])); 
    } 

    public static SafeBuffer Get() 
    { 
     return (SafeBuffer)bufferStack.Pop(); 
    } 

    public void Close() 
    { 
     bufferStack.Push(this); 
    } 

    public byte[] Buffer 
    { 
     get 
     { 
      return buffer; 
     } 
    } 

    public int Offset 
    { 
     get 
     { 
      return offset; 
     } 
     set 
     { 
      offset = value; 
     } 
    } 

    public int Lenght 
    { 
     get 
     { 
      return buffer.Length; 
     } 
    } 
} 
Questions connexes