OVH Cloud OVH Cloud

Eviter le coût de la résolution de "virtual"

29 réponses
Avatar
Vincent Richard
Bonsoir,

Soit le programme suivant :

class A
{
public:
virtual void f(const int p) = 0;
};

class B : public A
{
public:
void f(const int p) { /* ... */ }
};

int main()
{
A* a = new B;

for (int i = 0 ; i < 10000 ; ++i)
a->f(i);
}

Je crois savoir qu'à chaque fois que l'on fait un appel à 'f()', une
"recherche" est effectuée pour appeler la fonction correcte selon
l'objet sur lequel on l'appelle. Cette recherche a un coût.

Y'a-t-il un moyen de la limiter, par exemple, en obtenant l'adresse
une première fois (résolution), puis en l'appelant ensuite (simple
appel, sans résolution), ce qui fait que la résolution n'est pas
faite à chaque itération ?

Par exemple :

int main()
{
A* a = new B;
PointeurSurLaFonction pf = ???;

for (int i = 0 ; i < 10000 ; ++i)
(pf)(i);
}

Apparemment, cela ne peut pas être résolu avec les pointeurs sur
fonctions membres :

int main()
{
A* a = new B;

typedef void (A::*FuncPtr)(int);
FuncPtr pf = &A::f;

for (int i = 0 ; i < 10000 ; ++i)
(a->*pf)(i);
}

...puisque dans ce cas, la résolution est évidemment quand même faite.

Je ne sais pas si je suis très explicite...

Merci d'avance pour vos réponses.

Vincent

--
SL> Au fait elle est mieux ma signature maintenant ?
Oui. T'enlève encore les conneries que t'as écrit dedans et c'est bon.
-+- JB in <http://www.le-gnu.net> : Le neuneuttoyage par le vide -+-

10 réponses

1 2 3
Avatar
Samuel Krempp
--nextPart1875299.990kzJFL9n
Content-Type: text/plain; charset=iso-8859-15
Content-Transfer-Encoding: 8Bit

le Jeudi 4 Septembre 2003 10:11, écrivit :

Vincent Richard wrote
in message news:<3f56452c$0$27055$...
Je crois savoir qu'à chaque fois que l'on fait un appel à 'f()', une
"recherche" est effectuée pour appeler la fonction correcte selon
l'objet sur lequel on l'appelle. Cette recherche a un coût.


Un coût très faible, quand même. Il y a des cas où ça importe, mais ils
ne sont pas si fréquent que ça.


je sais pas exactement ce que va faire Richard, mais ça pourrait ressembler
à un prog de calcul, et en tout cas, pour des progs de calcul, le coût du
virtual importe énormément si l'appel a lieu au sein de l'itération de la
boucle la plus interne. ('inner-most loop' ..)

Aucune lib de calcul ne mettrait de fonctions virtuelles qui pourrait se
retrouver dans cette situation..


un exemple de temps d'éxécution virtual / non-virtual dans une boucle de 10
millions d'itérations (source attaché), donne :
non-virtual. Time=0.05
virtual. Time=0.15
ratio : 3
avec g++-3.3.2 (-O3 ), sur Pentium 4.

on peut s'y attendre : sans virtual, l'operator() est inliné et l'itération
s'éxécute 3 fois plus vite.

L'exemple n'est pas absurde, ça serait parfois utile d'avoir du
polymorphisme sur des objets du genre 'Iterated_Operation'.
mais vu l'imapct de 'virtual' dans cette situation (et l'importance de
l'inlining), les libs de calcul obtiennent du polymorphisme par templates,
pas par des fonctions virtuelles.

--
Sam
Enlever les mots en trop dans mon e-mail pour répondre
--nextPart1875299.990kzJFL9n
Content-Type: text/x-c++src; name="time_virtual.cpp"
Content-Transfer-Encoding: 8Bit
Content-Disposition: attachment; filename="time_virtual.cpp"

#include <iostream>
#include <functional>
#include <algorithm>
#include <vector>

#include <boost/timer.hpp>

class Iterated_Operation {
public:
virtual void operator() ( const double &) = 0;
virtual double val() const = 0;
};

