OVH Cloud OVH Cloud

vitesse: if vs pointeur de methode

126 réponses
Avatar
Guillaume Desticourt
bonsoir,

je m interroge sur la vitesse d execution entre une comparaison et un
pointeur de methode.
j ai une class dont une methode peut changer de comportement au cours de
la vie du process, mais cela rarement.
je me demandais si je devais avoir une methode unique avec un bloc
if/else ou alors un pointeur de methode sette a la methode qui va bien.
j ai donc ecrit deux petits programmes de test, et la solution if/else
est /visiblement/ plus rapide.
et donc je me demandais:
- est ce que mon test est pertinent?
- pourquoi une telle difference de temps?

les prog ont ete compile sous un linux 2.6 avec g++ 3.3.5

merci,

def.hh
------


#ifndef DEF_HH
# define DEF_HH

#include <stdlib.h>

#define MAX_LOOP 1000000000

class Test;

typedef int (Test::*behavior_t)(void);

class Test
{
public:
Test() :
_test(true)
{
}

int behavior1(void)
{
return 0;
}
int behavior2()
{
return 0;
}

inline bool isTrue(void)
{
return _test;
}
private:
bool _test;
};

#endif

if.cc
-----


#include "def.hh"

int main(void)
{
Test * test = new Test();

for (unsigned long u = 0;
u < MAX_LOOP;
++u)
{
if (test->isTrue())
test->behavior1();
else
abort();
}
return 0;
}


pointer.cc
----------



#include "def.hh"

int main(void)
{
Test * test = new Test();
behavior_t behavior;
if (test->isTrue())
behavior = &Test::behavior1;
else
abort();

(test->*behavior)();


for (unsigned long u = 0;
u < MAX_LOOP;
++u)
{
(test->*behavior)();
}
return 0;
}


Makefile
--------

all: iftest pointertest

iftest: def.hh
g++ -Wall -Werror -O2 if.cc -o iftest

pointertest: def.hh
g++ -Wall -Werror -O2 pointer.cc -o pointertest

clean:
rm -f *.o
rm -f *~
rm -f iftest pointertest


une tarball est - provisoirement - disponible ici:
http://www.freenopen.net/~guillaume/info/prog/source/ifpointerbench-20050720-1755.tar.bz2

--
Guillaume Desticourt

10 réponses

1 2 3 4 5
Avatar
kanze
Fabien LE LEZ wrote:
On 21 Jul 2005 00:26:43 -0700, :

Et je n'ai pas encore régardé du côté C#


A priori, C# a un gros inconvénient par rapport à C++ : la
pérennité. Ça m'étonnerait beaucoup que Microsoft ne décide
pas, dans quelques années, que C# c'est dépassé et qu'il faut
l'abandonner.


Comme ils ont fait avec Visual Basic ? (Je me suis laissé dire
qu'il n'y a que le nom qui n'a pas changé.)

Remarque, des mauvaises langues pourraient dire la même chose du
C++. Le C++ que j'utilise aujourd'hui n'a que peu de choses en
commun avec le C++ que j'ai appris il y a vingt ans. (J'exagère
beaucoup, évidemment. N'empêche que prèsque rien que j'ai écrit
il y a 20 ans tournerait aujourd'hui sans modification.)

--
James Kanze GABI Software
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
Matthieu Moy
writes:

Fabien LE LEZ wrote:
On 21 Jul 2005 00:26:43 -0700, :

Et je n'ai pas encore régardé du côté C#


A priori, C# a un gros inconvénient par rapport à C++ : la
pérennité. Ça m'étonnerait beaucoup que Microsoft ne décide
pas, dans quelques années, que C# c'est dépassé et qu'il faut
l'abandonner.


Comme ils ont fait avec Visual Basic ? (Je me suis laissé dire
qu'il n'y a que le nom qui n'a pas changé.)


Y'a quand même une grosse différence: C# est standardisé (ISO et ECMA
si mes souvenirs sont bons). Il y en a plusieurs implémentations.

En fait, le vrai risque avec C# c'est plutôt au niveau des API que du
langage (j'ai jamais regardé de près, mais si j'ai bien compris, une
partie non négligeable des API .NET est faite de trucs spécifiques
Windows et/ou de trucs brevetés MS).

--
Matthieu



Avatar
kanze
Matthieu Moy wrote:
Guillaume Desticourt writes:

inline bool isTrue(void)
^^^^^^

{
return _test;
}


Il est là à mon avis, ton problème. Met cette fonction dans un
.cpp inaccessible au compilo quand il compile le bout de code
que tu veux benchmarquer, et met cet appel dans les deux bouts
de codes que tu testes (l'un dans le if(...), l'autre
n'importe ou vu que le compilo ne pourra pas savoir si il y a
un effet de bord).


Ce n'est pas dit. Pour supprimer le test, il faut que le
compilateur puisse vérifier que _test ne peut pas changer dans
la boucle. Ce n'est pas vraiment difficile, mais il faut bien un
peu d'analyse (par exemple, pour savoir que l'appel à abort() ne
risque pas de le changer).

