Performances et pointeurs.

[Résolu]
Signaler
Messages postés
2527
Date d'inscription
jeudi 15 janvier 2004
Statut
Membre
Dernière intervention
16 octobre 2019
-
Messages postés
1023
Date d'inscription
dimanche 1 août 2004
Statut
Membre
Dernière intervention
17 août 2008
-
Bonjour M'sieurs'Dames,


Je suis en train d'essayer de combler une de mes nombreuses lacunes en prog' en tentant de dompter les pointeurs sous Delphi.

Pour cela, je fais des tests dans tous les sens.

Voici un de ces tests concernant ScanLine.

Ca marche, mais mon problème est que je ne comprends pas pourquoi.


Ce test ne fait que transformer une image couleur en niveaux de gris :


{Méthode ScanLine "classique".}

procedure TForm1.btnScanLineClick(Sender: TObject);

  type pRGBArray = ^TRGBArray;

       TRGBArray = ARRAY[0..0] OF TRGBTriple;

  var Bmp : TBitMap;

      Row : pRGBArray;

      X,Y : Integer;

      R,G,B,Grey : Byte;

      StartTime, EndTime : Int64;

begin

Bmp := TBitMap.Create;

try

  Bmp.LoadFromFile(ChangeFileExt(Application.ExeName,'.bmp'));

  Image1.Picture.Bitmap.Assign(Bmp);

  QueryPerformanceCounter(StartTime);

  for Y := 0 to Bmp.Height-1 do begin

    Row := Bmp.ScanLine[Y];

    for X := 0 to Bmp.Width-1 do begin

      R := Row[X].rgbtRed;

      G := Row[X].rgbtGreen;

      B := Row[X].rgbtBlue;

      Grey := (R+G+B) div 3;

      Row[X].rgbtRed := Grey;

      Row[X].rgbtGreen := Grey;

      Row[X].rgbtBlue := Grey;

    end;

  end;

  QueryPerformanceCounter(EndTime);// environ 7300 Kticks.

  Image2.Picture.Bitmap.Assign(Bmp);

  Label1.Caption : = Format('%.0n KCycles',[(EndTime-StartTime)*0.001]);

finally Bmp.Free; end;

end;


{ScanLine préalable de TOUT le Bitmap dans un tableau de pointeurs.}

procedure TForm1.btnTestAClick(Sender: TObject);

  type pRGBArray = ^TRGBArray;

       TRGBArray = ARRAY[0..0] OF TRGBTriple;

  var Bmp : TBitMap;

      MonTab : Array of pRGBArray;// ça, c'est nouveau!

      X,Y : Integer;

      R,G,B,Grey : Byte;

      StartTime, EndTime : Int64;

begin

Bmp : = TBitMap.Create;

try

  Bmp.LoadFromFile(ChangeFileExt(Application.ExeName,'.bmp'));

  QueryPerformanceCounter(StartTime);


  SetLength(MonTab,Bmp.Height); //On remplit d'abord tout le tableau dynamique.

  for Y := 0 to Bmp.Height-1 do MonTab[Y] := Bmp.ScanLine[Y];


  for Y := 0 to Bmp.Height-1 do begin //..puis on accède à chaque pixel par ce tableau.

    for X := 0 to Bmp.Width-1 do begin

      R := MonTab[Y]^[X].rgbtRed; //ou R := MonTab[Y,X].rgbtRed;

      G := MonTab[Y]^[X].rgbtGreen;

      B := MonTab[Y]^[X].rgbtBlue;

      Grey := (R+G+B) div 3;

      MonTab[Y]^[X].rgbtRed := Grey;

      MonTab[Y]^[X].rgbtGreen := Grey;

      MonTab[Y]^[X].rgbtBlue := Grey;

    end;

  end;

  QueryPerformanceCounter(EndTime);//environ 5500 Kticks.

  Image3.Picture.Bitmap.Assign(Bmp);

  Label2.Caption := Format('%.0n KCycles',[(EndTime-StartTime)*0.001]);

finally Bmp.Free; end;


