OVH Cloud OVH Cloud

Complexité du polymorphisme

21 réponses
Avatar
Gégé
Salut,

Suite =E0 un post que j'avais ouvert r=E9cemment, j'ai isol=E9 une curiosit=
=E9
que j'aimerais =E9claircir gr=E2ce =E0 vos remarques :

J'ai une classe m=E8re A avec une methode a() virtuelle pure, et une
methode b() normale.
Je cr=E9e une classe d=E9riv=E9e B dans laquelle je mets dans a() la m=EAme
impl=E9mentation que b().

Concr=E8tement B.a(), et B.b() font la m=EAme chose.

Je caste B en A de fa=E7on =E0 utiliser plutot A.a() et A.b()

Si j'ex=E9cute ces 2 m=E9thodes un tr=E8s grand nombre de fois, je remarque
que a() est beaucoup plus lente que b().

Est-ce un r=E9sultat attendu ?

Merci

G

10 réponses

1 2 3
Avatar
Gégé
Selon tous ces posts, mon problème est plus situé au niveau de ma
configuration de compilation on dirait. Il est vrai que j'ai
énormement de mal de ce côté là et qu'elle doit être assez hasard euse.
J'ai créé ma release en partant de ma debug, mais j'ai du laisser
passer des options importantes ... j'utilise VC2005+, connaissez-vous
un bon tutoriel ? Merci d'avance
Avatar
Gégé
Alors j'ai un peu bidouillé les options du compilateur : j'ai changé
pas mal de choses qui n'avait rien à voir avec un mode release et j'ai
pu gagner encore du temps de calcul. Par contre passé un certain
nombre de noeuds je constate encore que le temps s'allonge plus que
linéairement. Peut-être reste-t-il des optimisations à faire dans ma
config :

Voici ma config Release, qq1 aurait il des suggestions ?

C/C++
/O2 /Ot /GL /I ".include" /I /D "WIN32" /D "_CONSOLE" /FD /EHsc /MT /
Fo"Release" /Fd"Releasevc80.pdb" /nologo /c /TP /errorReport:prompt

Linker
/OUT:".ReleaseG.exe" /NOLOGO /LIBPATH:".lib" /MANIFEST /
MANIFESTFILE:"ReleaseGGPrime.exe.intermediate.manifest" /
ERRORREPORT:PROMPT kernel32.lib user32.lib gdi32.lib winspool.lib
comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib
odbc32.lib odbccp32.lib

Merci !!
G
Avatar
James Kanze
On Nov 11, 10:22 am, Gégé wrote:
j'utilise VC2005+, connaissez-vous
un bon tutoriel ?



Tutoriel, non, mais la site Microsoft documente toutes les
options en détail. A priori, ils essaient aussi à rendre la
documentation comprehensible par un débuttant, mais je suis
plutôt mal placé pour juger s'ils ont réussi.

Ce que je fais, chaque fois que j'abord un nouveau compilateur,
c'est de chercher une liste de toutes les options, et me
démander pour chacune si je le veux ou non, en fonction de ce
qu'il fait. Mais je ne suis pas sûr que ce soit une approche qui
vaut pour un débuttant, parce que pour l'utiliser, il faut
pouvoir comprendre la description de l'option. Si tu poses la
question sur ce qu'on entend par niveau d'optimisation, c'est
peu probable que tu comprendras quelque chose du genre :

-fgcse-sm
When -fgcse-sm is enabled, a store motion pass is
run after global common subexpression elimination.
This pass will attempt to move stores out of loops.
When used in conjunction with -fgcse-lm, loops
containing a load/store sequence can be changed to a
load before the loop and a store after the loop.

