Vous ne trouvez pas de réponse à votre problème ? Alors posez la question dans le forum. Souvenez-vous qu'il n'y a jamais de question bête, mais rester dans l'ignorance parce que l'on n'ose pas poser une question, ça c'est une erreur !

TACTIQUES D’OPTIMISATION DE LA VITESSE D'EXECUTION DU CODE


Information sur le tutorial

Catégorie :Exécution Date de création : 19/10/2007 14:33:30 Vu : 8 689 fois

Note :
Aucune note

Commentaire sur cette source (41)
Ajouter un commentaire et/ou une note


Description

Présentation de la "philosophie" et de quelques tactiques pour optimiser la vitesse d'exécution d'un code.

Tutorial

TACTIQUES D’OPTIMISATION DE LA VITESSE D'EXECUTION DU CODE






L’optimisation du code est la pratique qui consiste à modifier un code correct pour le rendre plus efficace. Il peut s’agir de la taille ou de la vitesse d’un programme. Mais dans ce tutoriel, nous ne nous occuperons que de la vitesse d’exécution.


On peut considérer ce problème d’optimisation de la vitesse à deux niveaux : stratégique ou tactique.

Au niveau stratégique, il s’agit de modifications obtenues par la refonte de la conception de tout le programme et des données. Autant dire de repartir presque à zéro. Il y a cependant une solution extrêmement efficace qui consiste à acheter (ou à faire acheter par l’utilisateur qui se plaint de la lenteur) un nouvel ordinateur plus rapide et plus puissant (héhéhé).

Je vous laisserai décider de la stratégie à adopter et je ne traiterai que des tactiques. C’est-à-dire de modifications à petite échelle qui ne concerneront que quelques lignes de code.


On a calculé, à propos des programmes Fortran, que 4% d’un programme représentait en moyenne 50% de son temps d’exécution. C’est sans doute vrai pour tous les langages. Nous allons voir comment améliorer cet état de fait.


    D’abord parlons d’un malentendu courant que voici :


«La réduction du nombre de lignes de code conditionne la taille et la vitesse de l’exécutable    


C’est faux !

Une preuve?

- Initialisons un tableau de 10 éléments :


for i := 1 to 10 do MaTable[ i ] :=0;


A votre avis, la méthode ci-dessous sera-t elle plus rapide ou plus lente?


MaTable[1] := 0;

MaTable[2] := 0;

MaTable[3] := 0;

MaTable[4] := 0;

MaTable[5] := 0;

MaTable[6] := 0;

MaTable[7] := 0;

MaTable[8] := 0;

MaTable[9] := 0;

MaTable[10] := 0;


Un test avec Delphi7 donne l’avantage à la deuxième méthode avec un ratio de performance de 1.7 : 1 (1.7 fois plus rapide !)


    J’en profite pour effectuer une petite mise en garde.

Les techniques que je vais présenter ne constituent pas des recettes à appliquer aveuglément. Peu de techniques s’appliqueront d’une façon assez générale pour être directement copiées dans votre code. Ce sera à vous de les tester et de vérifier que votre compilateur n’y est pas allergique. En effet, certaines techniques, paraissant pourtant de bon sens, pourront dégrader les performances après la compilation. J’en montrerai un exemple et je rappellerai ce point très important : « testez, testez et re-testez ! »


    Voici les six commandements à garder en mémoire pour obtenir de bonnes performances. Allez ! Mettons les doigts dans le cambouis !





« LOGIQUE DE DELPHI TU ADOPTERAS »


Je ne doute pas que vous ayez tous une certaine dose de logique. Mais bon… Il ne faut pas craindre l’overdose.

Par exemple, les expressions logiques suivantes sont équivalentes :

(not A) and (not B)

not (A or B)

En choisissant la 2ème, on gagne une opération ‘not’. Le temps gagné sera sans doute infime, mais dans une boucle très longue, ça se sentira.



Sortir d’un test en temps et en heure :


Voici un exemple de code optimisable :


for i := 0 to High( TableTest ) do if (TableTest[ i ] < 0) then NegativeFound := true;


C’est une boucle de recherche, un cas très courant. Le principe d’optimisation consiste à ne pas continuer à tester lorsque le résultat est connu. Appliquons ce principe :


for i := 0 to High( TableTest ) do begin

if TableTest[ i ] < 0 then begin

NegativeFound := true;

Break; //On sort dès que le résultat est connu.

end;

end;


Ici, le gain de performance sera très variable en fonction du contexte. Mais le plus souvent, le gain sera très important. Si on place une valeur négative au milieu du tableau, on peut s’attendre à un temps de traitement deux fois plus rapide. Et c’est bien ce que confirment les tests.


Un autre exemple :


N := Random(101);

if (N>=0) and (N<5) then Categorie := 1;

if (N>=5) and (N<20) then Categorie := 2;

if (N>=20) and (N<101) then Categorie := 3;


Et ce même code partiellement optimisé :


if (N>=20) then Categorie := 3 //On sort dès que le résultat est connu.

else if (N>=5) then Categorie := 2//On sort dès que le résultat est connu.

else Categorie := 1;

Ratio de performance = 1.3 : 1 Mais on peut faire mieux…



Ranger les tests par ordre croissant :


if (N<5) then Categorie := 1

else if N<20 then Categorie := 2

else Categorie := 3;

Maintenant, le ratio de performance = 1.9 : 1


Vous allez me dire : « - Où est la logique dans tout cela ? »

- Bein, c’est la logique du compilateur de Delphi7, tiens !


Je vous avez bien dit : « testez, testez et re-testez ! »

Cet exemple souligne la nécessité de ne pas suivre aveuglément des conseils d’optimisation. Ce qui est valable pour C# ne le sera pas forcément pour Java ou Delphi. Certaines optimisations valables pour un langage dégraderont même très fortement les performances dans un autre langage. De plus, cela peut même varier d’une version à l’autre d’un même compilateur. Vous devrez donc toujours mesurer les performances car les règles du jeu changent lorsque vous changez de langage, de compilateur ou de version d’un même compilateur, de processeur, de mémoire vive, de région (bon, d’accord, peut-être pas de région), etc…


Je cite Olivier DAHAN :

« Prenons l’instruction Case pour exemple. Jusqu’à Borland Pascal 7 (BP7) l’ordre le meilleur des constantes de cas en termes d’efficacité du code devait être celui de l’ordre décroissant de la fréquence d’utilisation : le cas le plus fréquent devant être placé en premier, le moins fréquent en dernier. Cela était lié au code machine généré par le Pascal (une suite de comparaisons et de sauts de cas en cas, du premier au dernier). L’Objet Pascal de Delphi génère un code très différent, et ici le moyen d’optimiser l’efficacité d’un Case est de placer les constantes de cas dans l’ordre numérique croissant (qui est celui de l’ordre de la déclaration dans le type si le sélecteur de cas est de type énuméré). »


J’espère donc avoir réveiller votre méfiance.


Sinon, je rajoute une couche :



Case of meilleur que if then else :


Au lieu d’écrire :


case N of

20..100 : Categorie := 3;

5..19 : Categorie := 2;

else Categorie := 1;

end


On préférera la formulation 1.5 fois plus rapide :


case N of

0..4 : Categorie := 1;

5..19 : Categorie := 2;

else Categorie := 3;

end;

Maintenant, le ratio de performance = 1.95 : 1


Presque deux fois plus rapide que l’exemple de départ ! (Ca tombe bien pour une fois, car Case of est toujours plus lisible que des if then else imbriqués).


-En C#, Case of et if then else sont comparables.

-Par contre, en Java if then else est 6 fois plus rapide qu’un case of.

-Et en Visual Basic, c’est Case of qui est 4 fois plus rapide que if then else (Steve McConnell dixit).


A bon entendeur, salut !



Remplacer des expressions logiques complexes par un tableau :


A titre d’exemple abstrait, supposons que vous ayez à assigner un numéro de catégorie (0, 1, 2, 3) à un objet en fonction de son appartenance à un ou plusieurs ensembles A, B, C :



