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
Fabien LE LEZ
On Thu, 21 Jul 2005 07:36:10 +0200, Richard Delorme
:

Un truc de mon expérience personnelle : la plupart des optimisations
rendent le code plus clair. La meilleure façon d'optimiser un code c'est
de supprimer du code inutile, et moins il y a de code, plus c'est clair.


On est loin de cette vision des choses dans le message de départ du
thread (<news:42de7c43$0$21325$).

Je te rejoins toutefois sur un point : si tu fais du code clair, sans
chercher à bidouiller, en bref, si on comprend au premier coup d'oeil
ce que tu cherches à faire, il y a des chances pour que le compilateur
(ou le processeur) le comprenne aussi, et optimise d'autant mieux le
code.

Note par ailleurs que pour optimiser, il faut bien souvent bien
connaître le processeur sur lequel on travaille. Du coup, il n'est pas
rare (quand on travaille sur la vidéo par exemple) de trouver, pour le
même OS, plusieurs versions d'un même logiciel : une version Pentium
3, une version Pentium 4, une version "vieux AMD", une version "AMD
avec SSE", etc.

Avatar
kanze
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 ?

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 ?
Est-ce qu'il y a des raisons pour faire autre chose ? D'après
mon expérience :

-- La syntaxe de l'utilisation des pointeurs deplaît à
beaucoup. Moi, je m'en sers de temps en temps, mais chaque
fois, j'ai dû bien en justifier l'utilisation dans les
révues de code. Il faut donc bien une justification pour les
utiliser.

-- L'utilisation des if/else (dans ce cas-ci) risque de donner
des fonctions trop grandes et trop complexes.

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

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?


Est-ce qu'il modèle réelement ton problème ? (À vue d'oeil, une
fois l'optimisateur a fini, tu as une boucle vide, voire rien de
tout dans le cas de if. Il ne faut pas supposer que les auteurs
du compilateur sont des idiots quand même.)

- pourquoi une telle difference de temps?


C'est plus facile au compilateur d'optimiser ton code dans le
cas des if/else. Ce qui ne dit rien pour le cas dans ton code
réel.

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)() ;

--

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() ;

?

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

--
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
kanze
Richard Delorme 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 ?

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.


Si les performances ne sont pas importantes, pourquoi diable
programmer en C++ ?


Parce que le boss l'impose:-).

Sérieusement, dans beaucoup de cas, quels sont les alternatifs ?
Pour diverses raisons, Ada n'est pas considéré, et encore moins
d'autres langages de la famille Pascal, comme Modula-3. Et dans
la famille C, C++ est à peu près le seul langage où on peut
écrire du code portable et robuste. Java va bien pour des petits
bricoles, mais c'est extrèmement difficile, sinon impossible, de
créer du code réelement robuste. Et je n'ai pas encore régardé
du côté C# ; puisque je travaille sous Solaris, ce n'est même
pas une option.

--
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
Fabien LE LEZ
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.

Avatar
adebaene

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++, 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...

Arnaud


Avatar
kanze
Richard Delorme wrote:
On Wed, 20 Jul 2005 20:32:41 +0200, Richard Delorme
:

Si les performances ne sont pas importantes


Je n'ai absolument pas dit ça.


C'est ce que je comprends.

Je dis juste que tenter de bidouiller, au détriment de la
lisibilité du code, et au détriment des autres
fonctionnalités (le temps qu'on passe à essayer d'optimiser
là où ce n'est pas forcément nécessaire, on ne le passe pas
à implémenter autre chose) est une mauvaise idée.


Un truc de mon expérience personnelle : la plupart des
optimisations rendent le code plus clair. La meilleure façon
d'optimiser un code c'est de supprimer du code inutile, et
moins il y a de code, plus c'est clair. Par exemple, enlever
un test toujours vrai (donc inutile) rend le code plus lisible
et plus rapide. Et avec la quantité de code que je vois passer
et qui contient toutes sortes de choses inutiles : variables
redondantes, calculs inutilisés par la suite, etc. il y a de
quoi optimiser.


Je n'ai jamais vu ce genre de problème. Ce que j'ai vu, c'est
que plus le code est encapsulé, plus on peut faire des
optimisations par la suite, en fonction des données du profiler,
sans casser tout.

, pourquoi diable programmer en C++ ?


Le C++ a deux gros avantages :

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


Je ne trouve pas justement.


Du côté du langage, si « riche et puissant » signifie beaucoup
de features, il y en a. Côté bibliothèque, effectivement, il y a
des lacunes importantes.

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.) et à
mon avis, on code beaucoup plus élégamment et rapidement avec
eux qu'en C++. Par contre leurs performances sont
lamentables...


Un langage représente un ensemble. Les bibliothèques de Java,
c'est chouette. De même que la présence par défaut d'un GC et le
supporte pour les threads (deux choses où une solution pûrement
bibliothèque ne marche pas). En revanche, le langage fait un
maximum pour rendre le développement des gros projets
difficiles, et met aussi les bâtons dans les roues quand on
cherche d'écrire du code robuste (avec programmation par
contrat, validation des types lors de la compilation, etc.).

Curieusement, je n'ai pas constaté des problèmes de vitesse (en
dehors de Swing) pour des applications à longue durée. (Les
temps de démarrage le rend inutilisable pour des applications à
exécution ponctuelle.)

- l'exécution des programmes est généralement rapide, ce
qui permet souvent de ne pas se préoccuper d'optimisation.


Mon raisonnement, est que, comme le C++ produit un code
rapide, le besoin de performances peu justifier sont choix. Et
ce critère oblige aussi à se préoccuper d'optimisation.

Le raisonnement va assez loin : si tu as quelques Mo de
données, tu peux les charger entièrement en mémoire, dans
tes propres structures, plutôt que d'utiliser un moteur
externe de base de données, avec tous les problèmes de
déploiement que ça implique.