class Sum_v :public Iterated_Operation {
double val_;
public:
Sum_v() : val_(0) {}
virtual void operator() ( const double & x) { val_ += x; }
virtual double val() const { return val_; }
};

class Sum {
double val_;
public:
Sum() : val_(0) {}
void operator() ( const double & x) { val_ += x; }
double val() const { return val_; }
};

int main( int argc, char* argv[]) {
using namespace std;

std::vector<double> v(1000*1000*20, 0.1);
Sum sum;
Sum_v sum_v;

boost::timer chrono;
Sum res = for_each(v.begin(),v.end(), sum);
double t1 = chrono.elapsed();

cout << "non-virtual. Time=" <<t1<< " res=" << res.val() << endl;

boost::timer chrono2;
Sum_v res2 = for_each(v.begin(),v.end(), sum_v);
double t2 = chrono2.elapsed();

cout << "virtual. Time=" <<t2<< " res=" << res2.val() << endl;
cout << "ratio : " << t2 / t1 << endl;
}

--nextPart1875299.990kzJFL9n--


Avatar
Samuel Krempp
--nextPart4584813.SeHn3fUIgW
Content-Type: text/plain; charset=iso-8859-15
Content-Transfer-Encoding: 8Bit

le Jeudi 4 Septembre 2003 12:52, écrivit :

un exemple de temps d'éxécution virtual / non-virtual dans une boucle de
10 millions d'itérations (source attaché), donne :
non-virtual. Time=0.05
virtual. Time=0.15
ratio : 3
avec g++-3.3.2 (-O3 ), sur Pentium 4.


J'ai un peu compliqué (utilisé une Iterated_Operation * externe pour tenter
de forcer le comportement virtuel, et distingué plusieurs types de boucles,
for_each, explicite, ..)

Avec intel-7.1 linux, j'obtiens à peu près les mêmes chronos (mais les cas
où il arrive à optimiser la boucle non-virtuelle ne sont pas les mêmes. il
y arrive avec une boucle explicite (avec un compteur entier, mais pas avec
une boucle explicite sur un pointeur), mais pas avec for_each - tandis que
c'est l'inverse pour g++.

en résumé (et pour les 2 compilos), j'obtiens en gros :
0.05s pour les boucles non-virtuelles optimisées
0.15s pour les boucles non-virtuelle quand l'optimisation n'a pas lieu
(for_each pour intel, boucle explicite pour g++)
0.19s pour la boucle virtuelle.