Exemple extrait du livre de Steve McConnell «CODE COMPLETE» et adapté du C++ à Delphi.

















En général, on écrira un truc du genre :


if ((N in EnsembleA) and not (N in EnsembleC)) or ((N in EnsembleA) and

(N in EnsembleB) and (N in EnsembleC)) then Categorie := 1

else if ((N in EnsembleB) and not (N in EnsembleA)) or ((N in EnsembleA) and

(N in EnsembleC) and not (N in EnsembleB)) then Categorie := 2

else if (N in EnsembleC) and not (N in EnsembleA) and not (N in EnsembleB)

then Categorie := 3

else Categorie := 0;


Avec une recherche dans un tableau, nous n’allons certes pas augmenter la lisibilité du code, mais ce ne sera pas beaucoup plus sibyllin… De toute façon, on peut toujours documenter le code avec des commentaires et un tableau sera plus facile à modifier si nécessaire. Cela donnera :


type

TTab = array[0..1, 0..1, 0..1] of byte;


const

TableCategorie : TTab = // notB,notC notB,C B,notC B,C


(((0, 3), (2, 2)), // notA

((1, 2), (1, 1))); // A


Categorie := TableCategorie[Ord(N in EnsembleA), Ord(N in EnsembleB), Ord(N in EnsembleC)];

Ratio de performance = 9 : 1





« LES BOUCLES TU VERIFIERAS »



Placer les tests à l’extérieur des boucles :


Il est facile de repérer les tests qui ne sont pas modifiés à l’intérieur des boucles :


for i := 0 to High( TableTest ) do begin

if Choix = 0 then TableTest[ i ] := TableTest[ i ] + 1

else TableTest[ i ] := TableTest[ i ] - 1;

end;


Et la même chose optimisée :


if Choix = 0 then

for i := 0 to High( TableTest ) do TableTest[ i ] := TableTest[ i ] + 1

else for i := 0 to High( TableTest ) do TableTest[ i ] := TableTest[ i ] - 1;

Ratio de performance = 1.35 : 1



Déroulez les boucles :


Cette technique se rapproche du premier exemple de ce tutoriel où on déroulait complètement une boucle de 10 itérations. En pratique, ce n’est pas réalisable pour un grand nombre d’éléments ou quand celui-ci nous est inconnu. On peut cependant adapter cette technique. Voici une boucle classique :


i:=0;

while i < High(TableTest) do begin

TableTest[ i ] := 0;

Inc(i);

end;


Et un exemple de la même boucle partiellement déroulée :


i:=0;

while i < High(TableTest)-3 do begin

TableTest[ i ] := 0;

TableTest[ i+1 ] := 0;

TableTest[ i+2 ] := 0;

TableTest[ i+3 ] := 0;

inc(i,4);

end;

{On ajuste les derniers cas:}

if i = High(TableTest)-1 then TableTest[ High(TableTest)-1 ] := 0;

if i = High(TableTest)-2 then TableTest[ High(TableTest)-2 ] := 0;

if i = High(TableTest)-3 then TableTest[ High(TableTest)-3 ] := 0;

Ratio de performance = 1.4 : 1


Mais c’est une technique que je ne recommande pas. D’abord, je la trouve affreuse. Ensuite, il faut vraiment tester le gain de performance dans chaque cas car il peut changer en fonction des calculs effectués. Le compilateur de Delphi fait en général beaucoup mieux qu’un tel bricolage. A tester et n’utiliser que lorsque le gain en vitesse de traitement est crucial.



Les valeurs sentinelles :


Exemple d’une boucle de recherche contenant un test composé :


NegativeFound := False;

i := 0;


while (not NegativeFound) and (i < High(TableTest)) do begin

if TableTest[ i ] < 0 then NegativeFound := true;

inc(i);

end;


Dans ce code, on sort bien de la boucle en temps et en heure, cependant chaque itération de la boucle teste (not NegativeFound) et (i < High(TableTest)). Le test TableTest[ i ] < 0 sert à déterminer si la valeur négative a été trouvée. La boucle exécute donc 3 tests par itération.


Mais on peut n’effectuer qu’un test par itération. Il suffit d’affecter la valeur de recherche à l’élément qui suit immédiatement la fin de la plage de recherche (N’oubliez pas d’allouer de l’espace pour cet élément lorsque vous déclarez le tableau !). Ici, on affectera une valeur négative au dernier élément du tableau. Si on ne trouve pas de valeur négative avant de rencontrer celle qu’on a placée à la fin, on saura que la valeur recherchée ne se trouve pas dans le tableau.

Quelques lignes de code valent mieux qu’une longue explication :



TableTest[ High(TableTest) ] := -1; // Valeur sentinelle.


NegativeFound := false;

I := 0;

while not TableTest[ i ] < 0 do inc(i); //On sort.

{Et on vérifie que la valeur a été trouvée}.

if i < High(TableTest) then NegativeFound := true;

Ratio de performance = 1.7 : 1



Placez la boucle la plus active à l’intérieur :


Lorsque des boucles sont imbriquées, réfléchissez à la boucle que vous allez placer à l’extérieur et à celle que vous voulez à l’intérieur. L’exemple suivant est optimisable :


for Col := 0 to 999 do

for Ligne := 0 to 1 do Total := Total + MaTable[ Col, Ligne ];


La boucle extérieure est exécutée 1000 fois. La boucle intérieure 1000*2 = 2000 fois. Soit un total de 3000 itérations.

En inversant les boucles :


for Ligne := 0 to 1 do

for Col := 0 to 999 do Total := Total + MaTable[ Col, Ligne ];

Ratio de performance = 1.9 : 1


Dans le code optimisé, la boucle extérieure est exécutée 2 fois. La boucle intérieure 2*1000 = 2000. Soit un total de 2002 itérations.





« ATTENTION AUX DONNEES TU FERAS »


Voici quelques lignes qu’on peut souvent trouver dans un code qui affiche une animation à l’écran par exemple :


Var X, Y, Angle, Hypot : Smallint;


for Angle := 0 to 360 do begin

X := Round( cos(DegToRad(Angle)) * Hypot );

Y := Round( sin(DegToRad(Angle)) * Hypot );

end;



Se méfier des fonctions système :


D’abord, l’unité Math nous permet d’utiliser la fonction SinCos() qui est deux fois plus rapide que l’appel à Sin() suivi de l’appel à Cso() :



Var X, Y, Angle, Hypot : Smallint

Sinus, Cosin : Extended;


for Angle := 0 to 360 do begin

SinCos(DegToRad(Angle) ,Sinus, Cosin);

X := Round( Cosin * Hypot );

Y := Round( Sinus * Hypot );

end;


Ratio de performance = 1.3 : 1


DegToRad() est très explicite et favorise la lisibilité du code, mais il est aussi très gourmand en temps de traitement. On le vire et on fait l’opération à la main :



for Angle := 0 to 360 do begin

SinCos(Angle*0.0174532925199433 ,Sinus, Cosin);

X := Round( Cosin * Hypot );

Y := Round( Sinus * Hypot );

end;

Ratio de performance = 1.5 : 1 (Par rapport au premier exemple)



Mise en cache des données :


La véloce fonction SinCos() n’est pas aussi rapide qu’un accès à un tableau. La technique de mise en cache des données consiste à enregistrer systématiquement les valeurs que vous utilisez souvent en mémoire. Ici, nous allons stocker les valeurs trigonométriques dans un tableau :


Var //var globales.

MonCos : array[0..360] of Extended; // Tableau en cache.

    MonSin : array[0..360] of Extended; // Tableau en cache.



procedure TForm1.FormCreate(Sender: TObject);

var Angle : Integer;

Sinus, Cosin : Extended;

begin

for Angle := 0 to 360 do begin

    SinCos(Angle*0.0174532925199433 ,Sinus, Cosin);

    MonSin[ Angle ] := Sinus; //Création des tableaux en cache.

    MonCos[ Angle ] := Cosin;

end;


...


for Angle := 0 to 360 do begin

X := Round( MonCos[ Angle ] * Hypot );

Y := Round( MonSin[ Angle ] * Hypot );

