4

J'ai effectué quelques opérations bitmap non sécurisées et j'ai découvert que le fait d'augmenter le temps passé en moins de temps peut conduire à de grandes améliorations de performances. Je ne suis pas sûr pourquoi est-ce le cas, même si vous faites beaucoup plus d'opérations au niveau du bit dans la boucle, il est toujours préférable de faire moins d'itérations sur le pointeur. Ainsi, par exemple, au lieu d'itérer sur des pixels de 32 bits avec un UInt32, itérez sur deux pixels avec UInt64 et faites deux fois les opérations dans un cycle. Ce qui suit le fait en lisant deux pixels et en les modifiant (bien sûr, il échouera avec des images avec une largeur impaire, mais c'est juste pour tester).itération de pointeur non sécurisée et bitmap - pourquoi UInt64 est-il plus rapide?

private void removeBlueWithTwoPixelIteration() 
    { 
     // think of a big image with data 
     Bitmap bmp = new Bitmap(15000, 15000, System.Drawing.Imaging.PixelFormat.Format32bppArgb); 
     TimeSpan startTime, endTime; 

     unsafe { 

      UInt64 doublePixel; 
      UInt32 pixel1; 
      UInt32 pixel2; 

      const int readSize = sizeof(UInt64); 
      const UInt64 rightHalf = UInt32.MaxValue; 

      PerformanceCounter pf = new PerformanceCounter("System", "System Up Time"); pf.NextValue(); 

      BitmapData bd = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), System.Drawing.Imaging.ImageLockMode.ReadWrite, bmp.PixelFormat); 
      byte* image = (byte*)bd.Scan0.ToPointer(); 

      startTime = TimeSpan.FromSeconds(pf.NextValue()); 

      for (byte* line = image; line < image + bd.Stride * bd.Height; line += bd.Stride) 
      { 
       for (var pointer = line; pointer < line + bd.Stride; pointer += readSize) 
       { 
        doublePixel = *((UInt64*)pointer); 
        pixel1 = (UInt32)(doublePixel >> (readSize * 8/2)) >> 8; // loose last 8 bits (Blue color) 
        pixel2 = (UInt32)(doublePixel & rightHalf) >> 8; // loose last 8 bits (Blue color) 
        *((UInt32*)pointer) = pixel1 << 8; // putback but shift so A R G get back to original positions 
        *((UInt32*)pointer + 1) = pixel2 << 8; // putback but shift so A R G get back to original positions 
       } 
      } 

      endTime = TimeSpan.FromSeconds(pf.NextValue()); 

      bmp.UnlockBits(bd); 
      bmp.Dispose(); 

     } 

     MessageBox.Show((endTime - startTime).TotalMilliseconds.ToString()); 

    } 

Le code suivant qu'il fait pixel par pixel et est environ 70% plus lent que la précédente:

private void removeBlueWithSinglePixelIteration() 
    { 
     // think of a big image with data 
     Bitmap bmp = new Bitmap(15000, 15000, System.Drawing.Imaging.PixelFormat.Format32bppArgb); 
     TimeSpan startTime, endTime; 

     unsafe 
     { 

      UInt32 singlePixel; 

      const int readSize = sizeof(UInt32); 

      PerformanceCounter pf = new PerformanceCounter("System", "System Up Time"); pf.NextValue(); 

      BitmapData bd = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), System.Drawing.Imaging.ImageLockMode.ReadWrite, bmp.PixelFormat); 
      byte* image = (byte*)bd.Scan0.ToPointer(); 

      startTime = TimeSpan.FromSeconds(pf.NextValue()); 

      for (byte* line = image; line < image + bd.Stride * bd.Height; line += bd.Stride) 
      { 
       for (var pointer = line; pointer < line + bd.Stride; pointer += readSize) 
       { 
        singlePixel = *((UInt32*)pointer) >> 8; // loose B 
        *((UInt32*)pointer) = singlePixel << 8; // adjust A R G back 
       } 
      } 

      endTime = TimeSpan.FromSeconds(pf.NextValue()); 

      bmp.UnlockBits(bd); 
      bmp.Dispose(); 

     } 

     MessageBox.Show((endTime - startTime).TotalMilliseconds.ToString()); 
    } 