end;




Je ne comprends pas pourquoi ma 2ème méthode est environ 25% plus rapide que la méthode ScanLine classique. En effet, dans les 2 méthodes, on parcourt toutes les lignes du Bmp pour les scanner une à une.

Pourquoi l'accès à chaque pixel est plus rapide en passant par un tableau?
'doit y avoir une histoire de gestion de la mémoire la-dessous, non?


Si une âme charitable pouvait m'allumer la lampe à Eugène ce serait sympa car je sens confusément que je n'ai encore rien compris à ces foutus pointeurs.

24 réponses

Messages postés
4202
Date d'inscription
samedi 16 octobre 2004
Statut
Modérateur
Dernière intervention
13 juin 2020
37
normal, l'accés a Scanline du Bitmap est assé long...
donc acceder a scanline a chaque boucle est long.

en faisant un tableau de ptr avant, permet donc de gagner un peu de temps

sinon voici les algo que j'utilise :

type
  pScanLine = ^TScanLine;
  TScanLine = array[0..32767] of integer;
  ByteQuad  = array[0..3] of byte;
  ByteTri   = array[0..2] of byte;
  pInt64    = ^Int64;

var
  BmpBuffer : TBitmap;

{ ------------------------------------------------------------------------ }

procedure Max(const p : pByte; const A,B,C : byte);
begin
  if (A > B) and (A > C) then
     p^ := A
  else
  if (B > C) then
     p^ := B
  else
     p^ := C;
end;

procedure Min(const p : pByte; const A,B,C : byte);
begin
  if (A < B) and (A < C) then
     p^ := A
  else
  if (B < C) then
     p^ := B
  else
     p^ := C;
end;

procedure Clamp(const p : pByte; const Val,aMin, aMax : integer);
begin
  if Val < aMin then p^ := aMin else
  if Val > aMax then p^ := aMax else
                     p^ := Val;
end;

{ ------------------------------------------------------------------------ }

procedure GrayByMinMax(src, dest : TBitmap; const pCycles: pInt64 = nil);
var X,Y   : integer;
    PCS,PCE : int64;
    pScan : pScanLine;
    Pix   : ByteQuad;
    Bo,Bm,Bx : byte;
begin
  QueryPerformanceCounter(PCS);
  Src.PixelFormat  := pf32Bit;
  Dest.PixelFormat := pf32Bit;
  Dest.Assign(Src);
  for Y := 0 to Dest.Height-1 do begin
      pScan := Dest.ScanLine[Y];
      for X := 0 to Dest.Width-1 do begin
          Pix := ByteQuad(pScan[X]);
          Max(@Bx,Pix[0],Pix[1],Pix[2]);
          Min(@Bm,Pix[0],Pix[1],Pix[2]);
          Bo := (Bx + Bm) shr 1;
          pScan[X] := (Bo shl 16) or (Bo shl 8) or (Bo);
      end;
  end;
  QueryPerformanceCounter(PCE);
  if pCycles <> nil then
     pCycles^ := PCE-PCS;
end;

{ ------- }

procedure GrayByRGBAverage(src, dest : TBitmap; const pCycles: pInt64 = nil);
var X,Y   : integer;
    PCS,PCE : int64;
    pScan : pScanLine;
    Pix   : ByteQuad;
    Bo    : byte;
begin
  QueryPerformanceCounter(PCS);
  Src.PixelFormat  := pf32Bit;
  Dest.PixelFormat := pf32Bit;
  Dest.Assign(Src);
  for Y := 0 to Dest.Height-1 do begin
      pScan := Dest.ScanLine[Y];
      for X := 0 to Dest.Width-1 do begin
          Pix := ByteQuad(pScan[X]);
          Bo  := (Pix[0] + Pix[1] + Pix[2]) div 3;
          pScan[X] := (Bo shl 16) or (Bo shl 8) or (Bo);
      end;
  end;
  QueryPerformanceCounter(PCE);
  if pCycles <> nil then

     pCycles^ := PCE-PCS;
end;

{ ------- }