end;

Ratio de performance = 7 : 1 (Par rapport au premier exemple)



La CPU est optimisée pour le 32 bits :


Vous aviez sans doute remarqué qu’on travaillait avec des SmallInt dans la boucle de calcul. C’est justifié car largement suffisant pour calculer des coordonnées écran. Cependant, la CPU est beaucoup plus à l’aise en travaillant avec des Integers. La modification est minime :


Var X, Y, Angle, Hypot : Integer;

Sinus, Cosin : Extended;

Ratio de performance = 9 : 1 (Par rapport au premier exemple)



Précision superflue des fonctions système :


    Tiens, c’est vrai qu’on travaille sur l’écran !

Si une valeur trigonométrique en Extended (15aine de chiffres après la virgule) se justifie pour faire « alunir » un homme, ici avec mon écran de ‘m…’, je n’ai pas besoin d’une telle précision. On va diminuer la précision des tableaux de valeurs trigonométriques en cache.

    Et comme nous venons de voir que notre CPU aimait les 32 bits. Nous allons donc, par la même occasion, transformer les extendeds en Integers.

Dans la foulée, on supprimera le Round, fonction système très coûteuse aussi.



Var //var globales.

MonCosInt : array[0..360] of Integer; // Tableau en cache.

    MonSinInt : array[0..360] of Integer; // Tableau en cache.



procedure TForm1.FormCreate(Sender: TObject);

var Angle : Integer;

Sinus, Cosin : Extended;

begin

for Angle := 0 to 360 do begin

    SinCos(Angle*0.0174532925199433 ,Sinus, Cosin);

    MonSinInt[ Angle ] := Round( Sinus * 1000 );

    MonCosInt[ Angle ] := Round( Cosin * 1000 );

end;


...

for Angle := 0 to 360 do begin

X := MonCosInt[ Angle ] * Hypot div 1000;

Y := MonSinInt [ Angle ] * Hypot div 1000;

end;

Ratio de performance = 300 : 1 (Par rapport au premier exemple)


Parfois, avec cette technique, on aura une petite erreur d'une unité sur une coordonnée à cause de l’arrondi vers le bas de Div. Mais dans une animation sur l’écran cela passera inaperçu et de toute façon tout a un prix !



Rester vigilant à tout :


A votre avis, qu’est-ce qui ne colle pas dans le code suivant ?


Hypothenuse1 := Sqrt( Sqr(CoteA1) + Sqr(CoteB1) );

Hypothenuse2 := Sqrt( Sqr(CoteA2) + Sqr(CoteB2) );


if Hypothenuse1 < Hypothenuse2 then {Faire ceci};


C’est simplement que si Hypothenuse1 < Hypothenuse2, alors

CoteA1 + CoteB1 < CoteA2 + CoteB2

On peut s’épargner les coûteux Sqrt() et Sqr() avec simplement :


if CoteA1 + CoteB1 < CoteA2 + CoteB2 then {Faire ceci};

Ratio de performance = 10 : 1





« SCANLINE() TU UTILISERAS »


Si Canvas.Pixels[] peut être bien pratique pour connaître la couleur d’un pixel isolé, dans le cas où on travaille sur toute la surface d’un Bitmap, il faut utiliser Scanline[] qui est beaucoup plus performant. En effet, les goulets d’étranglement pour ce qui concerne les temps d’exécution se trouvent souvent dans les traitements de Bitmap.


    Je vous renvoie à l’excellente démonstration de Florenth qui semble avoir fait plusieurs fois le tour du problème ;)


http://www.delphifr.com/codes/BON-USAGE-SCANLINE_43222.aspx




« DES IDEES RECUES TU TE MEFIERAS »



Un exemple : le décalage :


On voit et on entend souvent (j’étais dans le lot) que les décalages binaires sont plus rapides pour remplacer une multiplication ou une division par 2, 4, 8, etc :


X := X shl 1

Y := Y shr 2


Pourtant les temps de calcul sont équivalents avec D7. Alors, préférez la lisibilité :


X := X * 2

Y := Y div 4

Ratio de performance = 1 : 1


J’ai demandé une explication à f0xi, pensant que le compilateur de Borland était vraiment excellent. Voici sa réponse :


« C'est surtout les CPU qui deviennent bons.

A l'époque des CPU monocore, faire un shr 1 ou shl 1 était plus rapide qu'une multiplication ou division. Maintenant avec les CPU dual, quad, octo-core, ça devient ridicule ce genre de petite optimisation.

[ ]

L'optimisation "locale" n'est plus vraiment une question à se poser. Aujourd'hui on parle plus d'optimisation "globale" de l'ensemble du programme et de ses éléments et également de plus travailler la méthodologie ; car si le compilo est capable d'optimiser certaines instructions, il n'est pas encore capable d'optimiser tout seul une routine. »





« L’OPTIMALISATION TU EVITERAS »


Optimisation, optimalisation, l’un des deux termes ne vous semble-t il pas démesuré ? ;)


Un logiciel est un fragile équilibre entre sa lisibilité, sa facilité de maintien, sa vitesse d’exécution, sa taille, sa justesse, etc.

Touchez à l’un de ces éléments et il est probable que l’équilibre sera rompu. Ce qui est sûr, c’est que l’optimisation de la vitesse rend souvent le code beaucoup moins lisible et moins facile à maintenir. Il faut donc toujours se demander ce qui est le plus important. Avec les machines modernes c’est finalement rarement la vitesse qui est un problème. De toute façon, ce sera à vous d’arbitrer ce dilemme.





Conclusion :



L’abus nuit en tout. Sachez utiliser ces techniques à bon escient. En cas d’optimisation de votre code, n’hésitez pas à commenter la technique que vous employez. En effet, si les techniques décrites ici étaient toujours ‘cleans’, elles seraient la norme en programmation, non ?