La véritable problème, c'est que la fonction qu'il appelle par
la suite est inline, et ne fait rien. Du coup, la boucle qu'il a

for (unsigned long u = 0;
u < MAX_LOOP;
++u)
{
if (test->isTrue())
test->behavior1();
else
abort();
}

devient prèsqu'à coup sûr (même sans optimisation) :

for (unsigned long u = 0;
u < MAX_LOOP;
++u)
{
if ( ! test->_true ) {
abort() ;
}
}

(Sans appel de fonction du tout, évidemment.)

Pour aller plus loin, il faut soit que le compilateur connaisse
la sémantique d'abort() (ce qui est fort possible), soit qu'il
puisse établir que l'objet désigné par test n'est pas accessible
en dehors de cette fonction (qui est bien à la portée de
certains compilateurs aussi).

Si tu veux benchmarquer X Vs Y, il faut qu'il n'y ai que X et
Y qui changent ...


Et encore... Sur un processeur moderne, un bout de code ne
s'exécute pas en isolation. Ce qui contourne ce qu'on mesure
peut influer beaucoup. Faire des mesures significatives exige
pas mal de savoir faire. Et en général, n'a pas beaucoup
d'intérêt -- ce qui t'intéresse, c'est les temps d'exécution
dans *ton* programme, et une fois ton programme est écrit assez
pour les avoir, le profiler permet de savoir réelement où se
trouve les goulots d'étranglement.

--
James Kanze GABI Software
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
Matthieu Moy
writes:

Matthieu Moy wrote:
Guillaume Desticourt writes:

inline bool isTrue(void)
^^^^^^

{
return _test;
}


Il est là à mon avis, ton problème.



Le "là", s'applique au mot clé "inline" que j'ai mis en évidence.

if (test->isTrue())
[...]
devient prèsqu'à coup sûr (même sans optimisation) :
[...]
if ( ! test->_true ) {


Justement parce que la fonction est inline, donc pas si on applique ma
proposition de mettre la fonction dans un .cpp séparé. (A moins que
ton linker optimise aussi, mais là, bon ...)

Si tu veux benchmarquer X Vs Y, il faut qu'il n'y ai que X et
Y qui changent ...


Et encore...


Tout à fait. Je vais en rajouter une couche: « Il faut *mais il ne
suffit pas* que [...]. »

--
Matthieu



Avatar
Guillaume Desticourt
wrote:
Guillaume Desticourt wrote:


On Wed, 20 Jul 2005 18:30:57 +0200, Guillaume Desticourt
:




- est ce que mon test est pertinent?





Est-ce que la différence sera réellement visible par
l'utilisateur, dans le programme final ?




je ne sais pas. la en faisant un boucle de 1000000000
iterations, la difference est visible a l oeil nu. maintenant
je n ai aucune idee du volume de donnees que mon programme
aura a traiter.



Qu'importe, d'un certain côté.
[...]

Le choix
ne peut influer sur la vitesse totale d'exécution que si cette
partie représente une partie significative du temps d'exécution.


je sais tout de meme que cette partie du code sera appelee extremement
souvent, donc faire des economies a ce niveau a tout de meme des chances
d'etre rentable.

Chose que tu ne sauras de toute façon qu'une fois le programme
complet, avec les données du profiler. Tout essai à faire
quelque chose de ce genre avant est contreproductif.



donc tu preconises de ne pas s occuper de performance avant d'avoir code
le programme - comme Fabien Lelez si je vous ai bien suivi...

[...]

Code de la manière la plus claire et lisible possible, et
occupe-toi des problèmes de performances si le programme
final est effectivement trop lent.




heu bof, je trouve plus logique de viser le resultat desire
plutot qu'appliquer des rustines et me retrouver avec un code
- encore plus - illisible.



Je vois que tu n'as aucune expérience avec des vrais programmes.


j'en ai ecrits pas mal de faux alors...

Si les performances risquent d'être une facteur, la chose la
plus importante que tu peux faire, c'est de l'encapsulation. De
façon à être sûr de pouvoir modifier une partie du code (celui
que le profiler dit de modifier) sans impacter sur la reste.




oui enfin dans le cas present je ne vois pas trop le rapport,
l'encapsulation est la que je fasse
if
test->behavior1()
else
test->behavior2()

ou
(test->*behavior)();

Surtout que ca se fera a coup sur beaucoup plus tard, et tout
aussi surement par quelqu'un d'autre qui ne comprendra pas mon
code...



Si d'autres ne peut pas comprendre ton code, c'est foutu
d'avance. Ce que Fabien disait, justement, c'est d'écrire du
code que les autres peuvent comprendre.



bof. des beaux principes, mais comprendre le code de quelqu'un d'autre
est amha une des choses les plus diffiles. Des qu'un programme commence
a avoir une certaine taille/complexite, cela devient ardu, malgre la
lisibilite du code, les patrons de conception etc...

--
Guillaume Desticourt




Avatar
Guillaume Desticourt
Richard Delorme wrote:

Justement, a l'execution, la premiere methode (if) est plus rapide que
la deuxieme!!! mais peut etre le compilateur a t il reussi a supprimer
l'appel a la fonction vide mais pas a travers le pointeur de fonction...



Sans doute. Comme le test->isTrue() est toujours vrai, il l'a sans doute
supprimer aussi.

je referai le test demain avec une fonction non vide, et peut etre
sans le -O2




bon j'ai refait le test en retirant l'optimisation et le inline, les
deux tests prennent alors un temps visiblement similaire. donc mon test
etait ko.

--
Guillaume Desticourt


Avatar
Fabien LE LEZ
On Thu, 21 Jul 2005 11:20:05 +0200, Guillaume Desticourt
:

je sais tout de meme que cette partie du code sera appelee extremement
souvent,


Un nombre de fois comparable au "un milliard" que tu as utilisé pour
tester ?

Avatar
Loïc Joly

oui enfin dans le cas present je ne vois pas trop le rapport,
l'encapsulation est la que je fasse
if
test->behavior1()
else
test->behavior2()

ou
(test->*behavior)();


Sauf que ce que James proposais, c'était d'utiliser le pattern strategy,
qui apporte certainement une souplesse supplémentaire (et qui a plus de
chance d'être optimisé que l'appel par pointeur de fonction membre, en
plus).

--
Loïc

Avatar
Guillaume Desticourt
wrote:
Guillaume Desticourt wrote:


je m interroge sur la vitesse d execution entre une
comparaison et un pointeur de methode.



Pourquoi ? Ils font des choses différentes. Est-ce que tu as un
problème de performance, et ce sont les deux alternatifs ?



les deux idees que j ai eues. je suis ouvert aux autres bien sur.

j ai une class dont une methode peut changer de comportement
au cours de la vie du process, mais cela rarement. je me
demandais si je devais avoir une methode unique avec un bloc
if/else ou alors un pointeur de methode sette a la methode qui
va bien.



La solution classique ici, c'est la modèle stratégie, non ?
[...]

Pourquoi est-ce que la modèle stratégie ne s'applique pas


parce que je ne le connais pas :)
je vais me documenter.


