OVH Cloud OVH Cloud

C++ embarqué

29 réponses
Avatar
Stan
http://www.research.att.com/~bs/esc99.html


Même si le domaine visé est l'embarqué,
le côté didactique de BS est toujours
agréable à lire...


--
-Stan

10 réponses

1 2 3
Avatar
Laurent Deniau
Stan wrote:
http://www.research.att.com/~bs/esc99.html


Même si le domaine visé est l'embarqué,
le côté didactique de BS est toujours
agréable à lire...


Les temps qu'il donne pour les appels de fonctions souligne qqchose que
j'avais deja remarque il y a qqtemps: les appels virtuels (ou via des
pointeurs de fonction) sont plus rapide que des appels directs. Mais je
pense que ces resultats sont biaises.

a+, ld.

Avatar
Stan
"Laurent Deniau" a écrit dans le message de news:
db010r$j98$
Stan wrote:
http://www.research.att.com/~bs/esc99.html


Même si le domaine visé est l'embarqué,
le côté didactique de BS est toujours
agréable à lire...


Les temps qu'il donne pour les appels de fonctions souligne qqchose que
j'avais deja remarque il y a qqtemps: les appels virtuels (ou via des
pointeurs de fonction) sont plus rapide que des appels directs. Mais je
pense que ces resultats sont biaises.



Il est vrai que la différence est assez importante;
je suis resté un moment devant ce tableau, dubitatif.
Mais mis à part ça, j'aprécie la concision de la note.


--
-Stan


Avatar
kanze
Laurent Deniau wrote:
Stan wrote:
http://www.research.att.com/~bs/esc99.html

Même si le domaine visé est l'embarqué, le côté didactique
de BS est toujours agréable à lire...


Les temps qu'il donne pour les appels de fonctions souligne
qqchose que j'avais deja remarque il y a qqtemps: les appels
virtuels (ou via des pointeurs de fonction) sont plus rapide
que des appels directs. Mais je pense que ces resultats sont
biaises.


Ou qu'il s'est simplement trompé en copiant quelque chose ?

Si on ne régarde que la page postée, on se pose réelement des
questions. Littéralement, ce qu'il a écrit, c'est qu'un appel
virtuel prend 1,47 *sécondes* -- ce qui est inconcevable, même
sur la machine la plus lente que je n'ai jamais vu. Il a donné
un link à une page où il explique comment il a fait des mésures,
mais je ne trouve aucun chiffre là qui correspond à ce 1,47. En
revanche, il y a un tableau sur cette page aussi, avec ici en
plus des informations importantes comme le nombre d'itérations
et le code réelement testé.

Alors, un premier commentaire s'imposent : Dans le tableau
initial, il cite des temps pour "non-virtual member" : "p->f(1)"
et "virtual member" : "p->g(1)". Noms qu'on rétrouve sur la page
détaillée. Seulement, si on régarde le code, c'est f qui est
virtuel, et g qui ne l'est pas. Plutôt que du biais
intentionnel, je pencherais pour une simple erreur
d'étiquettage.

Ensuite, il faut reconnaître que mésurer des valeurs aussi
petites relève de la défie. Chez moi, par exemple, j'ai un test
qui mésure 1) un appel de fonction inline, 2) un appel non
inline, 3) un appel membre inline, 4) un appel membre non
inline, et 5) un appel virtuel, j'ai :

avec g++ (-O3) :
Testing Inline (1000000000 iterations)...
0.00281 microseconds per iteration
Testing Out of line (1000000000 iterations)...
0.00354 microseconds per iteration
Testing Inline member (1000000000 iterations)...
0.00259 microseconds per iteration
Testing Out of line member (1000000000 iterations)...
0.00131 microseconds per iteration
Testing Virtual (1000000000 iterations)...
0.07121 microseconds per iteration