Il existe bien sûr des centaines d’autres techniques plus ou moins performantes qui vont du fait d’éviter les tableaux à plusieurs dimensions jusqu’à utiliser l’assembleur ( ainsi que l’a fait remarqué WHITEHIPPO, l’optimisation ultime restera quand même l'optimisation en assembleur, et je vous renvoie à ce site : http://www.fastcodeproject.org/ pour obtenir les meilleures routines ). Mais le but de ce tutoriel était de vous mettre sur de bons rails, non pas d’établir une liste exhaustive des techniques. J’espère être parvenu à vous donner quelques conseils généraux qui vous aideront.


Et quand vous serez parvenu à multiplier par 300 la vitesse d’exécution d’un code, vous serez content de ce que vous aurez fait à la seule force de nos petits neurones ! ^_^

C’est une des satisfactions qui font partie des joies de la programmation.






Je remercie ceux qui m’ont aidé ou inspiré ce tutoriel:


Steve McConnell (TOUT sur le CODE, en français chez Microsoft)

Olivier Dahan et Paul Toth (Delphi7 Studio chez Eyrolles

fOxi (Administrateur de CS et légèrement obsédé par les optimisations, graphiste oblige ;)

Florenth (Membre de CS, dont le dada est l’optimisation. On se demande d’ailleurs bien pourquoi ce n’est pas lui qui a écrit ce tuto ;)



Caribensila



signaler à un administrateur
Commentaire de f0xi le 21/10/2007 19:56:29 administrateur CS

MonCos : array[0..360] of Extended; // Tableau en cache.
MonSin : array[0..360] of Extended; // Tableau en cache.

pour ce genre de truc on peut faire :

type
  TSinCosElements = (mSin, mCos);
  TSinCosArray360s = array[TSinCosElements, 0..359] of single; // 2880 octets
                  // array[TSinCosElements, 0..359] of extended; // 7200 octets
                  // de plus le type extended n'est pas portable a 100%
                  // Single correspond a la norme IEEE 32bits float

const
  cDegToRad = 180/Pi; // l'utilisation de constante fait partis de l'optimisation


procedure PrecalcSCA(var SCA: TSinCosArray360);
// on appel ça au debut du programme et plus de soucis.
var n : integer;
    S,C: extended;
begin
  for N := 0 to 359 do
  begin
    SinCos(N * cDegToRad, S, C);
    SCA[mSin, N] := S;
    SCA[mCos, N] := C;
  end;
end;



for Angle := 0 to 359 do
begin
  Am := Angle mod 360; // empeche le debordement du tableau
  H1 := Hypot div 1000; // calculé une fois pour toute
  X  := MonCosInt[Am] * H1;
  Y  := MonSinInt[Am] * H1;
end;


avec les angles pour sin et cos : 0 = 360 et [0..179] = -[180..359]




Hypothenuse1 := Sqrt( Sqr(CoteA1) + Sqr(CoteB1) );
Hypothenuse2 := Sqrt( Sqr(CoteA2) + Sqr(CoteB2) );

n'est pas faux mais :

C = Sqrt((A*A) + (B*B)); // sera plus rapide si on veux calculer l'hypoténuse.

signaler à un administrateur
Commentaire de f0xi le 21/10/2007 21:20:09 administrateur CS



equivalent assembleur pour comprendre :


pour :

if (N >= 0) and (N < 5) then Categorie = 1;
if (N >= 5) and (N < 20) then Categorie = 2;
if (N >= 20) and (N < 101) then Categorie = 3;


equivaux a :

    @@cat1:
      test    eax, eax
      jl      @@cat2           // N >= 0 ?
      cmp     eax, +$05
      jnl     @@cat2           // N < 5 ?
      mov     edx, $00000001   // categorie = 1

    @@cat2:
      cmp     eax, +$05
      jl      @@cat3          // N >= 5 ?
      cmp     eax, +$14
      jnl     @@cat3          // N < 20 ?
      mov     edx, $00000002  // categorie = 2

    @@cat3:
      cmp     eax, +$14
      jl      @@fin           // N >= 20 ?
      cmp     eax, +$65
      jnl     @@fin           // N < 101 ?
      mov     edx, $00000003  // categorie = 3

    @@fin:
      mov     eax, edx
  end;

total : 16 instructions / 7 minimum / 10 moyenne / 14 maximum


pour :

if (N >= 0) and (N < 5) then Categorie = 1 else
if (N >= 5) and (N < 20) then Categorie = 2 else
if (N >= 20) and (N < 101) then Categorie = 3;

equivaux a :

    @@cat1:
      test    eax, eax
      jl      @@cat2         // N >= 0 ?
      cmp     eax, +$05
      jnl     @@cat2         // N < 5 ?
      mov     edx, $00000001 // categorie = 1
      jmp     @@fin          // fin

    @@cat2:
      cmp     eax, +$05
      jl      @@cat3         // N >= 5 ?
      cmp     eax, +$14
      jnl     @@cat3         // N < 20 ?
      mov     edx, $00000002 // categorie = 2
      jmp     @@fin          // fin

    @@cat3:
      cmp     eax, +$14
      jl      @@fin          // N >= 20 ?
      cmp     eax, +$65
      jnl     @@fin          // N < 101 ?
      mov     edx, $00000003 // categorie = 3

    @@fin:
      mov     eax, edx       // fin

total : 18 instructions / 7 minimum / 11 moyenne / 14 maximum / gain 0% et pourtant plus rapide!


pour :

  case N of
    0..4: categorie := 1;
    5..19: categorie := 2;
    20..101: categorie := 3;
  end;

equivaux a :

    sub     eax, +$05      // N = 0..4 ?
    jb      @@cat1         // oui > categorie = 1
    sub     eax, +$0F      // N = 5..19 ?
    jb      @@cat2         // oui > categorie = 2
    sub     eax, +$51      // N = 20..100 ?
    jb      @@cat3         // oui > categorie = 3
    jmp     @@fin          // fin
    @@cat1:
      mov     edx, $00000001 // categorie = 1
      jmp     @@fin          // fin

    @@cat2:
      mov     edx, $00000002 // categorie = 2
      jmp     @@fin          // fin

    @@cat3:
      mov     edx, $00000003 // categorie = 3

    @@fin :
      mov     eax, edx       // arret

total : 13 instructions / 5 minimum / 7 moyenne / 8 maximum / gain 40 a 50% !

signaler à un administrateur
Commentaire de Oniria le 22/10/2007 18:17:59

Bonjour,

Je trouve que ce tutoriel est excellent, il ouvre les yeux sur beaucoup de morceaux de code mal ficelé que l'on peux facilement faire.
Un grand bravo !

Juste un petit truc, sur

if (N>=0) and (N<5) then Categorie := 1

else if (N>=5) and (N<20) then Categorie := 2

else Categorie := 3;

On peut supprimer le (N>=5) car si N>=5, on passera à la deuxième ligne. On gagne encore une comparaison et le code devient

if (N>=0) and (N<5) then Categorie := 1

else if (N<20) then Categorie := 2

else Categorie := 3;

Oniria

signaler à un administrateur
Commentaire de f0xi le 22/10/2007 20:44:50 administrateur CS

c'est bien Oniria en effet car ça donne :


    @@cat2:
      cmp     eax, +$19
      jnl     @@cat3         // N < 20 ?
      mov     edx, $00000002 // categorie = 2
      jmp     @@fin          // fin

    @@cat3:
      cmp     eax, +$79
      jnl     @@fin          // N < 101 ?
      mov     edx, $00000003 // categorie = 3

soit 4 instructions en moins

bien vus.

signaler à un administrateur
Commentaire de Caribensila le 22/10/2007 22:27:22

J'avoue que je m'attendais à des tentatives d'optimisation de ce tuto    lol

Mais je ne suis vraiment pas déçu par la qualité de vos commentaires. On peut dire qu'ils sont parties intrinsèques du tuto et qu'ils en sont la postface.

Grand merci à vous.


PS: On se prend vite au jeu de l'optimisation, hein?  ;)

signaler à un administrateur
Commentaire de MAURICIO le 25/10/2007 15:46:59

Salut Cari!
super, même moi j' ai appris quelques trucs:
- Vitesse integer Vs SmallInt même si l' integer bouffe plus de mémoire, surtout dans un array !!!
- le Case Of qu' on peut booster.

Juste un truc quand même, tu l' expliques très bien: quelques fois il est plus interessant de garder le source de façon compréhensible plutôt que de vouloir gagner 1/1000 de secondes, tout dépend de la nature des testes à faire dans les if etc ...

Exemple:
(not A) and (not B)
not (A or B)

Il est quand même plus simple de comprendre la 1ere   lol

Avec ça, nos utilisateurs n' auront plus le temps de cligner des yeux ou d' aller aux toilettes!

Impecc en tout cas,
A+

signaler à un administrateur
Commentaire de WhiteHippo le 25/10/2007 19:41:39

Hello

« OPTIMISATION TROP RAPIDE, TU EVITERAS » et « TUTORIAL TU CORRIGERAS » :P

[Citation]
Un autre exemple :
  if (N>=0) and (N<5) then Categorie := 1;
  if (N>=5) and (N<20) then Categorie := 2;
  if (N>=20) and (N<101) then Categorie := 3;
  
Et ce même code partiellement optimisé :
  if (N>=20) then Categorie := 3 //On sort dès que le résultat est connu.
  else if (N>=5) and (N<20) then Categorie := 2//On sort dès que le résultat est connu.
  else Categorie := 1;

[/Citation]

Attention !! si N=102 et plus, la catégorie sera égale à 3 alors qu'elle ne le devrait pas (selon la premiere portion de code). Idem pour les deux optimisations suivantes.

P.S. Merci à toi d'avoir pris le temps d'écrire ce tutorial Caribensila, dès a présent une référence en la matière.

Cordialement.

signaler à un administrateur
Commentaire de WhiteHippo le 25/10/2007 20:01:13

Ne pas oublier que pour les optimisations l'optimisation ultime reste quand même l'optimisation en assembleur.

N.B. On pourra notamment aller sur le site http://www.fastcode.dk/ pour obtenir les meilleures routines écrites en BASM.

Cordialement.

signaler à un administrateur
Commentaire de Jean_Jean le 26/10/2007 23:59:11

Salut Cari,

Je découvre ton tuto ce soir, sympa et instructif.
Je venais juste de faire une remarque sur un post à propos des types extended que l'on utilise trop souvent! Car il vaut mieux parfois plus de chiffres significatifs qu'un domaine étendu (exposant puissance).
Je suis allé sur http://www.fastcode.dk/ dont parle WhiteHippo. Je pense que c'est utile en cas de process très répétiif d'appel de fonctions. Par exemple, j'ai comparé les fonctions proposés pour l'ArcCos, on gagne un ns seconde par rapport à celle de l'unité Maths. Je crois donc surtout que l'optimisation vient surtout des bonnes pratiques de programmation.
Ce tuto va bien dans ce sens.
Merci
Jean_Jean  

signaler à un administrateur
Commentaire de Albedo039 le 29/10/2007 08:28:01

Salut,

Pour:
  if (N>=0) and (N<5) then Categorie := 1;
  if (N>=5) and (N<20) then Categorie := 2;
  if (N>=20) and (N<101) then Categorie := 3;

On peut par ex.:
Categorie := Ord(n<5) + 2*Ord(n<20) + 3*Ord(n<101);

Je n'ai pas testé "en live"... mais qd même: ça évite tous les sauts conditionnels :)

Si on utilise un array en plus, possibilité de simplification (toujours pas testée en live):
Multiplic: array [False..True, 1..3] of integer;
Multiplic[False,1]:=0;
Multiplic[False,2]:=0;
Multiplic[False,3]:=0;
Multiplic[True,1]:=1;
Multiplic[True,2]:=2;
Multiplic[True,3]:=3;
Categorie := Multiplic[n<5,1] + Multiplic[n<20,2] + Multiplic[n<101,3]; (* !!! *)

Bon: voila que je n'ai plus que 3 additions maintenant :)

signaler à un administrateur
Commentaire de Albedo039 le 29/10/2007 08:43:57

** Attention - Sorry **

Categorie := Ord(n<5) + 2*Ord(n<20) + 3*Ord(n<101);

est bien sûr faux !!!!  (:smiley-honteux:)


Je voulais bien sûr dire:
   Categorie := Ord(n>=0) + Ord(n>=5) + Ord(n>=20);

ou encore
   Categorie := 3*Ord(n<101) - Ord(n<20) - Ord(n<5) ;
  

Note 1:
l'array peut rester une technique interessante pour des test plus complexes.
C'est juste une question de "reconnaissance d'algo".

Note2:
voila ce que c'est que de poster un commentaire d'un café internet ;)
Dommage qu'on ne puisse pas éditer ses propres "bévues" .

signaler à un administrateur
Commentaire de Caribensila le 29/10/2007 13:35:06

@ ALBEDO039 :
Bien vu !
"Categorie := Ord(n>=0) + Ord(n>=5) + Ord(n>=20);"
fait passer le ration de performance de 2:1 avec le Case Of à 2.4:1 !!!
Mais la lisibilité devient... fumeuse, comme dirait MAURICIO  lol

Par contre :
Categorie := 3*Ord(n<101) - Ord(n<20) - Ord(n<5) ;
est sérieusement handicapé par la multiplication  ;)
Et ton array n'apporte rien dans ce cas. Il reste en effet toujours 3 additions.