const
  LumRed   = 0.2125;
  LumGreen = 0.7154;
  LumBlue  = 0.0721;

function Lum(const color : integer) : integer;
var
  LC : integer;
begin
  LC := round( (byte(Color)*LumRed) +
               (byte(Color shr 8)*LumGreen) +
               (byte(Color shr 16)*LumBlue)
             );
  if LC > 255 then
    LC := 255
  else
  if LC < 0 then
    LC := 0;
  result := integer(LC or (LC shl 8) or (LC shl 16));
end;

{ ------- }

procedure TrueGrayScale(Src : TBitmap; Dest : TBitmap;  const pCycles: pInt64 = nil);
var
  PCS,PCE : int64;
  X,Y   : integer;
  pScan : pScanLine;
begin
  QueryPerformanceCounter(PCS);
  Src.PixelFormat := pf32bit;
  Dest.Assign(Src);
  for Y := 0 to Dest.Height-1 do
  begin
    pScan := Dest.ScanLine[Y];
    for X := 0 to Dest.Width-1 do
      pScan[X] := Lum(pScan[X]);
  end;
  QueryPerformanceCounter(PCE);
  if pCycles <> nil then

     pCycles^ := PCE-PCS;
end;

<hr size="2" width="100%" />
Messages postés
4202
Date d'inscription
samedi 16 octobre 2004
Statut
Modérateur
Dernière intervention
13 juin 2020
37
en fait, voila le code que l'on appel avec ScanLine :

-> Scanline[Y]
  -> GetScanline
    -> Changing
    -> condition de verification des limites
    -> DibNeeded
    -> GDIFlush
    -> Retour du pointeur

<hr size="2" width="100%" />
Messages postés
1023
Date d'inscription
dimanche 1 août 2004
Statut
Membre
Dernière intervention
17 août 2008
2
Salut !
As-tu vu mon code source sur le traitement archi-extra-giga-bref-rapide des bitmaps via scanline ?

Le problème du Row[2X], c'est que chaque emploi demande au CPU de faire l'addition (@Row + X * SizeOf(TRGBTriple)) et de renvoyer le résultat. Et tu l'emploie 6 fois par pixel !

Alors qu'en faisant :

var
Pix: PRGBTriple;
begin
Pix := Bmp.ScanLine[239];
for X := 0 to 76799 do
begin
Grey := (Pix^.rgbtRed + Pix^.rgbtGreen + Pix^.rgbtBlue) div 3;
Pix^.rgbtRed := Grey;
Pix^.rgbtGreen := Grey;
Pix^.rgbtBlue := Grey;
Inc(Pix);
end;
end;

Là, tu as une addition par pixel, au lieu de 6, puisque l'usage d'un pointeur direct sur la mémoire à changer permet d'économiser le temps d'accès que Delphi génère avec ses foutus pseudos-pointeurs-de-tableaux infinis (le array[0..0] est supra lent).
Messages postés
418
Date d'inscription
mardi 3 janvier 2006
Statut
Membre
Dernière intervention
26 novembre 2013
3
Salut.

Tests faits sur gros bitmaps avec le code de Florent en n'appelant qu'1 x Scanline et en incrémentant Pix.
C'est environ 2x plus rapide que la méthode classique (appel à Scanline pour chaque ligne du bitmap).
De plus, pour peindre un bitmap dans une couleur uniforme, c'est tout aussi rapide que Bmp.Canvas.FillRect ! 
Dès lors,  n'a-ton pas atteint l'optimisation maximale sous Delphi ?
Messages postés
702
Date d'inscription
vendredi 21 mars 2003
Statut
Membre
Dernière intervention
1 octobre 2009
4
Le pointer est assez performant à la chasse, mais ses longues périodes d'arrêt font qu'il n'est pas si rapide que ça ! Finalement !
Tout bien pesé !

Ken@vo




<hr size="2" width="100%" />



Code, Code, Codec !
Messages postés
2106
Date d'inscription
mardi 10 décembre 2002
Statut
Modérateur
Dernière intervention
15 décembre 2014
5
Salut Cari,