Quelqu'un pourrait-il expliquer pourquoi incrémente le pointeur d'une opération plus coûteuse que de faire quelques opérations au niveau du bit? J'utilise framework .NET 4.

Est-ce que quelque chose comme ceci pourrait être vrai pour C++?

NB. 32 bits vs 64 bits le ratio des deux méthodes est égal, mais les deux façons sont comme 20% plus lent sur 64 vs 32 bits?

EDIT: Comme suggéré par Porges et arul, cela pourrait être dû à une diminution du nombre de lectures en mémoire et à un surdébit de branchement.

EDIT2:

Après quelques essais, il semble que la lecture de la mémoire moins de temps est la réponse:

Avec ce code en supposant que la largeur de l'image est divisible par 5, vous obtenez 400% plus rapide:

[StructLayout(LayoutKind.Sequential,Pack = 1)] 
struct PixelContainer { 
    public UInt32 pixel1; 
    public UInt32 pixel2; 
    public UInt32 pixel3; 
    public UInt32 pixel4; 
    public UInt32 pixel5; 
} 

Ensuite, utilisez ceci:

  int readSize = sizeof(PixelContainer); 

      // ..... 

      for (var pointer = line; pointer < line + bd.Stride; pointer += readSize) 
      { 
       multiPixel = *((PixelContainer*)pointer); 
       multiPixel.pixel1 &= 0xFFFFFF00u; 
       multiPixel.pixel2 &= 0xFFFFFF00u; 
       multiPixel.pixel3 &= 0xFFFFFF00u; 
       multiPixel.pixel4 &= 0xFFFFFF00u; 
       multiPixel.pixel5 &= 0xFFFFFF00u; 
       *((PixelContainer*)pointer) = multiPixel; 
      } 
+0

Est-ce sur une machine/processus 32 bits ou 64 bits? – Justin

+0

C'est sur un Windows 7 64 bits, mais j'ai essayé avec 32 bits binaires et 64 bits, l'augmentation des performances est la même avec la première méthode ... En fait, le faire avec un processus 64 bits est plus lent dans les deux cas. –

Répondre

5

Il s'agit d'une technique connue sous le nom de loop unrolling. Le principal avantage de performance devrait provenir de la réduction du surdébit de ramification.

Comme une note de côté, vous pouvez accélérer un peu en utilisant un masque de bits:

*((UInt64 *)pointer) &= 0xFFFFFF00FFFFFF00ul; 
+0

merci pour le lien et la suggestion de bitmasking, ce serait probablement * ((UInt64 *) pointeur) & = 0xFFFFFF00FFFFFF00ul; parce que le pointeur est un octet * –

+0

Oui, vous avez raison, le mot-clé var m'a intrigué :) – arul

+0

Je vais accepter votre réponse, car la boucle de déroulement est la clé ici. J'ai testé avec une structure contenant 5 pixels et c'est beaucoup plus rapide + votre technique de bitmap. –

2

ce n'est pas incrémenter e Le pointeur est plus lent, mais il lit depuis la mémoire. Avec les unités 32 bits, vous effectuez deux fois plus de lectures.

Vous devriez le retrouver plus rapidement si vous écrivez une fois au lieu de deux fois dans la version 64 bits.

+0

Vous voulez dire que l'opération de déréférencement du pointeur est lente? Donc je devrais en faire le moins possible? –

+1

La lecture d'un pointeur n'est pas vraiment lente, c'est juste la partie la plus lente de cette boucle, donc faire moins de lectures le rend plus rapide :) – porges

Questions connexes