J'ai essayé :
for i := 0 to 4    do TabCat[i] := 1;
for i := 5 to 19   do TabCat[i] := 2;
for i := 20 to 100 do TabCat[i] := 3;
et
Categorie := TabCat[N];
Mais on reste à 2.4:1
L'accés au tableau est donc trop pénalisant ici.


@ WHITEHIPPO
J'ai corrigé mon tuto avec :
N := Random(101);    :p   ;)

A ce propos, je ferai une MAJ avec toutes vos bonnes idées plus tard. Pour le moment un bug rend les MAJ impossibles sauf à demander à un administrateur (merci PCPT). Et dans ce cas l'Historique n'est pas complété.

signaler à un administrateur
Commentaire de khawarizm le 29/10/2007 19:53:25

Merci cari ; ce n'est qu'aujourd'hui que je vois ton tuto et je le trouve super bien et très intéressant, ça fait un moment que je voulais  te poser quelques question pour pouvoir améliorer un de mes code et voilà que tu  nous (nous= moi et les débutants comme moi) surpris avec ce cadeau qui , effectivement , nous montre les bonnes pratiques , merci encor et STP ne soit pas « radin «  de ton savoir faire et initier nous aux joies de la programmation.

signaler à un administrateur
Commentaire de Albedo039 le 30/10/2007 10:26:27

Encore une p'tite chose à propos de:

---------------------------------------------
--- for i := 1 to 10 do MaTable[ i ] :=0; ---
---------------------------------------------

Au lieu de faire du "ligne à ligne" comme proposé... on peut entre autre:
1) utiliser "Fillchar(...);"
2) utiliser "ZeroMemory( MaTable, SizeOf(MaTable) );"

à prendre en compte aussi dans l'exemple "Déroulez les boucles".

---------------------------------------------
--- (not A) and (not B) vs. not (A or B) ----
---------------------------------------------
# si le test est répété plusieurs fois
# si "(not A)" est souvent = false
# et si l'évaluation des expr. bool n'est pas configurée "évaluation complète"
alors il vaut mieux la première version

---------------------------------------------
--- if (N<5) then Categorie := 1          ---
--- else if N<20 then Categorie := 2      ---
--- else Categorie := 3;                  ---
---------------------------------------------
As-tu essayé cette possibilité? :

ND := N Div 5;
Categorie:=1;
If ND>3 then Categorie:=3 else if ND>0 then Categorie:=2;

(*
ND = 0 si N in [0..4]
ND = 1, 2 ou 3 si N in [5..19]
ND = 4 ou plus si N in [20....]
*)

signaler à un administrateur
Commentaire de Kenavo le 30/10/2007 22:22:36

Mon Dieu ! Quelle fôte !
----------------------------------------------------------------
A votre avis, qu'est-ce qui ne colle pas dans le code suivant ?


Hypothenuse1 := Sqrt( Sqr(CoteA1) + Sqr(CoteB1) );

Hypothenuse2 := Sqrt( Sqr(CoteA2) + Sqr(CoteB2) );


if Hypothenuse1 < Hypothenuse2 then {Faire ceci};


C'est simplement que si Hypothenuse1 < Hypothenuse2, alors

CoteA1 + CoteB1 < CoteA2 + CoteB2

On peut s'épargner les coûteux Sqrt() et Sqr() avec simplement :


if CoteA1 + CoteB1 < CoteA2 + CoteB2 then {Faire ceci};
--------------------------------------------------------------

Ben non ! Par exemple :

si
  CoteA1 = 1
  CoteB1 = 10

  CoteA2 = 7
  CoteB2 = 7

alors
  (Hypothenuse1)² = 101
  (Hypothenuse2)² = 98
  donc  :Hypothenuse1 > Hypothenuse2

alors que
  CoteA1 + CoteB1 = 11
  CoteA2 + CoteB2 = 14
  donc : (CoteA1 + CoteB1) < (CoteA2 + CoteB2)

Si on peut s'épargner le Sqrt, le Sqr reste bien utile !
Sinon, pour le rotations, le Scanline, etc ... je souscris totalement ...

Bien à toi, Cari

Kenavo

  

signaler à un administrateur
Commentaire de florenth le 02/11/2007 18:53:05

Je viens de découvrir ce tuto, et c'est une vraie anthologie a garder sous le coude.