avec Sun CC (-O4) :
Testing Inline (1000000000 iterations)...
0.00223 microseconds per iteration
Testing Out of line (1000000000 iterations)...
0.0116 microseconds per iteration
Testing Inline member (1000000000 iterations)...
0.00218 microseconds per iteration
Testing Out of line member (1000000000 iterations)...
0.0159 microseconds per iteration
Testing Virtual (1000000000 iterations)...
0.06582 microseconds per iteration

Mais pour être honnête, je ne crois pas trop les mésures
inférieur à une dizaine de nanosécondes par itération. À mon
avis, les différences à ce niveau relèvent autant des variations
naturelles (le « jitter ») que des différences réeles.

Toujours est-il que je crois qu'on peut dire qu'un appel virtuel
prend entre 60 et 75 nanosécondes, qu'un appel non-virtuel
prend probablement aux alentours de 10 ou 15 nanosécondes, et
que g++ est devenu plus intelligent que mon harnès de test, et
qu'il a réussi à implémenter inline des fonctions que je voulais
non-inline (et que j'avais mis dans une source à part, exprès
pour empêcher l'optimisation).

Je dirais que la différence entre virtuel et non-virtuel
(facteur 5), c'est à peu près ce à laquelle je m'attendrais sur
une machine généraliste moderne, avec pipeline, prédiction des
branchements, et al. La présentation dont il a été question ici
concernait les systèmes embarqués, qui ont typiquement des
processeurs plus simples, et où je m'attendrais à bien moins de
différence -- peut-être une facteur de deux maximum. (Mais là,
c'est un peu de la spéculation de ma part. Je n'ai pas accès à
de telles machines pour mésurer.)

--
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
Laurent Deniau
wrote:
Laurent Deniau wrote:

Stan wrote:

http://www.research.att.com/~bs/esc99.html




Même si le domaine visé est l'embarqué, le côté didactique
de BS est toujours agréable à lire...




Les temps qu'il donne pour les appels de fonctions souligne
qqchose que j'avais deja remarque il y a qqtemps: les appels
virtuels (ou via des pointeurs de fonction) sont plus rapide
que des appels directs. Mais je pense que ces resultats sont
biaises.



Ou qu'il s'est simplement trompé en copiant quelque chose ?

Si on ne régarde que la page postée, on se pose réelement des
questions. Littéralement, ce qu'il a écrit, c'est qu'un appel
virtuel prend 1,47 *sécondes* -- ce qui est inconcevable, même
sur la machine la plus lente que je n'ai jamais vu. Il a donné
un link à une page où il explique comment il a fait des mésures,
mais je ne trouve aucun chiffre là qui correspond à ce 1,47. En
revanche, il y a un tableau sur cette page aussi, avec ici en
plus des informations importantes comme le nombre d'itérations
et le code réelement testé.

Alors, un premier commentaire s'imposent : Dans le tableau
initial, il cite des temps pour "non-virtual member" : "p->f(1)"
et "virtual member" : "p->g(1)". Noms qu'on rétrouve sur la page
détaillée. Seulement, si on régarde le code, c'est f qui est
virtuel, et g qui ne l'est pas. Plutôt que du biais
intentionnel, je pencherais pour une simple erreur

Ensuite, il faut reconnaître que mésurer des valeurs aussi
petites relève de la défie. Chez moi, par exemple, j'ai un test
qui mésure 1) un appel de fonction inline, 2) un appel non
inline, 3) un appel membre inline, 4) un appel membre non
inline, et 5) un appel virtuel, j'ai :

avec g++ (-O3) :
Testing Inline (1000000000 iterations)...
0.00281 microseconds per iteration
Testing Out of line (1000000000 iterations)...
0.00354 microseconds per iteration
Testing Inline member (1000000000 iterations)...
0.00259 microseconds per iteration
Testing Out of line member (1000000000 iterations)...
0.00131 microseconds per iteration
Testing Virtual (1000000000 iterations)...
0.07121 microseconds per iteration