(C'est de la documentation g++, mais c'est typique du genre de
jargon qu'on rencontre quand on va dans les détails.)

N'empêche que c'est sans doute comme ça que je commencerais.
Quitte à essayer d'aprofondir mes connaissances quand il y a
quelque chose que je ne comprends pas. À condition d'en avoir
besoin---dans beaucoup de cas, le programme est assez rapide
comme ça, et on peut simplement ignorer toutes les options qui
concerne l'optimisation. Si ce n'est pas le cas, en revanche, il
faudrait en général apprendre un peu sur l'optimisation avant de
s'y attaquer. Bien que prèsque tous les compilateurs ont aussi
des options « généraliste » d'optimisation, par exemple -O1,
-O2 ou -O3 avec g++, et qu'un peu d'expérimentation avec
celles-ci peut souvent suffire.

--
James Kanze (GABI Software) email:
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34
Avatar
Gégé
Merci James pour ces conseils.

Toujours dans le même registre, j'aurais une question supplémentaire :
y a-t-il une différence de performance entre un pointeur de fonction
et une fonction virtuelle ? Ces 2 concepts me semblent assez voisins
dans leur finalité. Je pourrais essayer d'alléger mon arbre en
n'utilisant que des pointeurs de fonction ...
Avatar
Gégé
J'ai recréé exactement ce que je constate en mode release :
a() => 3.078 sec
b() => 0 sec

Si on augmente M, l'écart se creuse de plus en plus.
Qu'obtenez-vous de votre côté ?

Germain

/////////////////////////////////////////////////////////////////////////// //////////////////

#include <vector>
#include <iostream>
#include <ctime>
using namespace std;

class A
{
public :
virtual void a() = 0;
void b();
};

void A::b()
{
return;
}

class B : public A
{
public :
void a();
};

void B::a()
{
return;
}

void main(int argc, char * argv[])
{

const int M = 30000;
const int N = 50000;

// nodes
vector<A*> lst;
for ( int i = 0; i < M; i++ )
{
lst.push_back( new B() );
}

// timer
clock_t t1, t2, t0;
t0 = clock();

// a()
for ( int i = 0; i < M; i++ )
{
A * _A = lst[i];
for ( int j = 0; j < N; j++ )
{
_A->a();
}
}
t1 = clock();
cout << "a() : " << ( double )( t1 - t0 ) / CLOCKS_PER_SEC << "sec"
<< endl;

// b()
for ( int i = 0; i < M; i++ )
{
A * _A = lst[i];
for ( int j = 0; j < N; j++ )
{
_A->b();
}
}
t2 = clock();
cout << "b() : " << ( double )( t2 - t1 ) / CLOCKS_PER_SEC <<
"sec" << endl;

// free memory
while ( lst.size() )
{
delete lst.back();
lst.pop_back();
}

system("pause");

return;

}

/////////////////////////////////////////////////////////////////////////// //////////////////
Avatar
Sylvain SF
Gégé a écrit :
J'ai recréé exactement ce que je constate en mode release :
a() => 3.078 sec
b() => 0 sec



j'indiquais (il y a 2 jours) a() : 2 sec, b() 0 sec pour 1 million
d'itérations; passer à 1.5 M d'itér. vérifie une certaine linéarité,
cela devrait rassurer sur quel point ?

Si on augmente M, l'écart se creuse de plus en plus.
Qu'obtenez-vous de votre côté ?



que cherchez-vous de votre coté ?

si vous voulez seulement (re)constater q'un appel virtuel est très lent
(2 microsec), vous l'avez confirmé et si votre code fait 1 million de
foi *rien*, il lui faudra du temps - mais vous pourriez choisir de faire
une seule fois *rien*, le résultat sera le même et le délai plus court.

mais si votre code réalise (vraiment) quelque chose, le temps d'appel
sera négligeable. certes vous pouvez remplacer votre design abstrait
par de beaux pointeurs de fonctions, vous gagnerez quelque secondes,
à vous de considérer s'il gagne également en lisibilité et maintenance.

Sylvain.
Avatar
Gégé
Ce que je mets dans a() ou b() est une opération très simple ( une
somme, une multiplication) et ce qui me chagrine c'est que le temps
passé à effectuer cette opération élémentaire est presque négli geable
devant celui passé à trouver la fonction ... c'est que je n'utilise
surement pas les bons outils ... en tout cas merci pour ces bonnes
paroles que je vais méditer de ce pas en me brossant les dents.
Avatar
Sylvain SF
Gégé a écrit :
Ce que je mets dans a() ou b() est une opération très simple ( une
somme, une multiplication) et ce qui me chagrine c'est que le temps
passé à effectuer cette opération élémentaire est presque négligeable
devant celui passé à trouver la fonction ... c'est que je n'utilise
surement pas les bons outils ... en tout cas merci pour ces bonnes
paroles que je vais méditer de ce pas en me brossant les dents.



si l'opération (le traitement virtuel) est vraiment simplissime, alors
en effet un modèle abstrait n'est p.e. pas la meilleure solution et un
modèle plus statique basé sur un "identifieur de comportement" et un
switch feront l'affaire.

eg

class A {
public:
enum {
kAdd = 0,
kMul
} Operation;

A(Operation _op) { op = _op; }

/*final*/ int a(int a){
switch (op){
case kAdd:
return a + 1;
case kMul:
return 2 * a;
}
return a; // unhandled
}

private:
Operation op;
};

Sylvain.
Avatar
Fabien LE LEZ
Tu seras peut-être intéressé par cet article :
http://dohp.wordpress.com/2008/10/31/implementation-of-polymorphism/
Avatar
Jean-Marc Bourguet
Gégé writes:

J'ai recréé exactement ce que je constate en mode release :
a() => 3.078 sec
b() => 0 sec

Si on augmente M, l'écart se creuse de plus en plus.
Qu'obtenez-vous de votre côté ?



Dans ton exemple il est facile pour un compilateur de virer completement
les appels a b() et la boucle qui ne fait donc plus rien.

Je ne suis pas sur de ce que tu mesures.

En general, il faut etre *tres* prudent sur des benchmarks synthetiques, on
se retrouve tres facilement a mesurer autre chose que ce qu'on cherche a
mesurer.

A+

--
Jean-Marc
FAQ de fclc++: http://www.cmla.ens-cachan.fr/~dosreis/C++/FAQ
C++ FAQ Lite en VF: http://www.ifrance.com/jlecomte/c++/c++-faq-lite/index.html
Site de usenet-fr: http://www.usenet-fr.news.eu.org
1 2 3