Mais justement, faut faire gaffe quand on optimise :
- D'une part on perd en lisibilité
- On rend le code moins maintenable
- Souvent, ça améliore pas grand chose niveau vitesse (genre 25 millisecondes au lieu de 30), [sauf si c'est dans une boucle bien sûr^^]

- Et d'autre part, on n'est pas à l'abri de conn***** comme celle que te signale Kenavo !

Pour compléter le tuto, je rajouterai que la dérécursification des procédures récursives aide grandement à gagner de la vitesse.
Exemple: la fonction de Fibonacci:

function Fibo(X: Integer): Integer;
begin
  if (X = 0) or (X = 1) then
    Result := 1
  else
    Result := Fibo(X - 1) + Fibo(X - 2);
end;

Elle s'optimise largement ainsi :

function Fibo2(X: Integer): Integer;
var
  I: Integer;
  Pred, PredPred: Integer;
begin
  Pred := 1;
  PredPred := 1;
  for I := 2 to X do
  begin
    Result := Pred + PredPred;
    PredPred := Pred;
    Pred := Result;
  end;
end;

Cette dernière fonction est infiniment plus rapide que l'autre. (pour calculer le 40 ème terme, il lui faut 0ms contre 400ms pour la version récursive)

Enfin:
-------------------------
fOxi (Administrateur de CS et légèrement obsédé par les optimisations, graphiste oblige ;)
Florenth (Membre de CS, dont le dada est l'optimisation. On se demande d'ailleurs bien pourquoi ce n'est pas lui qui a écrit ce tuto ;)
--------------------------

Ce n'est pas moi le fada de l'opti mais f0xi !!!
Et si je n'ai pas écrit ce tuto, c'est justement car tu as été plus rapide que moi, t'as un cerveau plus optimisé... :-)

signaler à un administrateur
Commentaire de Caribensila le 03/11/2007 15:34:03

@Flo

« Et d'autre part, on n'est pas à l'abri de conn***** comme celle que te signale Kenavo !»

Wouah! J'sens que ça va me poursuivre pendant 107 ans, le truc du 1² ! (surtout si Francky tombe la-dessus, j'ai pas fini de l'entendre).
De toute façon, je m'autopardonne passe que j'ai pas allé à l'école quand j'étais petit et que je suis autodidacte (surtout auto, d'ailleurs).
J'avais déjà remarqué l'avatar de Kenavo: 1 oeil scrutateur, l'autre inquisiteur...
Et je dirai même plus! Un mec qui se balade avec 1,7182818285¤ en poche, on voit tout de suite que c'est un adepte du coupagedecheveuxenquatrisme ( http://www.delphifr.com/codes/REELS-REALITE_44550.aspx ). Mais, bon... Je corrigerai quand même ma bourde à la prochaine màj.


« je rajouterai que la dérécursification des procédures récursives aide grandement à gagner de la vitesse »

Et moi qui me suis souvent cassé la nenette à récursifier des fonctions en espérant optimiser!!!
Mais, tout bien réfléchi, c'est logique...
"Pour comprendre le principe de récursivité, il faut d'abord comprendre le principe de récursivité". C'est sûr... Mais on peut quand même se demander à quoi ça sert et pourquoi on enseigne la récursivité aux étudiants ingénieurs de 1ère année...


« Ce n'est pas moi le fada de l'opti mais f0xi !!! »

Vous êtes deux fadas, oui!
Yaka regarder le forum:
http://www.delphifr.com/infomsg_COMMENT-CALCULER-RATIO-PERFORMANCE-SELON-TUTORIAL-CARI_1029837.aspx
'manque plus que Albedo039 pour compléter l'équipe...


« t'as un cerveau plus optimisé... :-) »

Récursifiez-vous au début de ce post pour bien comprendre que ce n'est pas exact.


Bon, je n'ai plus qu'à me taper une sacrée MAJ, moi!


PS: Merci à Flo de nous consacrer un peu de ton temps en cette année de Bac.  :)))

signaler à un administrateur
Commentaire de florenth le 03/11/2007 20:41:06

Mais non, ça arrive à tout le monde de se tromper...
Et figure toi que si je prends la peine de le mentionner, ce n'est pas pour t'enfoncer, bien au contraire, mais parce que j'ai récemment fait une boulette semblable.
Eh ben je t'assure que j'en ai mis du temps à comprendre, surtout qu'un code optimisé est souvent un peu moins lisible que son homologue lent.

Et ouais, Kenavo, c'est un inquisiteur. Pas rien qu'avec ses 1.7182 ¤ en poche, mais avec tous ces trucs de webcam à manivelle et bidules de résistances... brrr, je me demande à quels genre de torture on a le droit ! ^^

Sinon, ben je dois me résoudre à avouer que je suis (un peu) fada d'optimisation. Mais c'est vraiment parce que tu me récursifie l'affaire !!!
Mais tu comprends, une des clés de la bonne réussite au bac, c'est la rapidité d'esprit. Et qui dit rapidité, dit optimisation... tiens, voila une bonne excuse pour coder au lieu de philosopher...
Nan, je peux pas, (je fais croire que) je suis un élève sérieux !! ;-)

Allez, en bonus, tu as le droit à un test de performance entre entiers, flottants, et tout le toutim:
http://www.delphifr.com/codes/MESURER-PERFORMANCES-VOS-FONCTIONS-ANAYLSE-DIFFERENTES-OPERATIONS-SUR_44599.aspx

signaler à un administrateur
Commentaire de Delphiprog le 06/11/2007 22:43:18 administrateur CS

Que dire ?
Chapeau bas Monsieur Cari pour t'être lancé dans cette aventure et avoir placé la barre très haut pour les suivants.
Dans les idées reçues en matière d'optimisation, je n'ai pas vu le réflexe stupide qui consiste à déclarer des identifiants les plus courts possibles. C'est vrai qu'avec un langage compilé, cela n'a pas d'importance. Ce qui m'amène à faire remarquer que l'essentiel des bonnes pratiques ci-dessus s'applique avant tout aux langages compilés.
Alors, à quand un didacticiel consacré aux langages interprétés ?

J'aurais aussi aimé voir un paragraphe sur la manière de transmettre des arguments aux fonctions et procédures. Combien de fois on voit des codes sources avec passage par copie au lieu de transmettre par adresse. Cela fait aussi partie des bons réflexes à adopter.

Enfin, bref, un excellent tuto à garder préciseusement et à relire tous les matins.
Excellent travail.

signaler à un administrateur
Commentaire de Kenavo le 08/11/2007 21:09:20

"Pour comprendre le principe de récursivité, il faut d'abord comprendre le principe de récursivité". C'est sûr... Mais on peut quand même se demander à quoi ça sert et pourquoi on enseigne la récursivité aux étudiants ingénieurs de 1ère année...

On l'enseigne parce que c'est une base de la réflexion mathématique, comme le calcul matriciel, mais ça reste de la théorie qui parfois peut s'appliquer (voir la procedure de tri QuickSort). Par contre, pour l'efficacité du code, on est souvent obligé de faire appel à des méthodes numériques plus efficaces ...
Par exemple, pour calculer sur les matrices (determinant, réduction ...) on apprend les méthodes théhoriques qui sont récursives : pour une matrice carrée d'ordre 3, calcul de 3 matrices d'ordre 2 ; pour matrice d'ordre 4 : 4 matrices d'ordre 3 ; etc ... Ce qui pour une matrice carrée d'ordre n donne en gros n! (factorielle n) calculs sur des matrices d'ordre 2. Infaisable dès qu'on manipule des matrices assez grandes. On utilise donc des méthodes moins "élégantes" mais plus rapides, sans lesquelles des applications comme le scanner ou l'IRM par exemple, n'auraient pas été possibles.  
Mais toutes ces méthodes numériques, pour qu'elles soient bien appliquées, ne doivent pas empêcher de connaître la théorie.

Et comme dirait Jappe Volfoni : "Moi quand on m'en fait trop j'correctionne plus, j'dynamite... j'disperse... j'ventile... je dérécurcifie..."  

Et qu'est ce qu'ils ont mes yeux ?

signaler à un administrateur
Commentaire de DeltaFX le 04/12/2007 15:29:51

Yo,

A propos de ce bout de code là :

if (N>=0) and (N<5) then Categorie := 1

else if (N<20) then Categorie := 2

else Categorie := 3;

Est- ce qu'on gagne en l'écrivant comme ça :

...
Categorie := 3;
if (N>=0) and (N<5) then Categorie := 1
else if (N<20) then Categorie := 2;
...

ça fait un branchement de moins, non ?

signaler à un administrateur
Commentaire de BruNews le 01/02/2008 14:15:42 administrateur CS

