OVH Cloud OVH Cloud

memcpy, ATLAS et C++

11 réponses
Avatar
plagne.laurent
Bonjour =E0 tous,

je viens de faire des mesures de perfs sur mon syst=E8me linux (X86) qui
me laissent un peu songeur...

Quand je fais une copy de vecteur de r=E9els avec une simple boucle (les
resultats sont =E9quivalent sur des pointeur C o=F9 des vecteurs STL),
j'obtiens des perfs qui sont les m=EAmes que si je passe par memcpy(..).
Je m'interesse en particulier aux grand vecteur (eg. size=3D10e6).

for (int i=3D0 , i <size ; i++) X[i]=3DY[i]

Sur ma machine, les perfs hors cache sont +/- constantes ( +/- 50
Millions d'=E9lements copi=E9s par seconde)

A ce point, tout va bien et le compilo (gcc) semble faire un boulot
correct...

Sauf que : Si je fais la m=EAme op=E9ration via ATLAS ( ATL_dcopy(..)),
j'obtiens une performance meilleure d'un facteur 2 (+/- 100 Millions
d'=E9l=E9ments copi=E9s par seconde).

Trois questions :
1 Vous semble-t-il normal qu'une op=E9ration aussi =E9l=E9mentaire soit
"nativement" aussi peu optimis=E9e ?
2 Est-il possible de modifier l'environnement pour que la version
boucle soit efficace (modif de la libc ?)
3 Je n'ai rien compris au film ?

P=2ES. Je d=E9veloppe une biblioth=E8que o=F9 les op=E9rations globale sur
les vecteurs sont d=E9finies et sil'utilisateur
=E9crit X=3DY cela appel ATLAS si la lib est install=E9e. Mais j'ai quand
m=EAme l'impression que ce bon niveau de perf
devrait =EAtre accessible directement depuis le langage...

Merci pour vos lumi=E8res !

Laurent

10 réponses

1 2
Avatar
Marc Boyer
Le 07-12-2006, a écrit :
Quand je fais une copy de vecteur de réels avec une simple boucle (les
resultats sont équivalent sur des pointeur C où des vecteurs STL),
j'obtiens des perfs qui sont les mêmes que si je passe par memcpy(..).


Avec quel niveau d'optimisation ?

Je m'interesse en particulier aux grand vecteur (eg. sizee6).

for (int i=0 , i <size ; i++) X[i]=Y[i]

Sur ma machine, les perfs hors cache sont +/- constantes ( +/- 50
Millions d'élements copiés par seconde)

A ce point, tout va bien et le compilo (gcc) semble faire un boulot
correct...


Et as-tu testé std::copy ?

Sauf que : Si je fais la même opération via ATLAS ( ATL_dcopy(..)),
j'obtiens une performance meilleure d'un facteur 2 (+/- 100 Millions
d'éléments copiés par seconde).


D'après le nom, elle est optimisée pour gérer des doubles.

Trois questions :
1 Vous semble-t-il normal qu'une opération aussi élémentaire soit
"nativement" aussi peu optimisée ?


Je ne considère pas que copier 1e6 doubles soit une opération
élémentaire.

2 Est-il possible de modifier l'environnement pour que la version
boucle soit efficace (modif de la libc ?)


Aucune idée, et cela n'a rien à voir avec le C++ standard, mais
avec la doc de ton compilo.

3 Je n'ai rien compris au film ?


Quelles connaissance en architecture Intel as-tu ?

P.S. Je développe une bibliothèque où les opérations globale sur
les vecteurs sont définies et sil'utilisateur
écrit X=Y cela appel ATLAS si la lib est installée. Mais j'ai quand
même l'impression que ce bon niveau de perf
devrait être accessible directement depuis le langage...


Ben, si c'était déjà dans le langage, pourquoi des gens se
seraient-ils amusé à écrire ATL_dcopy ?

Marc Boyer
--
Si tu peux supporter d'entendre tes paroles
Travesties par des gueux pour exciter des sots
IF -- Rudyard Kipling (Trad. André Maurois)

Avatar
Laurent Deniau
wrote:
Bonjour à tous,

je viens de faire des mesures de perfs sur mon système linux (X86) qui
me laissent un peu songeur...

Quand je fais une copy de vecteur de réels avec une simple boucle (les
resultats sont équivalent sur des pointeur C où des vecteurs STL),
j'obtiens des perfs qui sont les mêmes que si je passe par memcpy(..).
Je m'interesse en particulier aux grand vecteur (eg. sizee6).

for (int i=0 , i <size ; i++) X[i]=Y[i]

Sur ma machine, les perfs hors cache sont +/- constantes ( +/- 50
Millions d'élements copiés par seconde)

A ce point, tout va bien et le compilo (gcc) semble faire un boulot
correct...

Sauf que : Si je fais la même opération via ATLAS ( ATL_dcopy(..)),
j'obtiens une performance meilleure d'un facteur 2 (+/- 100 Millions
d'éléments copiés par seconde).

Trois questions :
1 Vous semble-t-il normal qu'une opération aussi élémentaire soit
"nativement" aussi peu optimisée ?


'aussi peu' est peut-etre un peu exagere.

2 Est-il possible de modifier l'environnement pour que la version
boucle soit efficace (modif de la libc ?)


En faisant la meme chose que ATLAS?

Lorsque j'ai developpe la SL++ (ca remonte a 98), les problemes de
performances venaient de la gestion des caches. Blitz++ est arrive aux
memes conclusions un peu avant. Optimiser du code portable pour utiliser
au mieux le cache n'est pas trivial surtout quand les operations
balayent lineairement la memoire. Il y a en premier lieu des
considerations d'alignement des donnees comme par exemple aligner les
blocs memoire sur 64, 128 ou 256 bytes. Ensuite pour les tres gros
blocs, utiliser des pages systeme (alignement et taille). Ces
contraintes sont plus fortes que celles du langages mais reste possible
de maniere portable. Le gain peut aller jusqu'a un facteur 2.

Ensuite on passe au niveau de l'assembleur. En utilisant les proprietes
propres aux caches, les techniques de pre-chargement permettent un gain
d'encore un facteur 2. Mais ce n'est plus du tout portable. C'est tres
bien decrit et compare dans la doc des processeurs AMD (ch 5 'Cache and
Memory Optimisation' du 'Code Optimization Guide'. Il y a surement la
meme chose dans les guides d'Intel et des autres constructeurs.

Voila peut-etre ce que ATLAS fait et qui depasse le cadre de la STL.

a+, ld.

Avatar
Mathias Gaunard
Laurent Deniau wrote:

Voila peut-etre ce que ATLAS fait et qui depasse le cadre de la STL.


Je vois pas pourquoi cela dépasserait du cadre.
Une implémentation de la bibliothèque standard n'a pas à être portable.
C'est justement l'intérêt qu'elle soit standard : chaque compilateur
peut écrire la sienne optimisée pour son compilateur.

En fait il est même difficile de distinguer compilateur et bibliothèque
standard, car le moyen le plus performant de l'implémenter et de faire
de cette bibliothèque un élément du compilateur.

Bien sûr quand le compilateur lui-même est portable, c'est assez compliqué.

Avatar
Laurent Deniau
Mathias Gaunard wrote:
Laurent Deniau wrote:

Voila peut-etre ce que ATLAS fait et qui depasse le cadre de la STL.



Je vois pas pourquoi cela dépasserait du cadre.
Une implémentation de la bibliothèque standard n'a pas à être portable.
C'est justement l'intérêt qu'elle soit standard : chaque compilateur
peut écrire la sienne optimisée pour son compilateur.


Vous confondez portabilite hardware (qui concerne les points que je
site) et portabilite software. Je ne connais pas de vendeur de
compilateur qui souhaite specialiser son compilateur ou sa bibliotheque
pour un hardware unique, a part peut-etre Micro$oft qui en a les moyens.

En fait il est même difficile de distinguer compilateur et bibliothèque
standard, car le moyen le plus performant de l'implémenter et de faire
de cette bibliothèque un élément du compilateur.


C'est ce que fait par exemple glib avec gcc pour les points critiques.
Mais dans ce cas, le code devient tres complique, donc couteux a
modifier et maintenir.

Bien sûr quand le compilateur lui-même est portable, c'est assez compliqué.


Ben oui. D'autre part, regardez la taille du code des bibliotheques
optimisees multi-plateformes par rapport aux fonctionnalites fournies,
comme par exemple BLAS (ce qui interesse le PO). Le ratio est en general
tres faible... Pour finir, la STL n'a pas vocation a etre ultra
optimisee, mais plutot a etre largement disponible (donc comportement
standardise), stable et polyvalente.

Pour ce qui concerne le PO et le type d'optimisation qu'il cherche, je
lui recommanderait de regarder si son compilateur fourni des valarray
optimises pour les movements memoires, mais meme si c'est le cas, ca
reste dependant du compilateur.

a+, ld.


Avatar
Fabien LE LEZ
On Thu, 07 Dec 2006 18:23:51 +0100, Mathias Gaunard :

Une implémentation de la bibliothèque standard n'a pas à être portable.
C'est justement l'intérêt qu'elle soit standard : chaque compilateur
peut écrire la sienne optimisée pour son compilateur.


Oui et non.

La STL de VC++, par exemple, peut être optimisée pour Windows, et pour
Intel 386 (voire, à la rigueur, Intel Pentium).
Il est possible qu'on puisse optimiser encore plus, avec une version
Intel Pentium (compatible tous processeurs), une version "Intel
récent", et une version "AMD récent".
(D'ailleurs, il me semble que certains logiciels, notamment de
compression ou décompression vidéo, sont fournis en trois versions.)

Pour la STL de g++, c'est plus compliqué : je vois mal l'auteur de
cette implémentation s'amuser à faire une implémentation par système.

Pour la STL de SGI, c'est encore plus compliqué, puisque le
développeur n'a a priori aucune idée du type de machine ou d'OS sur
lequel l'implémentation sera utilisée.

Avatar
plagne.laurent
Merci pour vos réponses. Je suis en train de faire les tests que vous
avez suggérés (std::copy, valarray ?). Je vais aussi voir ce que
donne le compilo intel (et aussi le fortran). En tout état de cause,
et malgré certains avis, mes questions ne me semblent toujours pas
tellement déplacées. En effet, je ne connais rien à la conception
des compilateurs mais je crois toujours que leur rôle est de
transformer, d'une façon aussi efficace que possible, du code source
(écrit par des humains) en langage machine. Étant donné qu'il existe
des outils open-source (e.g. ATLAS) qui fournissent des
implémentations d'opérations élémentaires (je maintiens !)
optimisées pour des machines cible données, il semble à priori
possible d'inclure ces fonctionnalités dans le compilo.

De mon propre point de vue, l'inefficacité du compilo est presque un
avantage puisque cela justifie encore plus l'usage de librairies
spécialisées. Au départ un de mes buts était de fournir des
opération globales (X+=a*Y+b*Z) sur des vecteurs multi-dimentionels
(vecteur<vecteur<..>>) en étendant l'usage des Expression Template
(à la Blitz++) à ce cas. Les implémentations classiques de l'E.T.
(via un CRTP), se fondent sur la définition d'un opérateur [] pour
les expressions qui retourne un résultat du type des éléments de
vecteurs. Mon idée a priori était que cela serait moins efficace que
les versions boucles dans le cas multidimensionnel du fait de la
création d'un élément de vecteur qui se trouve être un vecteur dans
le cas multidimensionnel. Quelle ne fut pas ma surprise de cosntater
que c'était l'inverse et que via ATLAS, j'obtenais des résultats E.T.
supérieurs aux boucles écrites à la main... De là, l'idée de
recoder des expressions vectorielles simples (vecteurs
mono-dimensionnels) en simulant des vecteurs 2D où la taille des
blocks est choisie de façon optimale. A première vue ça marche et
j'ai un gain de perf d'environ 40% sur l'écriture boucle... Avant
cela, je n'avais jamais entendu parler de blocking utile pour des
opérations vecteurs-vecteurs hors cache (seulement pour du
matrice-matrice).

On en apprend tous les jours... ;-)
Avatar
James Kanze
wrote:
Merci pour vos réponses. Je suis en train de faire les tests que vous
avez suggérés (std::copy, valarray ?). Je vais aussi voir ce que
donne le compilo intel (et aussi le fortran). En tout état de cause,
et malgré certains avis, mes questions ne me semblent toujours pas
tellement déplacées. En effet, je ne connais rien à la conception
des compilateurs mais je crois toujours que leur rôle est de
transformer, d'une façon aussi efficace que possible, du code source
(écrit par des humains) en langage machine. Étant donné qu'il existe
des outils open-source (e.g. ATLAS) qui fournissent des
implémentations d'opérations élémentaires (je maintiens !)
optimisées pour des machines cible données, il semble à priori
possible d'inclure ces fonctionnalités dans le compilo.


Je suis d'accord jusqu'un certain point. Dans l'absolu, le rôle
du compilateur, c'est de rendre ma vie, en tant que programmeur,
la plus facile possible. Si le compilateur doit générer un code
efficace, c'est parce que certaines applications en ont besoin,
et que c'est bigrement difficile pour le programmeur de
l'obtenir sans accéder au niveau assembleur. En compilateur qui
ne génère pas du code optimal ne fait donc pas son boulot.

Ceci dit, il faut bien reconnaître que l'efficacité du code
généré n'est pas le seul critère. Les auteurs des compilateurs
disposent des ressources finies, et ne peuvent pas tout faire.
Et en ce qui me concerne, si je dois choisir entre des messages
d'erreur clairs et compréhensibles, et les derniers petits bouts
de performances, je sais ce que je choisirais. (Note que je ne
dis pas ça pour te contradire, simplement pour rélativiser tes
dires. Et dans la pratique, il faut dire que la plupart du
temps, on n'a ni les performances, ni les messages d'erreur
clairs et compréhensibles.)

Enfin, les besoins des utilisateurs sont variés, et beaucoup
d'utilisateurs veulent un exécutable qui marche sur le plus
grand nombre de machines possibles (sans aller à l'extrème
d'imposer une machine virtuelle universelle). Sur Sparc, par
exemple, et Sun CC, et g++ génère du code qui s'exécute sur tous
les Sparcs jamais fabriqués, même s'il existe des instructions
plus performantes sur des Sparcs plus récents. Dans les deux
cas, il existe des options pour générer du code plus efficace
sur un Sparc moderne, mais qui ne s'exécutera pas sur des Sparc
plus anciens. Je ne connais pas le monde des processeurs Intel
actuels en detail, mais est-ce que les différences que tu
constates pourraient être en partie dues à ce genre de problème.
Et que dans le cas de la bibliothèque tierce qui fait mieux, que
soit la bibliothèque ne marche pas sur des machines bas de gamme
anciennes, soit qu'elle détermine le type de machine, et en
charge un objet dynamique différent en fonction du type ?

Note que cette dernière solution, très efficace dans la
pratique, est très difficile de mettre en oeuvre pour les
parties de la bibliothèque templatées.

--
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
Marc Boyer
Le 08-12-2006, a écrit :
Merci pour vos réponses. Je suis en train de faire les tests que vous
avez suggérés (std::copy, valarray ?). Je vais aussi voir ce que
donne le compilo intel (et aussi le fortran). En tout état de cause,
et malgré certains avis, mes questions ne me semblent toujours pas
tellement déplacées.


Personne n'a dit que tu étais hors thème.

En effet, je ne connais rien à la conception
des compilateurs mais je crois toujours que leur rôle est de
transformer, d'une façon aussi efficace que possible, du code source
(écrit par des humains) en langage machine.


S'il s'agit de croyance alors, ça devient plus difficile de discuter.
Pour moi, un compilo transforme du code source en langage machine
en faisant des vérifications.
Son objectif, c'est de faire gagner du temps au développeur par
rapport à une écriture directe en ASM.
Mais les programmeurs n'ont pas tous pour unique objectif
d'avoir un code le plus rapide possible. J'aurais même tendance
à dire qu'en 2006, le défis majeur de l'informatique c'est plus
la robustesse, la maintenabilité et l'interopérabiliuté que la
vitesse (ce qui n'a pas toujours été le cas).

Étant donné qu'il existe
des outils open-source (e.g. ATLAS) qui fournissent des
implémentations d'opérations élémentaires (je maintiens !)
optimisées pour des machines cible données, il semble à priori
possible d'inclure ces fonctionnalités dans le compilo.


A quel coût ? Performance, portabilité et maintenabilité
sont généralement des objectifs antagonistes.
Après, chaque compilo se place sur un certain segment.
Par exemple, Sun CC et icc ciblent des archis spécifiques,
et puisqu'ils laissent tomber un objectif, on est en droit
d'attendre qu'ils soient meilleurs sur les autres.

De mon propre point de vue, l'inefficacité du compilo est presque un
avantage puisque cela justifie encore plus l'usage de librairies
spécialisées.


Bien sur. Des outils spécifiques seront toujours plus adaptés
à un problème donné qu'un outil particulier. C'est vrai quelque
soit le domaine: une scie cloche est plus pratique pour
faire de jolis ronds qu'une scie sauteuse. Personne pourtant
n'utilise cette comparaison pour dire que la scie sauteuse
est mal faite.

Au départ un de mes buts était de fournir des
opération globales (X+=a*Y+b*Z) sur des vecteurs multi-dimentionels
(vecteur<vecteur<..>>) en étendant l'usage des Expression Template
(à la Blitz++) à ce cas. Les implémentations classiques de l'E.T.
(via un CRTP), se fondent sur la définition d'un opérateur [] pour
les expressions qui retourne un résultat du type des éléments de
vecteurs. Mon idée a priori était que cela serait moins efficace que
les versions boucles dans le cas multidimensionnel du fait de la
création d'un élément de vecteur qui se trouve être un vecteur dans
le cas multidimensionnel. Quelle ne fut pas ma surprise de cosntater
que c'était l'inverse et que via ATLAS, j'obtenais des résultats E.T.
supérieurs aux boucles écrites à la main... De là, l'idée de
recoder des expressions vectorielles simples (vecteurs
mono-dimensionnels) en simulant des vecteurs 2D où la taille des
blocks est choisie de façon optimale. A première vue ça marche et
j'ai un gain de perf d'environ 40% sur l'écriture boucle... Avant
cela, je n'avais jamais entendu parler de blocking utile pour des
opérations vecteurs-vecteurs hors cache (seulement pour du
matrice-matrice).

On en apprend tous les jours... ;-)


C'est peut-être cela l'expérience: relativiser ses connaissances
avant de faire des jugements à l'emporte pièce.

Marc Boyer
--
Si tu peux supporter d'entendre tes paroles
Travesties par des gueux pour exciter des sots
IF -- Rudyard Kipling (Trad. André Maurois)

Avatar
plagne.laurent
Le LunDi au soL... et non.
Merci pour vos réponses. Force est de reconnaître que vous avez
raison de vous placer du point de vue de l'intéret général tant le
C++ est largement et diversement utilisé.

De ma (petite) fenêtre, où j'utilise le C++ dans le contexte du
calcul (intensif) scientifique, l'equilibre entre la performance et les
autres enjeux de la conception de code (portabilité,
maintenabilité,..) n'est pas aussi tranché. Le point de vue étant
toujours prioritairement définis par l'endroit d'où l'on regarde,
j'ai exagéré l'importance de mes mesures.

Pour ma part je n'avais jamais observé la possibilité d'un gain de
performance pour des opérations vectorielles hors cache que je pensais
seulement et simplement limitées par la bande passante d'une machine
donnée. Les vieilles mesures disponibles (ATLAS, blitz++,...) sur le
site http://projects.opencascade.org/btl/ sont à mettre à jour
d'urgence !

Bonne journée à tous.

Laurent
Avatar
Jean-Marc Desperrier
Laurent Deniau wrote:
Sauf que : Si je fais la même opération via ATLAS ( ATL_dcopy(..)),



Ca serait bien d'envoyer un pointeur du bon coté :
http://math-atlas.sourceforge.net/

Histoire que les gens ne s'égare pas du coté d'une librairie pour jeux
vidéo :-)

1 Vous semble-t-il normal qu'une opération aussi élémentaire soit
"nativement" aussi peu optimisée ?


'aussi peu' est peut-etre un peu exagere.


Quand on voit le developer d'Atlas apprendre à l'équipe de gcc comment
*vraiment* optimiser on se dit qu'effectivement ce n'est pas du "aussi
peu". Cf http://gcc.gnu.org/bugzilla/show_bug.cgi?id'827

2 Est-il possible de modifier l'environnement pour que la version
boucle soit efficace (modif de la libc ?)


En faisant la meme chose que ATLAS?


Donc en laissant tourner la machine pendant le temps nécessaire pour
qu'elle fasse des tests sytématique de toutes les combinaisons de flag
et d'ordre de code possible pour trouver quelle la version la plus
optimisé sur *la* machine sur laquelle elle est en train de tourner ?
Plus le tuning à la main derrière par le type qui a des années
d'expérience là dedans ? :-)


1 2