avec Sun CC (-O4) :
Testing Inline (1000000000 iterations)...
0.00223 microseconds per iteration
Testing Out of line (1000000000 iterations)...
0.0116 microseconds per iteration
Testing Inline member (1000000000 iterations)...
0.00218 microseconds per iteration
Testing Out of line member (1000000000 iterations)...
0.0159 microseconds per iteration
Testing Virtual (1000000000 iterations)...
0.06582 microseconds per iteration

Mais pour être honnête, je ne crois pas trop les mésures
inférieur à une dizaine de nanosécondes par itération. À mon


Pourquoi du moment qu'elles sont mesurees sur une duree significative?

avis, les différences à ce niveau relèvent autant des variations
naturelles (le « jitter ») que des différences réeles.


Qu'est ce que tu entends par la?

Toujours est-il que je crois qu'on peut dire qu'un appel virtuel
prend entre 60 et 75 nanosécondes, qu'un appel non-virtuel


Les temps absolus sont peut parlant (depends des architectures et des
compilateurs). Je prefere de loin un ratio virtuel/non-virtuel.

prend probablement aux alentours de 10 ou 15 nanosécondes, et
que g++ est devenu plus intelligent que mon harnès de test, et
qu'il a réussi à implémenter inline des fonctions que je voulais
non-inline (et que j'avais mis dans une source à part, exprès
pour empêcher l'optimisation).

Je dirais que la différence entre virtuel et non-virtuel
(facteur 5), c'est à peu près ce à laquelle je m'attendrais sur


Je n'ai jamais vu un facteur 5. J'ai vu -10%/+50% au maximum. Et c'est
coherent avec un appel indirect de fonction via un pointeur.

Au passage j'ai une implementation de message dispatch en C (type
Objective-C) qui a un overhead de 40% a 80% par rapport a un appel
direct. Et c'est forcement plus lent qu'un method lookup. Alors j'ai du
mal a imaginer un facteur 5 pour les methodes virtuelles.

Tu ne comparais pas avec de l'inline par harsard?

a+, ld.



Avatar
Jean-Marc Bourguet
Laurent Deniau writes:

Stan wrote:
http://www.research.att.com/~bs/esc99.html
Même si le domaine visé est l'embarqué,
le côté didactique de BS est toujours
agréable à lire...


Les temps qu'il donne pour les appels de fonctions souligne qqchose que
j'avais deja remarque il y a qqtemps: les appels virtuels (ou via des
pointeurs de fonction) sont plus rapide que des appels directs. Mais je
pense que ces resultats sont biaises.


Je pense que pour les processeurs actuels (pipeline profond,
multiple instructions commencées/terminées dans le même
cycle, exécution dans le désordre, exécution spéculative,
prédiction des sauts conditionnels, prédictions des
adresses, deux ou trois niveaux de cache) essayer de mesurer
quelque chose de ce genre n'a plus de sens.

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


Avatar
Laurent Deniau
Jean-Marc Bourguet wrote:
Laurent Deniau writes:


Stan wrote:

http://www.research.att.com/~bs/esc99.html
Même si le domaine visé est l'embarqué,
le côté didactique de BS est toujours
agréable à lire...


Les temps qu'il donne pour les appels de fonctions souligne qqchose que
j'avais deja remarque il y a qqtemps: les appels virtuels (ou via des
pointeurs de fonction) sont plus rapide que des appels directs. Mais je
pense que ces resultats sont biaises.



Je pense que pour les processeurs actuels (pipeline profond,
multiple instructions commencées/terminées dans le même
cycle, exécution dans le désordre, exécution spéculative,
prédiction des sauts conditionnels, prédictions des
adresses, deux ou trois niveaux de cache) essayer de mesurer
quelque chose de ce genre n'a plus de sens.


Pourquoi? L'experience a montre que la mesure est stable et
reproductible. En revanche, je suis d'accord que sur de telle
architecture, l'optimisation manuelle d'implementation (pas
algorithmique) n'a plus de sens sur des instructions aussi courtes.



Avatar
kanze
Laurent Deniau wrote:
wrote:
Laurent Deniau wrote:



[...]
Ensuite, il faut reconnaître que mésurer des valeurs aussi
petites relève de la défie. Chez moi, par exemple, j'ai un
test qui mésure 1) un appel de fonction inline, 2) un appel
non inline, 3) un appel membre inline, 4) un appel membre
non inline, et 5) un appel virtuel, j'ai :

avec g++ (-O3) :
Testing Inline (1000000000 iterations)...
0.00281 microseconds per iteration
Testing Out of line (1000000000 iterations)...
0.00354 microseconds per iteration
Testing Inline member (1000000000 iterations)...
0.00259 microseconds per iteration
Testing Out of line member (1000000000 iterations)...
0.00131 microseconds per iteration
Testing Virtual (1000000000 iterations)...
0.07121 microseconds per iteration

avec Sun CC (-O4) :
Testing Inline (1000000000 iterations)...
0.00223 microseconds per iteration
Testing Out of line (1000000000 iterations)...
0.0116 microseconds per iteration
Testing Inline member (1000000000 iterations)...
0.00218 microseconds per iteration
Testing Out of line member (1000000000 iterations)...
0.0159 microseconds per iteration
Testing Virtual (1000000000 iterations)...
0.06582 microseconds per iteration

Mais pour être honnête, je ne crois pas trop les mésures
inférieur à une dizaine de nanosécondes par itération. À mon


Pourquoi du moment qu'elles sont mesurees sur une duree
significative?


Parce que je constate qu'elles varient d'une exécution à
l'autre. Et que du fait que mon compteur de boucle est surn un
entier 32 bits, je ne peux pas augmenter la durée de façon
illimitée. (Normallement, je considère quelque chose comme cinq
minutes pour chaque mésure comme un minimum. Mais ici, j'en suis
loin.)

avis, les différences à ce niveau relèvent autant des
variations naturelles (le « jitter ») que des différences
réeles.


Qu'est ce que tu entends par la?


Le « jitter », c'est les petites variations dans les mésures
d'une mésure à l'autre.

Toujours est-il que je crois qu'on peut dire qu'un appel
virtuel prend entre 60 et 75 nanosécondes, qu'un appel
non-virtuel


Les temps absolus sont peut parlant (depends des architectures
et des compilateurs). Je prefere de loin un ratio
virtuel/non-virtuel.


Mais ça aussi dépend de l'architectures et des compilateurs. Et
beaucoup. Disons que sur une machine généraliste moderne, je
m'attendrais à un rapport entre 1:2 et 1:10. Sur des machines
plus primitives (embarquées ou anciennes), j'ai déjà vu
nettement moins de 1:2.

