C++ optimisation de code

Bonjour,

Je cherche des ressources (lien etc…) sur comment optimiser son code?
Par exemple je vois souvent des operations genre << ou & sur des int pour faire des opérations.
J’aimerai apprendre ce genre de choses.

Merci

Bon un premier exemple concret :
quel est le plus rapide ?
int a

a76
ou
a<<2 + a<<3 + a<<6 (ce qui doit faire a
64 + a4 + a8 si j’ai bien compris)

En attendant d’avoir des sources, je fait des essais par moi meme, et un truc bizzare :


Solution 1
Declarer un tableau
float ** tab1
flaot **tab2
allouer la memoire
appeller une fonction déclarée comme ceci:
mafonction(float** tableau1,float** tableau2){
faire des operations sur ces tableaux
}
}

--------------

Solution 2
float tab1[hauteur][largeur]
float tab2[hauteur][largeur]
appeller une fonction déclarée comme ceci:
mafonction2(float tableau1[hauteur][largeur],float tableau2[hauteur][largeur]){
faire les meme operations sur ces tableaux
}

Et bien, curieusement si je mesure le temps d’execution de mafonction 1 et 2 (donc je ne prends pas en compte l’allocation mémoire), la solution 2 est plus rapide.

Pourtant en cherchant dans le forum clubic je suis tombé sur un post de Sans-Nom qui disais de toujours faire la solution 1 pour utiliser des tableaux en C++.

Je ne sais plus si en passant un tableau en paramètre, on ne travaille pas sur une copie du tableau ?

La solution 1c’est sûr qu’on travaille à la même adresse que tab1 et tab2, mais j’ai une doute pour la 2.

Non meme sur la solution 2 mon tableau est modifié après l’appel de la fonction…

Dans le second cas, tu passes par copie le tableau.

Si tu fais ça : void foobar(double a[5][5], double b[5][5]);

a et b sont passés en copie (il crée un tableau de 5x5 double), même si tu as en entrée foobar(new double[5]5, new double[5]5);

Pour t’en convaincre:

void foobar(double a[5][5], int x) {std::cout << ((int) &x - (int) &a) << std::endl;}
void foobar(double** a, int x) {std::cout << ((int) &x - (int) &a)<< std::endl;}

(j’ai fait brutal pour la taille, c’est sizeof(a) normalement, mais je suis pas sûr que ça t’affiche bien 55sizeof(double).

Pour le reste, explicite ta problématique.

C’est pas parce que c’est optimal que c’est forcément bien. Si tu veux, j’avais quelques un de mes profs qui en C te collait des variables globales ça et là (pas d’allocation sur la pile) pour grappiller 1 ou 2ms sur un programme de simulation de mouvements. Je peux comprendre l’intérêt, mais derrière ça fait du code sale.

La surcharge d’opérateurs est toujours à bien penser. Si surcharger l’opérateur + semble être une bonne idée, ça ne l’est pas souvent. Exemple classique de tout langage objet récent (java, c++, javascript, php, ruby, etc).

string a("toto"); // faut que je fasse du c++ je sais pas si c'est a("toto") ou a = "toto"; me semble que c'est équivalent
string b("toto");

string c = a + b + a + b;

Ca paraît anodin là. Mais en fait, tu fais ça réellement :

string tmp1 = a + b;
string tmp2 = tmp1 + a;
string tmp3 = tmp2 + b;
string c = tmp3;

ie: tu as crée des variables temporaires, soit de l’allocation en plus - et sur la pile.

L’opérateur += est préféré quand il est disponible :

string c = a;
c += b;
c += a;
c += b;

Car tu ne crée pas (trop) de variables temporaires, c’est juste c auquel on ajoute des données.

Plus généralement :

  1. Si le type de retour d’une fonction n’est pas un type primitif (int, pointeurs), alors c’est certainement pas optimal car tu as crée un objet sur la pile de la fonction appelée, que tu recopie à nouveau sur la pile de l’espace de retour, et certainement que tu feras une autre copie pour une autre variable de la fonction appelante.

éventuellement, tu peux faire des Smart Pointer. C’est un design pattern assez pratiqué en C++ qui ressemble à un proxy sur une zone mémoire, en minimisant le coût de la structure parente, tout en permettant la plupart des opérations.

  1. Si le paramètre d’une fonction n’est pas de type primitif ou pointeur, c’est qu’il y a de l’optimisation potentielle. Il faut toujours commencer par faire des passages par références, car tu évites de faire une copie. C’est plus ou moins un pointeur. Le gros avantage c’est que tu peux aussi faire des passages par références.

  2. Si le paramètre d’une fonction est de type objet quelconque, et plus encore s’il y a une hiérarchie de classes derrières, alors oublie complètement le passage par valeur. Passe par référence, ou dans ce cas là, par pointeur car sinon l’objet sera vu comme l’objet du type passé en paramètre (ie: si tu as foo(A b), et que tu fais foo(new B()) avec B extends A, alors c’est toutes les méthodes de A qui seront prises en compte.

j’ai fait :
int x;
double a1[5][5];
double** a2;
foobar1(a1,x);
foobar2(a2,x);

et j’obtient en sortie 4 et 4… je dois en conclure quoi?

Je travaille sur des algos qui doivent tourner en temps réel, donc 1 ou 2 ms sont importantes pour moi.
Et je cherche pas seulement des optimisations en rapport avec le c++ (donc l’objet). Des optimisations C sont intéressantes aussi.

C’est pour ca que avec mes tableaux initialisés en global avec leur tailles fixes, je vais plus vite sur une fonction qui fait des opérations dessus que avec des tableaux alloués dynamiquement quand j’execute la meme fonction?

Et merci pour les autres conseils, c’est un bon début.
Edité le 28/11/2007 à 08:33

+1 pour sans nom

Pour le cas des fonctions, tu dois en conclure que c’est passé par pointeur dans tous les cas.

Il y a cependant une différence, et normalement c’est le passage par copie. A voir avec un sizeof().

Si tu veux “optimiser” ton programme, sans avoir de considérations de maintenabilité (c’est sensible ça) : évite tout ce qui est indirection, évite les classes virtuels en C++ (l’objet n’est pas le plus léger qui soit, à cause de la v-table -> encore des indirections).

ie: a->x->f() == mal car :

*a = indirection sur a
*(*a).x = indirection sur le champ x
f((*a).x) = indirection sur la méthode (ça peut être optimisé par le compilateur, à voir)

Je peux pas te dire mieux ensuite, car ça passerait certainement par de l’assembleur. Et si tu fais du temps réel, le temps réel que j’ai connu (machine RT implémentation Java -> une boucle infinie = reboot !), alors je te conseillerai de déjà faire propre, avant d’ajouter de la saleté pour économiser 1 ou 2 ms.

Car la saleté, c’est sympa pour aller vite, mais pour débugger, have fun (et surtout en machine temps réel, sans un bi core t’es foutu)