Les temps varient un peu d'un compilo un autre, et d'une éxécution à l'autre
(d'un chouillat), mais les rapports restent à chaque fois consistant :

x3 entre boucle non-virtuelle selon qu'elle est optimisée ou non.
(intel optimisant la boucle explicite, g++ optimisant le for_each)
Quelqu'un a une idée du genre d'optimisation dont il peut s'agir, et
pourquoi ce n'est activé que dans un type de boucle ?
Je pensais que les 2 types de boucles donneraient lieux aux mêmes types
d'optimisation (inliner le code), mais du coup je m'interroge.


+20% entre la boucle non-virtuelle la plus lente et la boucle virtuelle.
Est-ce qu'on peut raisonnablement considérer que le cout de l'appel virtuel
a un surcoût de 20% par rapport au coût de la boucle elle-même + l'appel de
fonction basique ?

--
Sam
Enlever les mots en trop dans mon e-mail pour répondre
--nextPart4584813.SeHn3fUIgW
Content-Type: text/x-c++src; name="time_virtual_tout.cpp"
Content-Transfer-Encoding: 8Bit
Content-Disposition: attachment; filename="time_virtual_tout.cpp"

// -- time_virtual.hpp -------------------------------------------------------

class Iterated_Operation {
public:
virtual void operator() ( const double &) = 0;
virtual double val() const = 0;
};



// -- time_virtual_aux.cpp ---------------------------------------------------

#include "time_virtual.hpp"
#include <vector>
#include <cmath>

class Sum_v :public Iterated_Operation {
double val_;
public:
Sum_v() : val_(0) {}
virtual void operator() ( const double & x) { val_ += x; }
virtual double val() const { return val_; }
};

class LogSum_v :public Iterated_Operation {
double val_;
public:
LogSum_v() : val_(0) {}
virtual void operator() ( const double & x) { val_ += log(x); }
virtual double val() const { return val_; }
};

std::vector<Iterated_Operation*> const& un_vect() {
static std::vector<Iterated_Operation *> ops;
ops.push_back(new LogSum_v);
ops.push_back(new Sum_v);

return ops;
}

Iterated_Operation* une_op() {
// static Iterated_Operation * op = new Sum_v; // g++ merde (+400% temps !!) si on fait ça.
static Iterated_Operation * op = un_vect()[1]; // ça, ça passe mieux.
return op;
}

// -- time_virtual.cpp ---------------------------------------------------------

#include <iostream>
#include <algorithm>
#include <vector>
#include <cmath>
#include <boost/timer.hpp>

#include "time_virtual.hpp"

class Sum {
double val_;
public:
Sum() : val_(0) {}
void operator() ( const double & x) { val_ += x; }
double val() const { return val_; }
};

extern Iterated_Operation * une_op();

int main( int argc, char* argv[]) {
using namespace std;

int N= 10*1000*1000;
const std::vector<double> v(N, 0.1);
Sum sum;

double t_min=-1;

{
boost::timer chrono_each;
double res_each = for_each(v.begin(), v.end(), sum).val();
double t_each=chrono_each.elapsed();
cout << "non-virtual, for_each. Time=" <<t_each<< " res=" << res_each << endl;
if(t_min<0 || t_min>t_each)
t_min = t_each;
}

{
boost::timer chrono;
for(int i=0; i<N; ++i)
sum(v[i]);
double t1 = chrono.elapsed();
double res1=sum.val();
cout << "non-virtual, explicit loop. Time=" <<t1<< " res=" << res1 << endl;
if(t_min<0 || t_min>t1)
t_min = t1;
}

{
boost::timer chrono2;
for(int i=0; i<N; ++i)
(*une_op())(v[i]);
double res2 = une_op()->val();
double t2 = chrono2.elapsed();

cout << "virtual. Time=" <<t2<< " res=" << res2 << endl;
cout << "ratio : " << t2 / t1 << endl;
}

}

--nextPart4584813.SeHn3fUIgW--

Avatar
kanze
Samuel Krempp wrote in message
news:<3f571981$0$26411$...
le Jeudi 4 Septembre 2003 10:11, écrivit :

Vincent Richard wrote
in message news:<3f56452c$0$27055$...
Je crois savoir qu'à chaque fois que l'on fait un appel à 'f()',
une "recherche" est effectuée pour appeler la fonction correcte
selon l'objet sur lequel on l'appelle. Cette recherche a un coût.


Un coût très faible, quand même. Il y a des cas où ça importe, mais
ils ne sont pas si fréquent que ça.


je sais pas exactement ce que va faire Richard, mais ça pourrait
ressembler à un prog de calcul, et en tout cas, pour des progs de
calcul, le coût du virtual importe énormément si l'appel a lieu au
sein de l'itération de la boucle la plus interne. ('inner-most loop'
..)


C'est ce que j'ai dit, non ? Il y a des cas où ça importe. Ils ne sont
pas si fréquents que ça, mais ils existent bien.

En fait, le coût effectif dépend de beaucoup de chose : le processeur,
le compilateur, et ce qu'on fait dans la fonction. Je sais que le coût
(rélatif) est beaucoup plus grand sur des processeurs modernes, par
exemple, qu'il ne l'a été. Et qu'évidemment, si le processeur réussi à
mettre la fonction non-virtuelle inline, ça expose des possibilités
supplémentaire d'optimisation qui pourrait être non-négligible. Je sais
aussi qu'il y a de grandes différences entre les compilateurs en ce qui
concerne la qualité de l'optimisation des fonctions virtuelles.

Aucune lib de calcul ne mettrait de fonctions virtuelles qui pourrait
se retrouver dans cette situation..

un exemple de temps d'éxécution virtual / non-virtual dans une boucle
de 10 millions d'itérations (source attaché), donne :

