46Fermer48
bearbecueLe 30/10/2012 à 18:46
Bon j'arrive un peu apres la bataille triso
./3>
Au final ce qui va compter c'est la prédictibilité de c pour le CPU. Et encore, si ce n'est pas exécuté dans une boucle critique, ce n'est sans doute pas important de s'attarder dessus.
À priori, GCC te permet d'aider le CPU avec __builtin_expect(,) mais ça n'a pas l'air possible avec Visual C++ par exemple (vu que ta question mentionne GC,C je suppose que ça ira )

D'autres t'en diront sans doute plus (PpHd, bearbecue ?)

./8>
Tiens, intéressant, merci. Il y a un équivalent pour VC++ ?



non, pas d'equivalent sous VC++, principalement parceque la plupart des architectures pour lesquelles VC++ compile n'ont pas d'encoding specifique des hints de branchements au niveau des instructions asm directement.
contrairement, par exemples, aux PPC, ou tu peux directement ecrire un truc comme:

jge+ _Label	# dit au CPU de supposer que le jump sera execute
jge- _Label	# dit au CPU de supposer que le jump ne sera pas execute


le hint donne a GCC, sur les architectures qui supportent ca, va souvent directement se traduire par un flagging de l'instruction generee par la backend. Alors que sur les architectures ne le supportant pas (x86-64 par exemple), ca aura pour effet de convertir, lorsque les bon flags d'optimisation sont actives:

if (unlikely(condition))
	B();
else
	A();


en:

if (!condition)
	A();
else
	B();


dans visual, il faut le faire a la main, mais c'est _beaucoup_ moins genant de pas avoir ce genre de hints sur les x86 recents, du a la prediction dynamique des branchement (cf le Branch-Target-Buffer dans un des derniers liens postes. C'est en gros un historique des dernieres branches prises, on peut voir ca comme une sorte de hashmap qui mappe des addresses d'instructions de branchements avec un petit historique des derniers jumps effectues, et qui se sert de cet historique pour predire les branchements suivants. (il y a d'autres subtilites dans la prediction, mais, pour resumer, le CPU se demmerde vraiment tres bien, lorsque les jumps suivent un pattern pas trop complexe)).
./10>
Aucune idée de la confiance qu'on peut avoir en ce document, mais il dit (page 57) que GCC place B en premier et repousse A plus loin, potentiellement très loin, genre après tout le reste des instructions de la fonction (le schéma en bas de page sera plus clair que moi). De cette manière, on a une exécution parfaitement linéaire le plus souvent. C'est encore mieux que l'astuce décrite dans le post ./1


Ca, c'est pas pour economiser un jump statique (qui est en gros gratuit sur les architectures recentes), mais pour separer le "code froid" du "code chaud". Avoir plein de if/else avec un seul des blocs execute la plupart du temps et l'autre quasiment jamais, ca produit des "bulles" de code froid au milieu du code chaud.

Et ca a pour effet d'augmenter enormement le nombre d'octets inutiles par cache-line dans l'Instruction cache. donc plus de cache misses inutiles
sans ce genre d'optimisations, le ratio instructions executee / nombre de cache-lines fetch dans l'instruction cache peut vite devenir assez bas, et sous-optimal.
avec ces optims, la plupart du temps, toutes les instructions dans les lignes de cache fetchees sont du code utile.
et de temps en temps, l'execution saute dans une zone "froide", loade une cache-line, s'execute, et retourne au code chaud.

./39>Folco : Pour compléter la réponse de Brunni avec mon peu de connaissances, le pipeline c'est le système qui consiste, sur les processeurs modernes, à paralléliser les 4 étapes successives qui sont nécessaires pour exécuter une instruction (de mémoire : lecture, décodage, exécution, écriture). Chaque instruction passe successivement dans ces 4 modules, donc nécessite au moins 4 cycles pour s'exécuter comme sur 68k, mais, pendant qu'une instruction est traitée par un module, le module précédent traite l'instruction suivante. Il y a donc une instruction qui rentre dans le pipeline et une instruction qui se termine à chaque cycle, ainsi le temps d'exécution moyenné d'une instruction n'est plus que de 1 cycle


./42> pencil

c'est _BEAUCOUP_ plus complexe que 4 etapes successives.
les pipelines modernes peuvent avoir des profondeurs qui depassent les 30 cycles/"etapes".
une seule misprediction de branchement, et ca flushe tout le pipeline.
sachant que certaines instructions peuvent etre schedulees entre deux et 4 fois PAR CYCLE, je te laisse imaginer l'impact que ca peut avoir sur les perfs cheeky
(bon c'est pas aussi violent que "4*30 instructions de penalite" hein grin, il y a des interactions plus complexes entre les differents ports d'execution et autres trucs marrants dans le pipeline qui font que t'as jamais autant d'instructions en meme temps en cours de traitement dans ton pipeline)

Et sur les CPUs hyper-threades (genre les core-i7, ou des core2-quad HT, qui n'ont que 2 vrais cores), il y a encore des trucs en plus, les unites d'execution sont partagees dans les deux threads hardware, mais chaque hyper-thread a son propre bout de silicium pour fetch et decoder les instructions, donc les interactions peuvent devenir extremement complexes, voire pas calculables du tout, vu que la performance de ton bout d'asm micro-optimise aux petits oignons va dependre de quel autre process est schedule sur l'autre hyper-thread de ton core.

bref... ^^

c'est pour ca que ca sert souvent a grand chose de compter les cycles sur les CPUs modernes (ie: c'est pas toujours faisable), et qu'il vaut mieux profiler le code dans son cas reel d'utilisation grin

d'ailleurs a propos des cycles des instructions, il y a deux metriques bien distinctes:
- le throughput : le nombre de cycles a attendre avant que le pipeline du CPU puisse accepter une autre instruction de ce type (ca peut etre < 1 cycle : plusieurs instructions displatchees par cycle)
- la latence: le nombre de cycles que tu dois attendre avant de pouvoir utiliser le resultat d'une instruction. (le vrai "cout" en cycles de l'instruction, mais on ne peut pas se baser uniquement la dessus, vu que des instructions qui s'executent sur un autre port d'execution peuvent etre executees en parallele, du moment qu'elles n'ont pas besoin du resultat pour faire leurs calculs)


Si ca t'interesse, je te conseille d'aller jeter un oeil ici, c'est le meilleur doc sur le sujet que je connaisse:

http://www.agner.org/optimize/
http://www.agner.org/optimize/optimization_manuals.zip

(hors consoles, evidemment smile)