prend probablement aux alentours de 10 ou 15 nanosécondes,
et que g++ est devenu plus intelligent que mon harnès de
test, et qu'il a réussi à implémenter inline des fonctions
que je voulais non-inline (et que j'avais mis dans une
source à part, exprès pour empêcher l'optimisation).

Je dirais que la différence entre virtuel et non-virtuel
(facteur 5), c'est à peu près ce à laquelle je m'attendrais
sur


Je n'ai jamais vu un facteur 5. J'ai vu -10%/+50% au maximum.
Et c'est coherent avec un appel indirect de fonction via un
pointeur.


On est déjà à plus de quatre ci-dessus, sur un Sparc plutôt
ancien. Des personnes de chez HP (à l'époque) m'ont assuré que
sur l'architecture PA Risc, c'était pire.

Le problème, c'est que c'est un branchement indirect. Ce qui
pose des problèmes pour la prédiction des branchements. Dans le
cas du HP, d'après ce que j'ai compris, il en résulte un flush
du pipeline à chaque coup.

Note que je ne mésure que le coup de l'appel d'une fonction
triviale (qui renvoie un int constante) : « i = f() ; ». Il y a
donc non seulement l'appel de la fonction proprement dit, mais
aussi chargement du pointeur this, chargement et affectation de
la valeur de retour, et le retour lui-même. La différence dans
le temps de l'appel lui-même est donc encore plus grande.

De l'autre côté, il faut le comparer à ce qu'on fait dans la
fonction. Dès qu'il y a une multiplication ou une division
entière, avec des termes non constantes, le temps de l'appel
devient négligeable. Sur ma machine... on ne peut pas faire de
généralités.

Au passage j'ai une implementation de message dispatch en C
(type Objective-C) qui a un overhead de 40% a 80% par rapport
a un appel direct.


Sur quelle machine ? Mésuré comment ?

Et c'est forcement plus lent qu'un method lookup. Alors j'ai
du mal a imaginer un facteur 5 pour les methodes virtuelles.


C'est cependant le cas, au moins sur les machines modernes.

Tu ne comparais pas avec de l'inline par harsard?


Est-ce que tu as au moins lu ce que j'ai posté ? J'ai fait
plusieurs mésures, dans des conditions différentes. J'ai bien
l'impression que g++ a inliné les fonctions, parce qu'il n'y a
pas de différences entre les temps pour les fonctions inlinées,
et les temps pour les fonctions non-virtuelles. Et que ces temps
pour les fonctions inlinées ressemblent à peu près à celles de
Sun CC. Dans le cas de Sun CC, en revanche, il y a bien une
différence.

--
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
Laurent Deniau
wrote:
Mais pour être honnête, je ne crois pas trop les mésures
inférieur à une dizaine de nanosécondes par itération. À mon




Pourquoi du moment qu'elles sont mesurees sur une duree
significative?



Parce que je constate qu'elles varient d'une exécution à
l'autre. Et que du fait que mon compteur de boucle est surn un
entier 32 bits, je ne peux pas augmenter la durée de façon
illimitée. (Normallement, je considère quelque chose comme cinq
minutes pour chaque mésure comme un minimum. Mais ici, j'en suis
loin.)


De mon cote j'utilise environ 10 a 15 secondes par test, ce qui est
suffisant et permet d'utiliser un int (ou un unsigned) pour la boucle.

avis, les différences à ce niveau relèvent autant des
variations naturelles (le « jitter ») que des différences
réeles.




Qu'est ce que tu entends par la?



Le « jitter », c'est les petites variations dans les mésures
d'une mésure à l'autre.


C'est ce que je trouve etonnant. Avec 10-15 secondes, j'ai une stabilite
a +/- 5% relatif ce qui me suffit.

Toujours est-il que je crois qu'on peut dire qu'un appel
virtuel prend entre 60 et 75 nanosécondes, qu'un appel
non-virtuel




Les temps absolus sont peut parlant (depends des architectures
et des compilateurs). Je prefere de loin un ratio
virtuel/non-virtuel.



Mais ça aussi dépend de l'architectures et des compilateurs. Et


Oui mais ce que l'on cherche, ce n'est pas de savoir combien de temps
met une operation, mais son efficacite par rapport a une autre
operation. C'est cela qui conditionne le comportement des programmeurs.

beaucoup. Disons que sur une machine généraliste moderne, je
m'attendrais à un rapport entre 1:2 et 1:10. Sur des machines
plus primitives (embarquées ou anciennes), j'ai déjà vu
nettement moins de 1:2.


Jamais vu de tel rapport. Heureusement parce que OOC n'a que des
methodes virtuelles. Je n'aurais jamais fait ca si je n'etais pas
convaincu que virtuel = direct +/- 10% (note que j'ai mesure des appels
virtuel plus rapide que des appels directs).

prend probablement aux alentours de 10 ou 15 nanosécondes,




Le temps absolu ne me parle pas. Je considere qu'un appel direct est la
reference (ratio de 1) et je compare le reste a ca. Ca sous-entend que
la rapidite d'un appel direct me suffit comme etalon de mesure et donc
je ne me preoccupe pas des mesures inline. Par contre je fais tres
attention a bien avoir des appels direct non-inlines.

et que g++ est devenu plus intelligent que mon harnès de
test, et qu'il a réussi à implémenter inline des fonctions
que je voulais non-inline (et que j'avais mis dans une
source à part, exprès pour empêcher l'optimisation).




Je pense que c'est effectivement ce que montre tes resultats.

Je dirais que la différence entre virtuel et non-virtuel
(facteur 5), c'est à peu près ce à laquelle je m'attendrais
sur




Je n'ai jamais vu un facteur 5. J'ai vu -10%/+50% au maximum.
Et c'est coherent avec un appel indirect de fonction via un
pointeur.



On est déjà à plus de quatre ci-dessus, sur un Sparc plutôt
ancien. Des personnes de chez HP (à l'époque) m'ont assuré que
sur l'architecture PA Risc, c'était pire.


Je continue de penser que tu mesures inline vs virtual (ou direct).

Le problème, c'est que c'est un branchement indirect. Ce qui
pose des problèmes pour la prédiction des branchements. Dans le
cas du HP, d'après ce que j'ai compris, il en résulte un flush
du pipeline à chaque coup.


Personnellement je ne crois pas. Cela ecroulerait les performances de
facon mesurable et en ferait une architecture tres inefficace (mais je
n'ai pas HP sous la main...). La plupart des langages+projets
aujourd'hui utilisent des appels indirects (interfaces).

Note que je ne mésure que le coup de l'appel d'une fonction
triviale (qui renvoie un int constante) : « i = f() ; ». Il y a
donc non seulement l'appel de la fonction proprement dit, mais
aussi chargement du pointeur this, chargement et affectation de
la valeur de retour, et le retour lui-même. La différence dans
le temps de l'appel lui-même est donc encore plus grande.


Quand je fais ce type de tests, je fais avec:
- 0 arg + 0 ret
- 1 arg + 0 ret
- 0 arg + 1 ret
- 1 arg + 1 ret
- 2 args

Ca me donne une idee des performances plus realiste.

De l'autre côté, il faut le comparer à ce qu'on fait dans la
fonction. Dès qu'il y a une multiplication ou une division
entière, avec des termes non constantes, le temps de l'appel
devient négligeable. Sur ma machine... on ne peut pas faire de
généralités.


On ne parle que du cout de l'appel, le reste est speculatif. C'est
l'argument des utilisateurs d'Objective-C qui clame que le message
dispatch (bien que tres lent, 800% pour gcc) est suffisament rapide en
raison du code de la fonction. Et quand tu demandes comment faire une
classe Complex efficace (je te rappelle que les data membres ne sont
accessible que par des methodes), ils te repondent que Objective-C n'est
pas fait pour ca...

Au passage j'ai une implementation de message dispatch en C
(type Objective-C) qui a un overhead de 40% a 80% par rapport
a un appel direct.



Sur quelle machine ? Mésuré comment ?


Linux IA32, gcc 3.3.5
clock_t, duree > 10 sec, elimination des couts "parasites", ratio.

C'est une implemantation qui ne supporte pas le message forwarding avec
autre chose que [jusqu'a] deux objects (idem Stepstone objc). Cette
remarque est importante car elle "cristalise" le choix de
l'implementation du dispatcher:

- soit le message dispatch recherche le pointeur de fonction a partir du
selecteur et le renvoi a l'appelant qui l'invoque (POC, mon dispatcher
version rapide). Cette techique a l'avantage de permettre d'inliner le
code de recherche dans le cache qui avec mes tests arrive a 99,7% de
hits sur 1000000 de messages generes.

- soit tout est transfere (argument inclus), au dispatcher qui forward
le tout a la methode une fois trouvee (GCC, mon dispatcher version
lente). Cette solution permet de forwarder n'importe quel message, au
prix systematique d'un double appel de fonction, donc performance > 200%
(240-300% dans mon cas).

Ensuite tu as le choix de l'implementation du cache: GCC utilise un
sparse array a 2 (ou 3 niveaux configurable a la compilation), le mien
utilise une hash table.

Et c'est forcement plus lent qu'un method lookup. Alors j'ai
du mal a imaginer un facteur 5 pour les methodes virtuelles.


C'est cependant le cas, au moins sur les machines modernes.


Est-ce que les appels directs sont dans une TU sparee avec une
compilation separee? Parce que un facteur 5, c'est ce que je mesure
entre inline et direct (ou virtual).

Tu ne comparais pas avec de l'inline par harsard?



D'ou ma question ;-)

Est-ce que tu as au moins lu ce que j'ai posté ? J'ai fait


Oui mais je n'ai ni le code, ni la methode de compilation.

plusieurs mésures, dans des conditions différentes. J'ai bien
l'impression que g++ a inliné les fonctions, parce qu'il n'y a


C'est ce que je pense.

pas de différences entre les temps pour les fonctions inlinées,
et les temps pour les fonctions non-virtuelles. Et que ces temps


Bingo...

pour les fonctions inlinées ressemblent à peu près à celles de
Sun CC. Dans le cas de Sun CC, en revanche, il y a bien une
différence.


a+, ld.

PS. essaye de pas doubler la longueur du post, j'en ai deja rajoute pas
mal ;-)



Avatar
Jean-Marc Bourguet
Laurent Deniau writes:

Jean-Marc Bourguet wrote:
Laurent Deniau writes:

Stan wrote:

http://www.research.att.com/~bs/esc99.html
Même si le domaine visé est l'embarqué,
le côté didactique de BS est toujours
agréable à lire...


Les temps qu'il donne pour les appels de fonctions souligne qqchose que
j'avais deja remarque il y a qqtemps: les appels virtuels (ou via des
pointeurs de fonction) sont plus rapide que des appels directs. Mais je
pense que ces resultats sont biaises.
Je pense que pour les processeurs actuels (pipeline profond,

multiple instructions commencées/terminées dans le même
cycle, exécution dans le désordre, exécution spéculative,
prédiction des sauts conditionnels, prédictions des
adresses, deux ou trois niveaux de cache) essayer de mesurer
quelque chose de ce genre n'a plus de sens.


Pourquoi? L'experience a montre que la mesure est stable et
reproductible.


Mon idee est que maintenant essayer de separer les choses de leur
contexte n'a plus guere de sens parce que le cout depend fortement du
contexte; ca ne m'etonnerait pas du tout qu'on se retrouve meme
parfois avec des couts negatifs.

Dans le cas present, la premiere chose a noter, c'est que quand on
compile pour des objets partages (code PIC), on se retrouve souvent
(au moins sous Unix, je suppose pour Windows aussi) a ce que des
appels "directs" soient aussi en fait des appels indirects (je suppose
que B.S et toi savez ca et en avez tenu compte).

Deuxieme chose, ensuite, quels sont les facteurs qui comptent dans le
temps d'execution d'un appel?

1/ est-ce que la page est en memoire
2/ est-ce que l'entree de la page est dans le TLB
3/ est-ce que la fonction est dans le cache
4/ est-ce que l'appel est predictible (est-ce qu'il est pris ou non,
est-ce que sa destination est predictible ou non).

A mon avis ces quatres facteurs vont en pratique dominer la difference
entre un appel direct et un appel indirect et ces quatres facteurs
sont dependants du contexte.

Vu la simplicite ici si la mesure est bien faite, on ne mesurera que
le cas ou la reponse a ces quatres questions est oui. Je ne suis pas
sur que ce soit une donnee pertinente (ca neglige en particulier
l'effet qu'il est normalement plus difficile de prevoir la destination
d'un appel indirect).

J'ai ecrit si la mesure est bien faite car je suis un peu etonne que
le cas appel direct soit plus lent que le cas appel indirect et je me
demande si un facteur (le premier qui vient a l'esprit est le partage
d'une ligne de cache entre le code appelant et le code de la fonction
directe; dans ce cas le cache est recharge a chaque fois tandis que
sans partage le cache contient tout ce qui est necessaire en
permanence; mais celui-ci je suppose que tu l'as verifie) n'a pas ete
ignore. J'aurais tendance a refaire tourner ca avec un simulateur
architectural avant de deduire quoi que ce soit a ce niveau. Il y en
a un ici: http://www.simplescalar.com mais je ne l'ai jamais utilise.

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




Avatar
Laurent Deniau
Jean-Marc Bourguet wrote:
Mon idee est que maintenant essayer de separer les choses de leur
contexte n'a plus guere de sens parce que le cout depend fortement du
contexte; ca ne m'etonnerait pas du tout qu'on se retrouve meme
parfois avec des couts negatifs.

Dans le cas present, la premiere chose a noter, c'est que quand on
compile pour des objets partages (code PIC), on se retrouve souvent
(au moins sous Unix, je suppose pour Windows aussi) a ce que des
appels "directs" soient aussi en fait des appels indirects (je suppose
que B.S et toi savez ca et en avez tenu compte).


Oui, mais dans ce cas, on a un appel indirect. On peut donc tester la
rapidite d'un appel indirect hors PIC et en deduire le coup d'un appel
direct avec PIC.

Deuxieme chose, ensuite, quels sont les facteurs qui comptent dans le
temps d'execution d'un appel?

1/ est-ce que la page est en memoire
independant de direct-indirect


2/ est-ce que l'entree de la page est dans le TLB
independant de direct-indirect


3/ est-ce que la fonction est dans le cache
independant de direct-indirect


4/ est-ce que l'appel est predictible (est-ce qu'il est pris ou non,
est-ce que sa destination est predictible ou non).
la d'accord, et c'est justement en partie ce que l'on mesure.


A mon avis ces quatres facteurs vont en pratique dominer la difference
entre un appel direct et un appel indirect et ces quatres facteurs
sont dependants du contexte.


Je pense que les trois premiers ne sont pas representatif du coup d'un
appel direct - indirect.

Mon point de vue est de dire que les appels virtuels sont en general
aussi rapides que les appels direct. L'objectif est d'eviter que des
programmeurs partent dans des delires algorithmiques pour ne pas
utiliser les fonctions virtuelles pour cause d'efficacite. La STL est en
partie un exemple avec le manque de flexibilite qui va avec (voir par
exemple la proposition sur les allocateurs de Lakos).

Vu la simplicite ici si la mesure est bien faite, on ne mesurera que
le cas ou la reponse a ces quatres questions est oui. Je ne suis pas


Apres la premiere boucle 1-3 sont ok et 4 est peut-etre optimise.

sur que ce soit une donnee pertinente (ca neglige en particulier
l'effet qu'il est normalement plus difficile de prevoir la destination
d'un appel indirect).

J'ai ecrit si la mesure est bien faite car je suis un peu etonne que
le cas appel direct soit plus lent que le cas appel indirect et je me


Je l'ai effectivement mesure. Les raisons (obscures) envisageable serait
l'optimisation des registres ou l'alignement des donnees/code.

demande si un facteur (le premier qui vient a l'esprit est le partage
d'une ligne de cache entre le code appelant et le code de la fonction


C'est ce que je pense aussi. En reformulant le code sans changement de
semantique, le resultat etait parfois identique, parfois different (dans
les deux sens). C'est pour ca que je parlais de difficulte a optimiser
ce genre de chose. Il n'y a que le compilateur (et encore) qui puisse
faire qqchose.

directe; dans ce cas le cache est recharge a chaque fois tandis que
sans partage le cache contient tout ce qui est necessaire en
permanence; mais celui-ci je suppose que tu l'as verifie) n'a pas ete
ignore. J'aurais tendance a refaire tourner ca avec un simulateur
architectural avant de deduire quoi que ce soit a ce niveau. Il y en
a un ici: http://www.simplescalar.com mais je ne l'ai jamais utilise.


Moi non plus. Par contre sur le probleme du cache, la doc AMD Athlon
montre comment gagner un facteur 2 sur les copies memoire (memcpy) en
prechargeant le cache. Le code assembleur est environ 20 fois plus long
mais 2 fois plus rapide. Tres difficile a prevoir ;-)

a+, ld.

1 2 3