j ai donc ecrit deux petits programmes de test, et la solution
if/else est /visiblement/ plus rapide.



[...]

En général, les appels sur des pointeurs à fonction membre ne
sont pas ce qu'il y a de plus rapide. Mais ce n'est pas là la
question. Qu'est-ce qu'il est plus clair :

(this->*pmf)() ;



ma foi, cette solution va reduire le code, mais la syntaxe est plus
lourde qu un if/else donc c est une question de gout...

--

if ( c1 ) {
f1() ;
} else {
f2() ;
} // Avec éventuellement plus de cas.

--

switch ( mode ) {
case m1 :
f1() ;
break ;

case m2 :
f2() ;
break ;
} // Avec éventuellement plus de cas.

--

deleguee->f() ;

?



je suppose que c est le cas ou on utilise une strategie(?) mais comme je
ne connais pas je regarde...

(En passant, en C, j'aurais certainement utilisé le switch.
Pourquoi ne pas en avoir parler aussi ?)



et bien if/else c est pareil que switch et puis il n y a que deux cas
donc if/else me semble bien...

--
Guillaume Desticourt


Avatar
Richard Delorme


Le C++ a deux gros avantages :

- c'est un langage très puissant, très riche ;


Je ne trouve pas justement. Des langages comme Java, Python, Ruby, etc
dont beaucoup plus riches et supportent plein de fonctionnalité
nativement (multi-threads, graphismes, réseau, ramasse-miette, etc.)



AMHA, tu confonds richesse du langage et richesse de la/les
bilbiothèque(s)fournie()s avec le langage : Il est vrai que les
bilbiothèques standards de ces langages couvrent un spectre de besoins
plus large que ce qui est couvert par le C++,


Séparer ce qui est du ressort de la bibliothèque et du langage est
difficile. Par exemple, synchronized est un mot clé de Java, sert au
multi-tâche, et ne fait pas partie d'une bibliothèque. On peut dire la
même chose des ramasses-miettes, c'est une propriété de ces langages et
non une bibliothèque. En Python, "print" est une instruction et non une
fonction d'une bibliothèque. Même en C++, une notion comme la chaine de
caractère est partagée par le langage (avec les chaines littérales) et
la bibliothèque (std::basic_string, les fonctions str*(), etc.).

mais en terme de pouvoir
d'expressivité du langage lui-même, C++ reste supérieur : je pense
à l'héritage multiple, aux template, etc...


A ce jeu là, un langage comme Python est beaucoup plus fort. Il supporte
l'héritage multiple et la généricité (sans la complexité syntaxique du
C++), et offre plein de paradigmes quasi-inexistant en C++
(programmation fonctionnelle, introspection, etc.)

--
Richard



1 2 3 4 5