j' ai suivi la conversation de loin car je ne voulais pas me mouiller ^^,
mais vu qu' il pleut et que l' on est tous mouillé ...

Je t' annonce tout de suite qu' à ta questions, je ne sais que répondre: bem les pointeurs c' est plus rapide car ça pointe directement dans la mémoire vive (aussi apelée RAM biensûr) oú les données (les pixels donc) de l' image sont stockées.

J' ai moi même une source ou 2 sur le site utilisant scanLine, notamment pour le redimensionnement.
Par contre, je ne connaissait pas la méthode optimisée que Florenth propose ici.
C' est très interessant et ça mériterait une source (Ça c' est pour Florenth) avec comparaison des diverses méthodes.

Moi ce qui m' embête un peu c' est que les méthodes présentes sur le site ne fonctionnent qu' avec un pixel format défini (Toi aussi vu que tu utilises TRGBTriple, donc 16 millions de couleurs
): ça nous oblige à convertir l' image (ou alors la propre procédure le fait) vers le pixelFormat utilisé par la fonction.
Pour illustrer tout ça, prenons la fonction de conversion en niveau de gris que tu proposes: si tu as une image à traiter en 16 couleurs, 256, 16 millions ou 24 millions tu vas devoir convertir l' image en 16 millions de couleurs pour faire la conversion: Une image en 24 millions de couleurs va donc perdre en qualité et une image de 256 couleurs aurait pu être convertie 10 fois plus vite si une fonction était écrite pour ce format là (même si le résultat avec ta fonction sera de meilleure qualité je te l' accorde vu que l' on passe l' image en 16 millions de couleurs avant la conversion).

Sinon je suis d' accord avec toi: les pointeurs c' est bien mais on y comprend rien  !!!
Entre @xxx, xxx^, Pointer(xxx),   pRGBArray = ^TRGBArray,
TRGBArray = ARRAY[0..0] OF TRGBTriple, Row : pRGBArray ça manque cruellement d' un bon tuto pour clouer le clou comme on dit.

Tu as le mérite de vouloir mettre les points sous les "i", quitte à passer pour ... une personne humble!

Je suis sûr que ton topic en va interesser plus d' un! Moi je suis déjà en train d' attendre l' opinions des autres membres ...

A+
Messages postés
2106
Date d'inscription
mardi 10 décembre 2002
Statut
Modérateur
Dernière intervention
15 décembre 2014
5
Pendant que j' attends vos commentaires, les points c' est sur les "i" ^^ qu' on les mets ... (je retourne me faire foueter)
A+
Messages postés
2106
Date d'inscription
mardi 10 décembre 2002
Statut
Modérateur
Dernière intervention
15 décembre 2014
5
Un boulet? plutôt un ballon de boderuche!


Il reste encore des topics et sources interessants! Ça nous change de "j' en ai besoin tout de suite, c' est urgent car c' est pour pomper un max d' argent avec un programme de merde et pour montrer que je suis le roi des programmeurs".

Je viens de moins en moins et vous me manquiez les gars! Y en a pas un qui passerait ses vacances au Portugal par hasard pour se faire une petite bouffe entre Delphistes? Faudrait organiser une réunions anuelle entre membres un de ces 4 ...

A+

Les pointeurs c'est comme des femmes : faut pas trop chercher à les comprendre.

Je n'ai pas particper à la discussion car ca dépasse mes compétences .
Messages postés
702
Date d'inscription
vendredi 21 mars 2003
Statut
Membre
Dernière intervention
1 octobre 2009
4
Il est sûr que gérer les formats des bitmaps est <strike>un sacré merdier</strike> drôlement compliqué.