non-virtual. Time=0.05
virtual. Time=0.15
ratio : 3
avec g++-3.3.2 (-O3 ), sur Pentium 4.

on peut s'y attendre : sans virtual, l'operator() est inliné et
l'itération s'éxécute 3 fois plus vite.


Sur ma machine de travail, la différence en coût de l'appel d'une
fonction vide, non-inline, et de l'ordre de 10. C-à-d que ça peut bien
faire une différence. Si la fonction appelée est courte, évidemment, et
si le compilateur n'arrive pas à résoudre le type statiquement. En
revanche, le coût absolu est extrèmement faible dans les deux cas. C-à-d
que dès qu'il y a du traitement dans la fonction, la différence devient
negligible : qu'importe qu'une partie du traitement va 10 fois plus
vite, si cette partie ne représente 1% du temps total.

--
James Kanze GABI Software mailto:
Conseils en informatique orientée objet/ http://www.gabi-soft.fr
Beratung in objektorientierter Datenverarbeitung
11 rue de Rambouillet, 78460 Chevreuse, France, +33 (0)1 30 23 45 16



Avatar
Gabriel Dos Reis
Laurent Deniau writes:

| Negligeable. C'est du meme ordre de rapidite qu'un appel de fonction
| standard depuis l'introduction des chunks (orthographe
^
s/c/t/

[...]

| Pour plus d'info, lire Design and Development of C++ de BS.
^^^^^^^^^^^
s/Development/Evolution/

-- Gaby
Avatar
Gabriel Dos Reis
writes:


| Sur ma machine de travail, la différence en coût de l'appel d'une
| fonction vide, non-inline, et de l'ordre de 10.

La difference ou le rapport ?

-- Gaby
Avatar
Laurent Deniau
Gabriel Dos Reis wrote:
Laurent Deniau writes:

| Negligeable. C'est du meme ordre de rapidite qu'un appel de fonction
| standard depuis l'introduction des chunks (orthographe
^
s/c/t/

[...]

| Pour plus d'info, lire Design and Development of C++ de BS.
^^^^^^^^^^^
s/Development/Evolution/


Merci. Fatigue' moi :-)

a+, ld.

--
[ Laurent Deniau -- Scientific Computing & Data Analysis ]
[ CERN -- European Center for Nuclear Research ]
[ - http://cern.ch/Laurent.Deniau ]
[ -- One becomes old when dreams become regrets -- ]

Avatar
Samuel Krempp
le Jeudi 4 Septembre 2003 17:16, écrivit :

Samuel Krempp wrote in message
news:<3f571981$0$26411$...
le Jeudi 4 Septembre 2003 10:11, écrivit :

Un coût très faible, quand même. Il y a des cas où ça importe, mais
ils ne sont pas si fréquent que ça.


je sais pas exactement ce que va faire Richard, mais ça pourrait
ressembler à un prog de calcul, et en tout cas, pour des progs de
calcul, le coût du virtual importe énormément si l'appel a lieu au
sein de l'itération de la boucle la plus interne. ('inner-most loop'
..)


C'est ce que j'ai dit, non ? Il y a des cas où ça importe. Ils ne sont


