tu sais pas ce qu'un compilo / une VM malins peuvent faire !
Mouais, c'est quelque chose que l'on entend répété ad nauseam depuis des années, mais en pratique il est rare qu'ils produisent un code plus performant qu'un premier jet bêtement codé à la main.
Chic, chic, un troll ! Donc euh, mon boulot a été, pendant un moment, d'optimiser des codes scientifiques (qui souvent sont bien plus faciles à optimiser pour un compilateur que des codes plus traditionnels). Je n'ai presque jamais eu à recourir à l'écriture en ASM, mais par contre savoir le lire était important :
Il fallait pouvoir reconnaître un code peu optimisé par le compilateur (alors qu'on pouvait faire mieux)
Il fallait quand même pouvoir écrire 2-3 trucs à la main dans certains cas précis
La plupart des cas où j'avais besoin de faire du fine tuning, je passais par les intrinsics de gcc/llvm/icc (par exemple, si on veut utiliser les instructions MMX/SSE/AVX sur x86/x64), ce qui était quand même 'achement plus simple pour obtenir des diagnostics d'erreur quand je me plantais.
La plupart du temps, le compilateur est bien plus malin que le programmeur.
Concernant mon dernier point : au final, plutôt qu'écrire en ASM, je finissais par savoir comment exprimer mes programmes sous une forme que le compilateur savait optimiser. Souvent, ça passe par la « débilisation » du code : si on essaie d'être trop intelligent, le compilateur voit un tas de code compliqué, et laisse tomber. Si au contraire on écrit le code de la façon la plus simple possible, souvent le compilo comprend l'idiome, et trouve des choses intelligentes à faire. Il y a bien entendu des exceptions :
Si on essaie d'optimiser pour les caches, il faut souvent recourir à des techniques de « blocking » ou « tiling », mais là encore on peut s'arranger pour ne pas se mettre en travers du compilateur (par exemple, on met la partie à optimiser du code dans une fonction à part, et ainsi on l'isole du nid de boucle qui l'entoure).
Il y a des fois où le compilateur se plante réellement dans la génération du code, mais là encore, faire de « l'assembleur en C » permet le plus souvent de le remettre dans le droit chemin.
Sur certaines architectures c'est relativement faux : par exemple sur Itanium, tout un tas de mécanismes super cool concernant la prédication des branches, la spéculation de contrôle ou de données, etc., n'étaient tout simplement pas accessible à moins de faire de l'assembleur (gcc est dans les choux niveau optim sur ia64, et icc interdit l'utilisation d'assembleur inline — mais il y a des intrinsics pour certains trucs).
Bref. J'en profite pour lier vers ce blog qui propose un quiz à propos de différentes optimisations que le compilateur peut effectuer.
Et de toutes manières, vu qu'en général on passe son temps à appeler des fonctions assez génériques qui ne sont donc pas optimisées pour le cas que l'on utilise, il n'y a pas grand chose d'optimisable par le compilateur...
Ça par contre je suis relativement d'accord. Je n'ai pas été regarder, mais j'aimerais bien savoir s'il existe des fonctions de la libc qui ont des variantes, du genre (code non testé, y'a sans doute des bugs) :
#ifdef __HAS_AVX__ #define memcpyAVX memcpy#elif __HAS_SSE2__#define memcpySSE2 memcpy#elif //...//...#endifvoid*memcpySSE2(void*restricts1,constvoid*restricts2,size_tn){if(n<sizeof(_m128d)){// size to copy is less than 8 charschar*dst=(char*)s1,*src=(char*)s2;while(n--)*dst++=*src++;}elseif(is_aligned16(s1)&&is_aligned16(s2)){// aligned on 16B_m128dsrc;// or _m128i with _mm_load_si128/_mm_store_si128, doesn't really matter here...size_ti;for(i=0;i<n-8;i+=8){src=_mm_load_ps(s2+i);_mm_store_ps(s1+i,src);// copy, 8 chars at a time}for(;i<n;++i)// epilogue*((char*)s1+i)=*((char*)s2+i);}else{// unaligned accesses_m128dsrc;// or _m128i with _mm_loadu_si128/_mm_storeu_si128, doesn't really matter here...size_ti;for(i=0;i<n-8;i+=8){src=_mm_loadu_ps(s2+i);_mm_storeu_ps(s1+i,src);// copy, 8 chars at a time}for(;i<n;++i)// epilogue*((char*)s1+i)=*((char*)s2+i);}}
NB: avec icc (et en supposant que mon code ne soit pas complètement rempli de bugs, ce qui est très possible), les boucles vont être déroulées, entre 2 et 6 fois, pour tirer avantage des 16 registres SSE. Pour tirer avantage des cas intermédiaires, icc va générer des variantes qui vont complètement dérouler les cas du genre N=8, N=16, N=32, etc. Je ne crois pas que gcc fasse cela aussi systématiquement, mais il commence aussi à être bon pour ce qui est de la vectorisation.
[^] # Re: Avec du poil aux pattes
Posté par lasher . En réponse au journal Esod mumixam !. Évalué à 4.
Chic, chic, un troll ! Donc euh, mon boulot a été, pendant un moment, d'optimiser des codes scientifiques (qui souvent sont bien plus faciles à optimiser pour un compilateur que des codes plus traditionnels). Je n'ai presque jamais eu à recourir à l'écriture en ASM, mais par contre savoir le lire était important :
Concernant mon dernier point : au final, plutôt qu'écrire en ASM, je finissais par savoir comment exprimer mes programmes sous une forme que le compilateur savait optimiser. Souvent, ça passe par la « débilisation » du code : si on essaie d'être trop intelligent, le compilateur voit un tas de code compliqué, et laisse tomber. Si au contraire on écrit le code de la façon la plus simple possible, souvent le compilo comprend l'idiome, et trouve des choses intelligentes à faire. Il y a bien entendu des exceptions :
Sur certaines architectures c'est relativement faux : par exemple sur Itanium, tout un tas de mécanismes super cool concernant la prédication des branches, la spéculation de contrôle ou de données, etc., n'étaient tout simplement pas accessible à moins de faire de l'assembleur (gcc est dans les choux niveau optim sur ia64, et icc interdit l'utilisation d'assembleur inline — mais il y a des intrinsics pour certains trucs).
Bref. J'en profite pour lier vers ce blog qui propose un quiz à propos de différentes optimisations que le compilateur peut effectuer.
Ça par contre je suis relativement d'accord. Je n'ai pas été regarder, mais j'aimerais bien savoir s'il existe des fonctions de la libc qui ont des variantes, du genre (code non testé, y'a sans doute des bugs) :
NB: avec icc (et en supposant que mon code ne soit pas complètement rempli de bugs, ce qui est très possible), les boucles vont être déroulées, entre 2 et 6 fois, pour tirer avantage des 16 registres SSE. Pour tirer avantage des cas intermédiaires, icc va générer des variantes qui vont complètement dérouler les cas du genre N=8, N=16, N=32, etc. Je ne crois pas que gcc fasse cela aussi systématiquement, mais il commence aussi à être bon pour ce qui est de la vectorisation.