Par exemple pf24bit et pf32bit sont des formats de couleurs codées RVB (RGB if you préfères !) alors que les formats  pf4bit, pf8bit renvoient un numéro de couleur qu'il faut s'en aller quérir derechef dans une palette associée !
Et puis <strike>faire gaffe</strike> user de grandes précautions dans l'utilisation d'un unique ScanLine sur la dernière ligne du bitmap, sachant que chaque début de ligne est "aligné" sur 4 octets (ou 8), et cela peut <strike>foutre le bordel</strike> causer de grandes contrariétés dans le cas d'image au nombre de pixels horizontaux non divisibles par 4 (impair en particulier) !

Sur ce commentaire quelque peu hors sujet - mais il y a des choses bonnes à dire - je vous souhaite une agréable soirée

Ken@vo




<hr size="2" width="100%" />



Code, Code, Codec !
Messages postés
4720
Date d'inscription
dimanche 26 février 2006
Statut
Modérateur
Dernière intervention
31 juillet 2021
14
@Caribensila :

..me semble avoir vu passé un peu de cours sur les pointeurs sur le site..
si je le retrouve dans mon fourbi..
je te l'envoie avec un ruban autour.

cantador
Messages postés
418
Date d'inscription
mardi 3 janvier 2006
Statut
Membre
Dernière intervention
26 novembre 2013
3
@Kenavo : tu as parfaitement raison avec les bitmap de largeur non divisible par 4.
Il faut donc faire un seul appel à Scanline mais sur la 1° ligne. Voir le code de l' OptimizedBitmap de Florent (cfr le lien renseigné par lui).

A +
Thierry
Messages postés
1023
Date d'inscription
dimanche 1 août 2004
Statut
Membre
Dernière intervention
17 août 2008
2
Ouais, il faut utiliser packed pour lire des structures bien définies en terme de taille.

La mise en garde de l'aide est légitime mais pour d'autres raisons. En fait si tu déclares
type
TRec = record
E: Extended;
I: Integer;
B: Byte;
I2: Integer;
end;

Alors SizeOf(TRec) ne sera pas la même que s'il était packed.
Tout ça parce que Delphi rajoute des zéros pour compléter les trous et aligner les débuts de variables sur des dword (directive {$A4} il me semble), ce qui rend l'accès plus rapide sur les processeurs 32 bits (puisque un dword 4 octets 32 bits)

Mais dons notre cas, on ne peut pas faire comme ça, puisque la structure interne du format bitmap est telle que les données sont alignées sur 3 octets pour pf24bit, voire moins pour les autres (pf16bit, pf15bit, pf8bit).
Mais dans tous les cas, ici il n'y a pas de baisse de perf parce que leur remarque n'est valable que pour des types dont la taille est un multiple de 4 octets.
Sinon, il y a de toutes façons une opération interne du CPU pour récupérer la partie qui t'intéresse.

Et en effet, quand on utilise des pointeurs, il vaut mieux être sûr de la taille de que qu'on manipule, donc utiliser packed de toutes façons.

NB: à noter que, dans mon exemple précédent, j'aurais pu remplacer tous les P^.R par P.R car cette syntaxe est supportée par Delphi. Cela dit, ce n'est pas conseillé (mais utile pour relire certains codes), car on oublie qu'on manipule un pointeur et puis on risque de faire la même boulette en faisant Pixel1 := Pixel2 (copie d'adresse) qui lui n'est pas identique à Pixel1^ := Pixel2^ (copie de données)

NB2: la différence entre P et P^ est la même en assembleur.
Si P est stocké dans EAX, alors P^ correspond à [EAX], ce qui est sympa à savoir pour comprendre le code asm généré que l'on peut voir par Ctrl-Alt-C lorsqu'on est sur un point d'arrêt.
Et donc, mov ebx,[eax] correspond à un déférencement de eax: le Integer qui se situe à l'adresse eax sera placé dans ebx.
Messages postés
2527
Date d'inscription
jeudi 15 janvier 2004
Statut
Membre
Dernière intervention
16 octobre 2019
18
Merci pour les algo, f0xi. Ca va me donner du grain à moudre... 

Cependant, je crois qu'il y a quand même un truc qui nous échappe, là...
Tu dis : « Normal, l'accès a Scanline du Bitmap est assez long... donc acceder a scanline a chaque boucle est long. »