DES IDEES RECUES TU TE MEFIERAS
décalage au lieu de division, ABSOLUMENT et TOUJOURS.
Que vous obteniez des temps identiques en écrivant l'un ou l'autre n'est du qu'au fait que vous avez un compilo correct qui fait le remplacement de la div par le shift.

Ce n'est bien entendu pas en comparant les perfs d'un listing de code avec un autre qu'on peut s'en apercevoir mais en ASM, là précisément où le compilo ne peut rien changer.
Voici listing de test, fait sur VC++ mais on s'en fout, c'est pur asm.

http://brunews.com/Decal.zip

__declspec(naked) char* __fastcall bnultoa(unsigned int dwnum, char* szdst)
{ // ECX = dwnum, EDX = szdst
  __asm {
    test     ecx, ecx
    jnz      short L1
    lea      eax, [edx+1]
    mov      byte ptr[edx], 48
    mov      byte ptr[eax], cl
    ret      0
L1:
    mov      [esp-4], edi
    mov      [esp-8], edx
    mov      edi, edx
L2:
    mov      eax, -858993459
    mul      ecx
    mov      eax, edx
    shr      eax, 3
    mov      edx, ecx
    lea      ecx, [eax+eax*8]
    add      ecx, eax
    sub      edx, ecx
    add      dl, 48
    mov      [edi], dl
    mov      ecx, eax
    add      edi, 1
    test     eax, eax
    jnz      short L2
    mov      byte ptr[edi], al
    mov      [esp-12], edi
    mov      eax, [esp-8]
L3:
    sub      edi, 1
    mov      dl, [eax]
    mov      cl, [edi]
    mov      [edi], dl
    mov      [eax], cl
    add      eax, 1
    cmp      eax, edi
    jb       short L3
    mov      eax, [esp-12]
    mov      edi, [esp-4]
    ret      0
  }
}

#pragma comment(linker, "/entry:myWinMain")
__declspec(naked) void __stdcall myWinMain()
{
  __asm {
    lea     edi, [esp-32]
    mov     ebx, 1000
    or      eax, -1
    mov     ecx, 2
    mov     esp, edi
    rdtsc
    mov     esi, eax
    xor     edx, edx
goDIV:
    div     ecx
    sub     ebx, 1
    jne     short goDIV
    rdtsc
    sub     eax, esi
    mov     edx, edi
    mov     ecx, eax
    call    bnultoa
    
    or      eax, -1
    mov     ebx, 1000
    rdtsc
    mov     esi, eax
goSHIFT:
    shr     eax, 1
    sub     ebx, 1
    jne     short goSHIFT
    rdtsc
    sub     eax, esi
    lea     edx, [edi+16]
    mov     ecx, eax
    call    bnultoa
    
    lea     edx, [edi+16]
    push    0
    push    edx
    push    edi
    push    0
    call    dword ptr MessageBox
    add     esp, 32
    push    0
    call    dword ptr ExitProcess
  }
}

RESULTAS DE 5 TESTS CONSECUTIFS:
shr 1 div 2
2967 79220
1811 76007
1811 76007
1819 76007
1810 76016
c'est sans appel, la division reste 40 fois plus lente que le shift, comme d'ailleurs on peut le voir dans les manuels Intel.
Fait sur BIXEON 64, 3.4 Ghz (pas une antiquité).

Plaisanterie ON:
Pour f0xi, lui dire que c'est heureux que les CPUs deviennent bons mais qu'il ferait bien de les suivre.
Bah finalement je lui dirai moi même ce soir sur msn.
Plaisanterie OFF.

signaler à un administrateur
Commentaire de matrix1 le 03/02/2008 10:14:47

Avec le metriel qui est disponible! tous ça reste quelque pédagogique.

signaler à un administrateur
Commentaire de matrix1 le 03/02/2008 10:17:02

tous ça est pédagogiques*

signaler à un administrateur
Commentaire de BruNews le 04/02/2008 13:52:37 administrateur CS

Matrix1 > Il ne faut surtout pas raisonner ainsi sinon tu es mûr pour .NET, JAVA ou tout autre interprété profond. Serait inutile de rester sur un langage permettant au développeur de s'exprimer si c'est pour envoyer l'utilisateur prendre un café à la moindre routine lancée.
Rassurons de suite tout le monde: du taf exigeant des perfs ça existe. Certes c'est du travail de niche mais pas pour demain de le voir disparaitre. Bien plus que pédagogique, ce genre de tuto est une piqûre de rappel aux bonnes méthodes qu'il serait bon d'avoir plus souvent sous les yeux.
Kenavo >
QuickSort est le type même du tri qu'on met en oeuvre quand on veut des perfs et c'est donc justement le cas où la récursivité est à exclure.
EX: http://brunews.com/SortInt.zip
; QueryPerformanceCounter(&liDeb);
; QuickSort(pmem, nelems);
; QueryPerformanceCounter(&liFin);
Regarde en combien ça se fait sans récursivité. Noter que j'ai bloqué l'affichage, inutile ici.

signaler à un administrateur
Commentaire de Caribensila le 04/02/2008 15:16:08

« QuickSort est le type même du tri qu'on met en oeuvre quand on veut des perfs et c'est donc justement le cas où la récursivité est à exclure. »

C'est justement en tentant de débrouiller en dilettante le challenge de Brunews :
http://www.delphifr.com/infomsg_COMPARATIFS-PERFS_1061255.aspx?p=4
que je me suis un peu intéressé aux méthodes de tri rapides. Et j'ai été étonné de trouver de nombreux exemples de QuickSort sur Internet utilisant la récursivité. C'est vrai que ça semble un peu absurde vu le but recherché. Mais reconnaissons que la récursivité apporte une touche d'élégance et de professionnalisme à la plus rébarbative des routines.

Tout cela pour dire que c'est Florenth qui nous signalait ici la mollesse de la récursivité et qu'il a gagné le droit de retoucher son excellente source :
http://files.codes-sources.com/fichier.aspx?id=34509&f=Sorts.pas

Et espérons que Kenavo a évité le piège du QuickSort récursif dans sa participation aux COMPARATIFS-PERFS.   lolll

signaler à un administrateur
Commentaire de Kenavo le 08/02/2008 18:48:58

"Et espérons que Kenavo a évité le piège du QuickSort récursif dans sa participation aux COMPARATIFS-PERFS. "
Ben non ! En plein dedans !

J'ai jeté un oeuil sur les sources de Brunews, mais là, c'est moi qui ne suis point optimisé pour lire le C !!! J'ai toujours la fâcheuse impression d'avoir une police exotique où manquent toutes les lettres de l'alphabet !
C'est étonnant, ce besoin de ne pas faire de phrases !

signaler à un administrateur
Commentaire de Caribensila le 08/02/2008 19:59:20

'faut dire qu'y'a le C... Et y'a le C par Brunews.
Et c'est tomber de Charybde en Scylla pour un delphiste. lolll

QuickSort sans récursivité, très bien commenté et expliqué:

http://delphi.developpez.com/sources/?page=sec_math_algo#JFS_Quicksort

signaler à un administrateur
Commentaire de BruNews le 08/02/2008 23:37:08 administrateur CS

Mon clavier est aussi usé que moi, pour ça que je ne fais que C ou ASM, peu de touches suffisent.

Les alertes mail sur les tutos sont encore en rade mais j'ai mis dans la todo list de Nix, espérons.

signaler à un administrateur
Commentaire de matrix1 le 26/02/2008 16:57:40

BruNews> tu ma pas compris,premièrement c un trés bon tuto et j'admire ce que à fait Caribensila c quasiment de bon travail qui mérite bc d'applaudissement car c vrais, c choses se font rare de nos jours, mais ce que je voulais dire c que le pratique est tj <> de la théorie, tu c tous ça mais qu'on ton travail sur un projet OOP comme le delphi par exemple, tu ne peut pas faire ça
MaTable[1]  := 0;
MaTable[2] := 0;
MaTable[3] := 0;
MaTable[4] := 0;
MaTable[5] := 0;
MaTable[6] := 0;
MaTable[7] := 0;
MaTable[8] := 0;
MaTable[9] := 0;
MaTable[10] := 0; + si j'essayerais d'apprendre le java en + alors ta pas le temps surtout si tu sais bien que l'utilisateur de ton logiciel a des pc un peut performants, tu ton fiche carrément .
et personnellement j'aime pas le .net ni le java je préfère le TCL/TK ou le python que ces ramasseurs de miettes :D