euh, oui. je donne juste un exemple de "cas où ça importe",
je ne contredis pas que ça a un "un coût très faible" (dans l'absolu).

Par contre, ça contredit le "coût négligeable" du message de Laurent,
j'aurai aussi pu mettre mon msg en réponse à celui-là.

mettre la fonction non-virtuelle inline, ça expose des possibilités
supplémentaire d'optimisation qui pourrait être non-négligible. Je sais


Au fait, on dit "négligeable" en français, "négligible" n'existe pas :-).
[ Je me permets de jouer l'emmerdeur pour le cas où tu voudrais encore
perfectionner ta maîtrise de la langue .. ]

Sur ma machine de travail, la différence en coût de l'appel d'une
fonction vide, non-inline, et de l'ordre de 10. C-à-d que ça peut bien
faire une différence. Si la fonction appelée est courte, évidemment, et
si le compilateur n'arrive pas à résoudre le type statiquement. En
revanche, le coût absolu est extrèmement faible dans les deux cas. C-à-d
que dès qu'il y a du traitement dans la fonction, la différence devient
negligible : qu'importe qu'une partie du traitement va 10 fois plus
vite, si cette partie ne représente 1% du temps total.



je suis bien d'accord. d'autant que sur mon PC ce facteur est même de
l'ordre de 2 - 3 seulement (avec g++ et intel).
Pour savoir ce qui est important à vue de nez, il est utile de savoir en
gros à quels facteurs s'attendre ; je n'avais pas vraiment idée des ordres
de grandeurs en jeu, dans les appels de fonction et de fonction virtuelle,
comparés à des opérations élémentaires. Mes petits tests m'ont appris qque
chose.

--
Sam
Enlever les mots en trop dans mon e-mail pour répondre



Avatar
Laurent Deniau
Samuel Krempp wrote:
le Jeudi 4 Septembre 2003 17:16, écrivit :


Samuel Krempp wrote in message
news:<3f571981$0$26411$...

le Jeudi 4 Septembre 2003 10:11, écrivit :



Un coût très faible, quand même. Il y a des cas où ça importe, mais
ils ne sont pas si fréquent que ça.


je sais pas exactement ce que va faire Richard, mais ça pourrait
ressembler à un prog de calcul, et en tout cas, pour des progs de
calcul, le coût du virtual importe énormément si l'appel a lieu au
sein de l'itération de la boucle la plus interne. ('inner-most loop'
..)


C'est ce que j'ai dit, non ? Il y a des cas où ça importe. Ils ne sont



euh, oui. je donne juste un exemple de "cas où ça importe",
je ne contredis pas que ça a un "un coût très faible" (dans l'absolu).

Par contre, ça contredit le "coût négligeable" du message de Laurent,
j'aurai aussi pu mettre mon msg en réponse à celui-là.


Je parlais par rapport a un appel effectif d'une fonction (attention aux
optimisations avec -On, n>2). Par rapport a un appel indirect de fonction,
j'avais note que les fonctions virtuelles etaient plus rapides de qques % (<5%),
ce qui parait normal dans une boucle ou la fonction/pointeur de fonction sont
des invariants. Mais cela date de qques annees et avec un vieux gcc/CC donc
c'est peut-etre obsolete.

a+, ld.

--
[ Laurent Deniau -- Scientific Computing & Data Analysis ]
[ CERN -- European Center for Nuclear Research ]
[ - http://cern.ch/Laurent.Deniau ]
[ -- One becomes old when dreams become regrets -- ]




Avatar
Gabriel Dos Reis
Samuel Krempp writes:

| le Jeudi 4 Septembre 2003 17:16, écrivit :
|
| > Samuel Krempp wrote in message
| > news:<3f571981$0$26411$...
| >> le Jeudi 4 Septembre 2003 10:11, écrivit :
|
| >> > Un coût très faible, quand même. Il y a des cas où ça impo rte, mais
| >> > ils ne sont pas si fréquent que ça.
| >
| >> je sais pas exactement ce que va faire Richard, mais ça pourrait
| >> ressembler à un prog de calcul, et en tout cas, pour des progs de
| >> calcul, le coût du virtual importe énormément si l'appel a lieu au
| >> sein de l'itération de la boucle la plus interne. ('inner-most loop'
| >> ..)
| >
| > C'est ce que j'ai dit, non ? Il y a des cas où ça importe. Ils ne s ont
|
| euh, oui. je donne juste un exemple de "cas où ça importe",
| je ne contredis pas que ça a un "un coût très faible" (dans l'abso lu).

Mais tu as commis un crime de lèse-majesté <g>.

-- Gaby
Avatar
Samuel Krempp
le Jeudi 4 Septembre 2003 18:20, écrivit :

Par contre, ça contredit le "coût négligeable" du message de Laurent,
j'aurai aussi pu mettre mon msg en réponse à celui-là.


Je parlais par rapport a un appel effectif d'une fonction (attention aux


ah ok. Je n'avais pas interepreté comme ça, en considérant le blocage de
l'inlining comme faisant partie du coût de la 'recherche' de la fonction.

--
Sam
Enlever les mots en trop dans mon e-mail pour répondre


1 2 3