Ce n'est pas un exemple d'optimisation ça ?


Optimisation de quoi ? Côté vitesse d'exécution, c'est prèsque
sûrement une pessimisation, à moins que tu te sens capable tout
seul d'optimiser les accès mieux que Oracle (qui y met des
moyens). Côté vitesse de développement aussi, puisqu'il faut
développer du code à la place d'utiliser du tout fait.

Mais comme Fabien a dit, il peut y avoir de nets avantages au
niveau du deploiement. C'est beaucoup plus simple à installer un
seul exécutable que d'exiger l'installation d'une base de
données à côté, avec création d'une base, le maintenance, etc.

--
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
Fabien LE LEZ
On Thu, 21 Jul 2005 07:36:10 +0200, Richard Delorme
:

si tu as quelques Mo de données, tu peux les charger
entièrement en mémoire, dans tes propres structures, plutôt que
d'utiliser un moteur externe de base de données, avec tous les
problèmes de déploiement que ça implique.


Ce n'est pas un exemple d'optimisation ça ?


Justement non. On va au plus simple, i.e. on utilise des tableaux et
des objets avec la mécanique interne du langage, sans se préoccuper de
la taille des données ou du temps qu'il faut pour les charger en
mémoire.
Dans des langages moins rapides, cette méthode est vite trop lente, et
on est obligé d'optimiser en utilisant des méthodes détournées mais
plus rapides.


Avatar
kanze
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é. Ce qui importe aussi, c'est
l'impacte rélatif. Mettons que le if prend 100 nanosécondes, et
l'appel indirect 10 microsécondes (une facteur 100 -- je doute
qu'il soit réelement aussi grand). Si le traitement concerné
prend 100 millisécondes, ça ne change absolument rien. 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.
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.

mais la difference de temps d executions des deux programmes
de test est tellement enorme que je me dis qu'il doit y avoir
une erreur.

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.
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.

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.

--
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
Fabien LE LEZ
On 21 Jul 2005 00:42:39 -0700, :

Côté vitesse de développement aussi, puisqu'il faut
développer du code à la place d'utiliser du tout fait.


Pas forcément. Tu as une structure de données assez complexe
(typiquement, une classe contenant des objets qui eux-mêmes
contiennent des objets etc.), sur laquelle repose le gros du
programme.

Pour obtenir les données sous cette forme utilisable, à partir du
contenu d'un fichier (et éventuellement, l'opération inverse), tu peux
utiliser soit un système de sérialisation directe à partir d'un
fichier, soit un système d'accès à une base de données.

La solution "base de données" a une autre particularité : toutes les
données ne sont pas en mémoire en même temps (du moins, dans ton
programme), il faut donc une gestion plus fine des
chargements/déchargements de données.

C'est beaucoup plus simple à installer un
seul exécutable que d'exiger l'installation d'une base de
données à côté, avec création d'une base, le maintenance, etc.


Je confirme : un logiciel constitué d'un seul .exe, c'est vachement
confortable, que ce soit pour le programmeur du logiciel
d'installation, pour les techniciens SAV, ou pour les clients.

Avatar
kanze
Richard Delorme wrote:

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


La question est donc de savoir si ça :

{
if (test->isTrue())
test->behavior1();
else
abort();
}


est plus lent que :

{
(test->*behavior)();
}


Ça me semble un peu évident non ?


Pas tant que ça. En fait, les vitesses réeles vont dépendre
beaucoup de ce qu'on fait autour, et de comment le compilateur
optimise. Si, par exemple, test->isTrue() est inline, et renvoie
une constante, il n'y a probablement pas de compilateur au monde
qui ne supprimera pas le test. De la même façon, si behavior1
est inline et assez simple, tous les compilateurs le génèreront
inline, sans le moindre appel de fonction, tandis qu'il faudrait
bien un certain analyse pour générer l'appel indirect en ligne.
(Étant donné la rélative rareté des appels à travers des
pointeurs aux fonctions membres, c'est probable aussi que les
compilateurs ont investi rélativement peu dans son
optimisation.)

Appeler une fonction (vide en plus) à travers un pointeur de
fonction, ou par la fonction elle même produit à peu près les
mêmes performances.


Tu l'as mesuré ? Sur quelle machine ?

Sur ma machine (un Sparc), un appel direct d'une fonction est
une instruction machine, sans accès mémoire. L'implémentation
des pointeurs aux fonctions membres varient, mais il exige
typiquement trois ou quatre instructions machine, avec au moins
un accès mémoire, pour trouver la destination. Et il s'avère que
sur ma machine, au moins (c'est peut-être différent sur des
Sparc plus récents), la prédiction des branchements ne
fonctionne pas quand le cible du branchement doit être lu de la
mémoire. D'où un vidage du pipeline. Correctement mesurer, au
piff, je m'attendrais à une différence d'une facteur entre 5 et
25 (mais n'ayant pas mesuré, je peux me tromper).

Ajouter un autre appel de fonction (test->isTrue()) et un test
(if(...)) supplémentaire va donc logiquement réduire les
performances.


Si je me souviens bien de son code, toutes les fonctions en
question étaient inline. Je n'exclurais pas, dans son cas :

-- que le compilateur génère test->behavior1() en ligne,
-- qu'il constate ensuite que la boucle ne modifie rien,
-- qu'en conséquence, il sort le test de la boucle, et
finalement
-- qu'il supprime complètement la boucle maintenant vide.

Le benchmark qu'on nous a montré ne teste absolument rien. Au
minimum, il faudrait que les fonctions ne soient pas inline, et
qu'elles soient définies dans une autre unité de compilation.

--
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


1 2 3 4 5