Mais si je fais :

NB: l'image fait 320x240 pixels pour éviter les ajustements de fin de ligne !!!

Row := Bmp.ScanLine[239]; // 1 seul appel à ScanLine au début du Bmp (coin inf. gauche). for X : 0 to 76799 do begin // 76799 320 x 240  -  1
   R := Row[X].rgbtRed;
   G := Row[X].rgbtGreen;
   B := Row[X].rgbtBlue;
   Grey := (R+G+B) div 3;
   Row[X].rgbtRed := Grey;
   Row[X].rgbtGreen := Grey;
   Row[X].rgbtBlue := Grey;
end;

Ca donne les perfs les plus décevantes!

Il n'y a qu'1 seul appel à ScanLine[] et c'est la plus lente de toutes les méthodes ! ! !

J'suis tj dans le brouillard, quoi... 
Messages postés
2527
Date d'inscription
jeudi 15 janvier 2004
Statut
Membre
Dernière intervention
16 octobre 2019
18
Salut,

« Dès lors,  n'a-ton pas atteint l'optimisation maximale sous Delphi ? »
Oui, si on considère que Bmp.Canvas.FillRect est le nec plus ultra en matière d'optimisation.

J'entrevois cependant une posibilité d'encore diminuer le temps de traitement par deux (au moins théoriquement) :

A la condition de disposer d'un Multi Core, c'est de faire une moitié du traitement dans un Thread et l'autre moitié dans un autre Thread.
La difficulté étant de partager un Bmp entre 2 Threads.  Mais ça doit être jouable.
En tout cas, ça vaut le coup d'explorer cette voie car cette technique du multi-threading dans un environnement MultiCore est applicable à tout traitement long.

Mais tout cela m'éloigne de ma préoccupation actuelle que sont les pointeurs et ça ne répond toujours pas à ma première question : « Pourquoi l'accès à chaque pixel est plus rapide en passant par un tableau? ».   
Messages postés
2527
Date d'inscription
jeudi 15 janvier 2004
Statut
Membre
Dernière intervention
16 octobre 2019
18
Je sens comme une profonde solitude qui m'remonte par le bas et qui me paralyse la repousse neuronale...






Un vol de perdreaux
Par dessus les champs
Montêêêêêêê
êêêêê
dans les nuages- ageux... 
Messages postés
2527
Date d'inscription
jeudi 15 janvier 2004
Statut
Membre
Dernière intervention
16 octobre 2019
18
Salut Mauricio,

« quitte à passer pour ... une personne humble! »
- N'ayons pas peur des mots :   "quitte à passer pour un boulet!" 

En attendant, tu me remontes le moral. Merci. 
Messages postés
2527
Date d'inscription
jeudi 15 janvier 2004
Statut
Membre
Dernière intervention
16 octobre 2019
18
Ok. Merci.


Mais sans ruban, stp.
J'ai peur de m'y prendre les pieds...  
Messages postés
2527
Date d'inscription
jeudi 15 janvier 2004
Statut
Membre
Dernière intervention
16 octobre 2019
18
Je te remercie, Cantador.
C'est le tuto de CptPingu.
C'est en effet une bonne entrée en matière pour qui s'intéresse aux pointeurs.
Messages postés
2527
Date d'inscription
jeudi 15 janvier 2004
Statut
Membre
Dernière intervention
16 octobre 2019
18
Un grand merci à Florent qui a pris le temps de nous éclaircir le truc.

Pour généraliser, j'ajouterais que dans le cas d'un tableau ou d'un type structuré, un pointeur contient l'adresse du premier élément de la structure.
Et donc, si j'ai bien compris, l'utilisation de Packed est obligatoire lorsqu'on veut utiliser des pointeurs vers une structure quelconque. C'est bien ça?

Donc, la mise en garde de l'Aide Delphi est discutable quand on utilise des pointeurs, à savoir :
« l'utilisation de packed ralentit l'accès aux données... »

Bon, petit à petit, je commence à y voir plus clair je crois...
Encore merci, Flo  :)