signaler à un administrateur
Commentaire de Caribensila le 29/02/2008 23:41:37

@MATRIX
Je n'ai jamais dit qu'il fallait dérouler les boucles!
Ce n'était qu'un exemple pour illustrer qu'un code court n'est pas toujours le plus performant en vitesse d'exécution.

Et si tu ne prends pas le temps de faire des applications performantes en temps d'exécution, ce seront les utilisateurs qui perdront leur temps à attendre le résultat d'un traitement un peu gourmand. C'est un choix. Mais un programme rapide n'a jamais été flashé pour excès de vitesse, et la route est encore longue.  ;)

signaler à un administrateur
Commentaire de acanicio le 28/03/2008 10:07:43

Salut,

Tout d'abord felicitations à Caribensila pour ce tutoriel très efficace !

Ayant connu delphi depuis sa premiere version, j'ai une petite amélioration à proposer à ton exemple de calculs d'angles avec les entiers, elle est aussi très "subtile".
Je suis d'accord avec toi dans le fait que le SHR et le DIV ont à peu près la même vitesse d'execution. Mais le top c'est d'essayer de toujours penser en "binaire", et pas en "décimal".

Ton exemple :

<<
for Angle := 0 to 360 do begin

    SinCos(Angle*0.0174532925199433 ,Sinus, Cosin);

    MonSinInt[ Angle ] := Round( Sinus * 1000 );

    MonCosInt[ Angle ] := Round( Cosin * 1000 );

end;




...

for Angle := 0 to 360 do begin

X := MonCosInt[ Angle ] * Hypot div 1000;

Y := MonSinInt [ Angle ] * Hypot div 1000;

end;

Ratio de performance = 300 : 1 (Par rapport au premier exemple)
>>
Peut être très fortement amélioré en remplaçant le diviseur 1000 par 1024 ou toute autre valeur multiple de 2.

On obtient donc :
<<
for Angle := 0 to 360 do begin

    SinCos(Angle*0.0174532925199433 ,Sinus, Cosin);

    MonSinInt[ Angle ] := Round( Sinus * 1024 );

    MonCosInt[ Angle ] := Round( Cosin * 1024 );

end;




...

for Angle := 0 to 360 do begin

X := MonCosInt[ Angle ] * Hypot div 1024 ;

Y := MonSinInt [ Angle ] * Hypot div 1024 ;

end;
>>

Ratio de performance = 4,5 : 1 (Par rapport à ton propre exemple) Donc 1350:1 par rapport au premier...

Si on travaille avec des entiers, et qu'on a besoin de précision, on se fout que cette précision soit en puissance 10 ou en puissance 2... Et ça fait une sacrée différence !!!

;-)

Cordialement
Axel

signaler à un administrateur
Commentaire de Caribensila le 28/03/2008 14:15:48

Whaooo!
Méthode "subtile, harmonieuse et efficace"!
Grand merci.

signaler à un administrateur
Commentaire de acanicio le 28/03/2008 14:58:36

Merci !

Mais je viens de refaire un truc, en remplaçant le DIV 1024 par un SHR 10, et je gagne encore un facteur de 1.3 par rapport à mon résultat précédent.
Comme on travaille réellement sur un multiple de 2...

Mais je bosse avec Delphi 5... peut être que sous D7 c'est différent.

Axel

signaler à un administrateur
Commentaire de BruNews le 30/03/2008 15:55:37 administrateur CS

RECTIF:
un shift remplace un DIV ou MUL en cas de PUISSANCE de 2 et non multiple de 2.

signaler à un administrateur
Commentaire de acanicio le 31/03/2008 12:01:10

T'as raison, merci, je me suis trompé dans le terme, il s'agit bien d'une puissance de 2.

Axel

signaler à un administrateur
Commentaire de JulioDelphi le 10/05/2008 18:45:07 administrateur CS

Salut cari,
on peut aussi parler de ce genre d'optimisation :

J'ajoute dans une ListBox 100000 chiffres en random 100, je vais faire un bête :
"
var i: integer; begin for i:=0 to 99999 do listbox1.Items.Add(IntToStr(Random(100))); end;
"
et je vais mettre (chez moi) entre 7 et 8 secondes.
mais si je fais plutot ça :
"
var
i: integer;
temp : TStringlist;
begin
temp := tstringlist.Create;
for i:=0 to 99999 do
  temp.Add(IntToStr(Random(100)));
listbox1.Items.Text := temp.Text;
temp.free;
end;
"
je le fais en moins d'une seconde. Si c'est pas de l'optimisation, je sais pas ce que c'est.
Il faudrait voir au niveau de la mémoire mangée, je pense que ma méthode en mange un peu plus, mais j'ai pas que 2mo de ram chez moi donc je pense que ça passe un peu partout ;)

moralité : n'affichez pas ce ue vous insérez dans une liste visuelle si vous etes dans une boucle, gardez le tout en mémoire et insérez ensuite le tout dans la liste d'un coup.

signaler à un administrateur
Commentaire de f0xi le 15/05/2008 19:53:42 administrateur CS

@Julio

ListBox1.Items.BeginUpdate;
try
  for i:=0 to 99999 do
    listbox1.Items.Add(IntToStr(Random(100)));
finally
  ListBox1.Items.EndUpdate;
end;

parfois on doit preserver la memoire.

sinon autant faire :

var S : string;
...
for i := 0 to 99999 do
  S := S + IntToStr(Random(100))+#13#10;
ListBox1.Items.Text := S;

ce qui ne sera pas forcement plus rapide.


signaler à un administrateur
Commentaire de saidbelhaj le 23/05/2008 12:46:16

Bravo!

signaler à un administrateur
Commentaire de Scaythe le 14/11/2008 09:06:18

Merci pr ce tuto ( oui, un merci de plus)

N := Random(101);
if (N<5) then Categorie := 1
else if N<20 then Categorie := 2
else Categorie := 3;

Je vais essayer une petite optimisation alors :

Categorie :=3;
if ( N<20)
   then
{  Categorie :=2
    if ( N <5 ) Categorie :=1
}

Mon raisonnement : je n'optimise pas pour 1 traitement mais pour n traitements
Théoriquement 80% de nos traitements ne devraient jamais rentrer dans le premier if
J'appellerais cela " L' Optimisation par le context" :D ... un genre d'optimisation a ne pas oublier ??



signaler à un administrateur
Commentaire de kelloucheaeh le 24/03/2009 14:09:05

que dire de plus, tout ces conseils et gratuitement grand merci à toi maître.

une chose, si seulement tout les bons commentaires trouvés leur place dans la version 2 du tuto alors se serait génial.

Ajouter un commentaire



Nos sponsors

Sondage...

CalendriCode

Juillet 2009
LMMJVSD
  12345
6789101112
13141516171819
20212223242526
2728293031  

Consulter la suite du CalendriCode

Comparez les prix Nouvelle version

Photothèque Nouveau !



Développement réalisé par Nicolas SOREL (Nix) avec l'aide de : Cyril DURAND et Emmanuel (EBArtSoft), Merci à Vincent pour ses précieux conseils
CodeS-SourceS.com© Toute reproduction même partielle est interdite sauf accord écrit du Webmaster
CodeS-SourceS.com© est une marque déposée tous droits réservés
Temps d'éxécution de la page : 0,031 sec

Google Coop CodeS-SourceS Google Coop CodeS-SourceS


Certaines images présentes sur le site (notament certains avatars) sont issues des collections IconShock, donc si vous souhaitez utiliser ces icons vous devez les acheter, ne les copiez pas et ne utilisez pas dans vos sites et applications sans les avoir commandé.