Ou if(nombre&1) si on chipote sur la performance :)
Version imprimable
Non, ca, ce n'est pas une bonne optimisation, le compilateur est capable de la faire, et le code est moins lisible.
Tu peux me sortir un benchmark? car pour le moment, dans tout les moteurs nécessitant des performances élever le modulo et la division sont a proscrire au maximum.
Comment es-tu sur à 100% de ce que le compilateur va faire? De la version de ton compilateur? De ton compilateur?
Le code est tout à fait lisible, où il faut revoir ces compétences sur le masquage.
CF : https://software.intel.com/en-us/for...r/topic/298589
Ce genre d'optimisation compte énormément lorsque tu travailles avec un système qui doit rouler rapidement ( Car utiliser par une équipe ) et en debug ( Pour dump les callstacks par exemple ). Je pense à une configuration release avec information de débogage tu as besoin de ce genre de chose.
Après si c,Est pour un logiciel lamba, ça ne fera rien gagné, mais pour la connaissance général il est toujours bon de ne pas oublier ceci.
J'admets une limitation : Le type de "number" doit est unsigned
Je vous retournerais la question : l'optimisation à priori étant la racine de tous les maux, j'attendais d'un bon développeur qu'il écrive if (valeur % 2 == 0) à moins que celui-ci puisse me prouver via profiling que :
- cette instruction en particulier coîte du temps d'exécution
- ET une autre forme soit plus efficace.
Mais vous laissant le bénéfice du doute, et en dépis du fait que je suis persuadé que n'importe quel compilateur décent génère le même assembleur dans les deux cas, j'ai fait le test. Surprise : https://godbolt.org/g/XJcyxw
Code:
1
2
3 bool f1(int value) { return value % 2 == 0; } bool f2(int value) { return ! (value & 1); }
Code:
1
2
3
4
5
6
7
8
9
10 f1(int): mov eax, edi not eax and eax, 1 ret f2(int): mov eax, edi not eax and eax, 1 ret
Passe le sur MSVC 19 2017 RTW sans optimisation pour voir... :) On ne peut être sur de rien...
Bien sur le compilateur peut optimiser le modulo 2 et heureusement, mais une instruction div en plus est très couteux dans certains cas.
J'ai dit un compilateur décent.
dans certains cas.
Je maintiens donc mes attentes : un bon développeur devrait écrire if (valeur % 2 == 0) à moins que celui-ci puisse prouver via profiling que :
- cette instruction en particulier coîte du temps d'exécution
- ET une autre forme soit plus efficace.
Heu, c'est une blague ??? ou du troll ???? :weird:Citation:
sans optimisation
Comparer des performances sans optimisations, c'est sans intérêt.
Certes pour un entier non signé, x % 2 est remplacable par x & 2.
Mais qu'en est-il de x % 4 ?
Et x % 3?
Très bien alors en optimisation complete 'Ox' avec symbol de debug ( même si cela ne change rien ) sous Visual Studio 2015:
Le code suivant:
La version '%' te donne ceci:Code:
1
2
3
4
5
6
7
8
9 volatile int32 I = 256; { volatile bool IsEven = !(I % 2); IsEven; } { volatile bool IsEven = !(I & 1); IsEven; }
et la version optimisée te donne ceci:Code:
1
2
3
4
5
6
7
8 000007FEE11C6B94 mov eax,dword ptr [I] 000007FEE11C6B9B and eax,80000001h 000007FEE11C6BA0 jge Test::InitializeModule+29h (07FEE11C6BA9h) 000007FEE11C6BA2 dec eax 000007FEE11C6BA4 or eax,0FFFFFFFEh 000007FEE11C6BA7 inc eax 000007FEE11C6BA9 test eax,eax 000007FEE11C6BAB sete byte ptr [rsp+128h]
Donc : la version '&' te permets de t'assurer que peut importe le niveau d'optimisation, de compilateur etc... la version '&' te donne au pire des performances identiques a '%' et au mieux un gain signifiant.Code:
1
2
3
4 000007FEE11C6BBB mov eax,dword ptr [I] 000007FEE11C6BC2 xor al,0FFh 000007FEE11C6BC4 and al,1 000007FEE11C6BC6 mov byte ptr [IsEven],al
Une benchmark ne se fait pas sur une simple fonction comme tu as fait, mais sur une version plus complexe comme 90% des cas réels et également dans un environnement multi-thread, etc... donc rigole bien dans ta SSII :)
Voila ! Vous avez prouvé que sur ce compilateur-ci spécifiquement la réécriture du test a un impact. Si dans un cas concret un besoin d'optimisation se fait sentir et que c'est ce test a un impact significatif, utiliser l'idiome &1 est justifié.
(Ca prouve surtout la qualité de MSVC mais bon...)
Moi pas : https://godbolt.org/g/idQHcm
J'attache beaucoup d'importance à la lisibilité et la simplicité du code. J'ai observé que dans 99% des cas, les développeurs même séniors ont une mauvaise idée de ce qui est "optimisé" et ce qui ne l'est pas. Cela encourage l'écriture de code avec un haut WTF/ligne. Et savez-vous combien coûte une minute passée par un developpeur à essayer de comprendre une ligne inutilement compliquée ? Une fois multiplié par le nombre de lignes farfelues, cela coûte beaucoup, beaucoup plus cher que d'acheter le meilleur cpu du marché pour tous les serveurs du parc.
N'optimisez pas à priori au détriment de la lisibilité, vous aurez tord dans 99% des cas !
Bref, dans le cadre du sous-forum "Débuter en C++" de notre communauté, enseigner la lisibilité est plus important.
Et j'attends encore que tu nous donnes ta version optimisée de x % 3 == 2, c'est à dire un cas réel d'utilisation de modulo.
Permets moi d'émettre beaucoup de réserve sur les sites comme ça comme benchmark...
Le développeur qui ne comprends pas &1 devrait changer de métier.
Et la il n'y a pas tord, &1 te donnera toujours un résultat meilleur ou identique peut importe le compilateur et l'optimisation faite. Donc a moins de travailler dans un SSII comme probablement c'est pas mal le cas ici... Ce genre de choses ont un importance et sont demandées dans certains entretiens...
Et le fait que en debug, le code roule vite à une importance. Je lance souvent en debug une application à déboguer au boulot. Si cette application doit charger des centaines de ressources, decompressé, lire des fichiers etc... la moindre instruction compte et un simple 'F5' sur Visual ce compte en secondes voir minutes.
Donc je m'en contre fou de faire un '%' au lieu d'un '&' si c'est pour une SSII, dans d'autre cas ceci est très important.
Aucun rapport... On cherche à savoir si un entier positif est pair... pas autres choses.Citation:
Et j'attends encore que tu nous donnes ta version optimisée de x % 3 == 2, c'est à dire un cas réel d'utilisation de modulo.
Un type qui travaille dans une SSII aimerait vous indiquer que sur une architecture en one's complement votre optimisation cassera la fonctionnalité pour les nombres négatifs. Félicitation.
Ho, et c'est vous qui distribuez les pouces rouges comme des petits pains ? :-}
En général, nous sommes complètement à la merci du compilateur et de sa capacité d'optimiser. La plupart des conteneurs de la bibliothèque standart ainsi que ses algorithmes ont un fort niveau d'indirection ; indirection qui disparaît complètement ou quasiment une fois que le compilateur et le linkeur ont fini leur tâche d'optimisation et d'inlining.
Un compilateur décent produira le même code dans les deux cas. Un compilateur non décent produira un binaire aux performances catastrophiques quoi que vous écriviez.
Finalement, envahi par le doute, j'ai cherché sur google "c++ efficient way to test for even number". Je suis rassuré de voir qu'un consensus existe, mais peut être Google ne me montre-t-il que ce que je veux voir. Essayez, et dites-moi ce que vous obtenez s'il-vous-plaît.
Heu, depuis quand la taille de l'assembleur généré est un indicateur fiable des performances d'un code ??? :aie:
Dans une SSII, on est de la chair à pisser du code, donc les aspects maintenabilités du code sont bien mieux encadrés que des boites où un "cowboy programmeur" peut imposer des merdes à tout le monde parce que : "j'étais là à la création de la boite et je vous emmerde".
Ok, une boite de sodomites des drosophiles, qui se gaussent de leur fatuité.Citation:
Ce genre de choses ont un importance et sont demandées dans certains entretiens...
On va dire qu'on a envoyé notre CV par mégarde.
C'est pas les boites qui laissent ces pauvres diptères tranquilles qui manquent.
Si on veut vraiment être productif, au lieu de recruter des détraqués sexuels, utilisez des outils d'analyses statiques, c'est plus fiable.
Et si c'est si important, vous pouvez customiser Roslyn pour que le compilateur insulte ce puceau des insectes, qui ose écrire du code lisible.
https://msdn.microsoft.com/fr-fr/lib...or=-2147217396
@Astraya, c'est quand la dernière fois que vous avez essayé de vous faire recruter par une boite ? :weird:
Pour l'optimisation sûrement , mais lisibilité je pense que c'est relatif je trouve les 2 lisibles et compréhensibles.Citation:
Envoyé par ternel
Le ET est peut être moins 'connu' mais il est facilement compréhensible (preuve en l'apprend dans des bac pro ou STI donc loin d’être des bruts en math ! ).
Cela était vrai a une époque ( les divisions/multiplications n'existait pas sur les processeurs ou qu'il prenait trop de cycle ).
Pour les divisions (et je dirait les multiplications aussi) entier ou float , ce n'est plus trop vrai tu peux avoir des divisions/multiplications qui ne te coûte qu'un cycle ;)
Encore plus incroyable , certain processeur permettent de faire des calcul vectoriel/matriciel sur un cycle !
C'est important de le dire , je rajouterait que sans avoir la pipeline en tête d'un processeur donné il est très compliqué de connaître son optimisation réel ! (mais je peux en tout cas dire que le faire manuellement c'est très pénible).Citation:
Heu, depuis quand la taille de l'assembleur généré est un indicateur fiable des performances d'un code ???
Après il est vrai que le compilateur réfléchira en tout cas moins pour l'instruction ET (tout les processeurs possède l'instruction AND a ma connaissance) pour l'optimisation le compilateur saura choisir.
Je trouve beaucoup de débat pour un truc qui n'en vaut pas la peine , l’optimisation il faut y réfléchir sur du code critique (un truc exécuter 10 000/ 100 000/ 1 000 000 par seconde).
Si pour l'initialisation tu fait du code un peu lent et que tu perd 1 voir 2 cycle bon c'est vraiment s'énerver contre du vent :roll:
NT: Alors je me suis renseigné sur le x86 , l'instruction DIV permet de récupérer le modulo et comme de nos jours grâce a la pipeline cette instruction peut être l'équivalent d'un cycle , je crois que vous avez votre réponse ;)
Gameloft, Eidos pour Montréal, Ubisoft et Spider Games pour Paris. Quelques SSII de temps en temps, les pires expériences et les projets les plus merdiques que j'ai jamais rencontré ^^
Je me base sur mon expérience, vous avez la votre. Maintenant pour savoir si un entier positif est pair, '&1' est le meilleur des deux mondes. n'en déplaise à Stack overflow.
Tout comme le fait de faire *0.5 plutôt que /2 est plus efficace... mais bon j'en ai marre de débattre ici de ça :)
Un très bon document présentant le nombre de micro opération par instruction par processeur: http://www.agner.org/optimize/instruction_tables.pdf
Je ne parle pas de compilateur, car pour ce qui est des optimisations la première règle c'est déjà de ne pas prendre le compilateur pour un génie qui à toujours raison.
Pour ce qui est de ne pas les faire trop tôt, il y a optimisation et optimisation. Changer un * en / et % en & n'est pas en soit un optimisation mais une règle de codage qui permets d'inclure l'optimisation sans avoir à y penser.
Comme quoi on lit les mêmes documents , je trouvais le document intéressant parce que il mettait le nombre de cycle des vieux intel x86 et je voulais le comparer avec celui de la PS2 :mrgreen:
(Si dans un débat XBOX vs PS2 on sait jamais , ce genre de chiffre ça permet de dire que le processeur de la PS2 était plus balèze :') )
Mais bref ces nombres d'instructions sont 'vrai' mais les processeurs modernes (et la plupart figurant a cette liste ) possède une pipeline.
Par exemple le intel atom fait pour un mul 3 à 8 cycle , mais tu peux faire en sorte qu'il fasse qu'un cycle faut juste qu'il soit pipeliné , alors comment faire ben ça se fait automatiquement il faut juste pas faire une instruction qui arrête sa pipeline(sinon l'instruction coûte plus qu'un cycle).
Alors si tu programme en assembleur ça donnerai que tu devrai avoir a chaque instruction en tète la pipeline et donc avoir une idée de la pipeline pour chaque instruction , t'imagine le truc ? En plus ça t'oblige a réécrire ton code si tu veux faire une modif , parce que oui la modif ça peut chamboulé cette maudite pipeline !
D'ailleurs toute la difficulté du processeur de la PS2 était le VU0 et VU1 outre que c'était des co-processeurs 128 bits , il était superscalaire ,évité les pipeline stall sur deux instructions en même temps c'était comment dire très chiants :mouarf:
Mon but était de réduire l'instruction Div en 1 cycle (elle devait faire 6 cycle sur le VU) , ça permettait au final de faire des calcul de projection 3D/2D(pour l'écran) en quelque cycle !
D'ailleurs je me disait qu'il fallait créer un macro assembleur pour pas ce soucier de cette pipeline , bref de laisser un programme s'occuper de cette optimisation , ah oui ça existe presque deja c'est le C ;)
Alors sur PS2 il existait pas de compilateur C pour ces VU et puis il fallait codé de manière assez bas niveau sur quelque octet alors autant aller en asm , mais il est clair que on passais des heures pour optimiser quelque ligne a la con...
Je dirais qu'en 1995 a cette époque les processeurs ont commencé a avoir une pipeline et tu pouvait bien faire sur ps2 un calcul matriciel en 4 cycle (4 instruction ) et la je parle d'un processeur qui date du siècle dernier (1999).
Et encore je ne connais pas encore tout les processeurs plus récent le CELL (PS3) , les processeur ARM moderne , les X86-64 bits moderne possède encore quelque astuce pour réduire le nombre de cycle (ou éviter d'avoir des conflits sur cette pipeline comme le OoO) , la mémoire cache permet aussi d'avoir des accès mémoire encore plus rapide (et donc réduire le nombre de cycle et donc évité que la pipeline soit pleine).
Bref je suis beaucoup plus réticent d’être si catégorique , si en 1999 on avait la technologie pour faire 1 cycle pour une division ou 4 cycle pour un calcul matriciel (que je codais en asm ) j'ose esperer que 20 ans après on a pas reculé niveau technologie !
Pour les processeurs récents Intel à un document très complet:
https://www.intel.com/content/www/us...on-manual.html
On en revient à une optimisation qui est principalement, aligner la mémoire et limiter le cache-miss. Pour ce qui est du couts des instructions ils discutent aussi des SIMD.
Heureusement que aujourd'hui on ne s’occupe plus des mêmes choses que au temps de la PS2 ;) Mais certains choses reste toujours présente comme le calcule d'une division qui "internement" parlant sera plus long qu'une multiplication, pipeliné ou pas.
D'ailleurs dans le document d'Intel je t'invite a jeter un rapide coup d'oeil à 11.12 DIVIDE AND SQUARE ROOT OPERATIONS
puisCitation:
In Intel microarchitectures prior to Skylake, the SSE divide and square root instructions DIVPS and SQRTPS have a latency of 14 cycles (or the neighborhood) and they are not pipelined. This means that the throughput of these instructions is one in every 14 cyclese
Citation:
In microarchitectures that provide DIVPS/SQRTPS with high latency and low throughput, it is possible to speed up single-precision divide and square root calculations using the (V)RSQRTPS and (V)RCPPS instructions. For example, with 128-bit RCPPS/RSQRTPS at 5-cycle latency and 1-cycle throughput or with 256-bit implementation of these instructions at 7-cycle latency and 2-cycle throughput, a single Newton-Raphson iteration or Taylor approximation can achieve almost the same precision as the (V)DIVPS and (V)SQRTPS instructions.
Cela a toujours était (même sur Ps2) que la division est plus lente en 'interne' que la multiplication ou le ET , mais la pipeline permet heureusement que cette instruction a le coût équivalent a un cycle , la simple division est pipeliné donc peut être optimisé et avoir le même coût que la multiplication et le ET.
Pour les deux instructions elles sont dans des cas spécial faut avoué , avoir 4 division en même temps a fournir c'est pas commode :p
Pour SQRTPS 4 racine carré en même temps a 14 cycle cela reste intéressant (même si je ne suis jamais tombé sur un tel cas ! ).
Limite je comprend qu'il a du mal a les pipeliné ^^
Après je n'ai pas tout regardé (600 pages) et je programme quasiment jamais en assembleur sur un intel ;)
Il y a plusieurs problème avec le fait de considérer que comme il peut être fait en un cycle il le sera.
L'ensemble des informations doivent être dans le cache pour commencer, ensuite cela est très dépendant du type de processeur.
Et même si tu arrives à la faire en 1 cycle, tu aurais pu avancer d'autre instruction plutôt que de tout bloquer dans le pipeline pour 1 div, c'est pour cela que aujourd'hui cela ne veut plus trop rien dire de réfléchir en cycle. Le principale de l'optimisation étant la limitation du cache miss pour permettre ce que tu dis. Mais même avec ça, ton jeu d'instruction aura un bottleneck.
La normalisation d'un vecteur 4, d'un quaternion, la division d'une matrice 4*4 ou 4*3. Ce qui est censé être fait quasiment tout le temps ( Au moins 1 fois après modification de l'un de ces éléments )
Beaucoup de formule mathématique peuvent être améliorer en utilisant les SIMD SSE par exemple (_mm_div_ps pour Microsoft Specific --> DIVPS) Mais la encore on va limiter sont utilisations car comme on le vois dans la documentation d'Intel une tel division prend 14 cycles!! Ce qui est énorme si on compte le nombre de fois ou l'on doit le faire par frame et le nombre de frame par seconde par exemple :)
Mais la on digresse à mort sur le sujet de base je crois :p
Appendix C Instruction Latency and throughput :)
Si sur un projet spécifique, sur un matériel spécifique, quelqu'un a passé du temps en amont pour se convaincre, PoC à l'appui, que celà avait un impact positif mesurable, je suis parfaitement d'accord avec ça ; c'est même ce qu'on attend de bon developpeurs.
Mais, encore une fois mon message est : conseiller à un débutant de remplacer systématiquement un %2 == 0 par un &1 ou dire que dans tous les cas c'est positif, là je suis contre.
Si vous pouviez comprendre cette distinction que je fais, peut-être pourrions nous tomber d'accord.
Partons en HS alors :D
Je ne suis pas vraiment d'accord , je serait étonné que le DIV pose encore souci sur des processeurs modernes (tant en terme de cycle que de profondeur de la pipeline).
Mais oui sur certain processeur actuel (comme AVR-atmel il faut mieux éviter).
Avis tout t'as fait personnel les instruction SIMD même si fort utile la plupart sont aussi pour des soucis de compatibilité.
Si tu parle de jeux vidéo , il est clair qu'on prefere faire tout les calculs coté GPU (les consoles sont même pensé pour ce cas de figures de mon point de vue) pour moi "l'abandon" du processeur CELL de Sony pour la PS4 (malgré que en terme de calcul matriciel un processeur CELL est plus performant que un intel même récent en terme de GFLOPS) de je que j'ai pu lire le CPU CELL , c'est en gros comme la PS2 avec 8 co processeur mathématique capable de faire des div , des muladd en un cycle etc sa puissancde de calcul était d’alléger une partie du GPU des calcul mathématique , mais malgré cela il n'a pas était vraiment plus incroyable que la concurrence (mais Sony aime bien les techno exotique).
Bref pour dire qu'un GPU est massivement parallèle et sur ce coté la en terme de calcul mathématique est plus intéressant.
Je n'ai pas de preuve sur ce sujet (dans le sens une doc des instruction GPU ) mais les GPU me semble pas avoir des instructions SIMD , leur force se trouve sur leur nombre important de calcul en parallèle et je pense que l'avenir sera de ce coté la (sinon intel aurait mis depuis le temps un muladd donc 4 multiplication + 4 addition en une seule instruction).
Pour le compilateur je reste sceptique , si tu considère le compilateur mauvais faudrait mieux que tu code en asm alors :p
(Parce que s'il arrive pas a optimiser les div ou autre , j'imagine pas le reste :D ).
PS: 14 cycle je trouve cela peu mais peut être que j'ai l'habitude de ce genre d’instruction longue, sur des vieux proc comme le M68000 (qui équipait la Mega drive , la Neo Geo et la plupart des ordi et borne d'arcade de l'époque) , les instructions coûtait entre 4 et 12 cycle en moyenne , et il fallait faire un jeu avec 8 ou 12 MHZ !
Alors je vais être plus explicite :
1- Si tu veux tester si un entier non signé est pair, fait X&1
2- Si tu veux connaitre le reste d'une division, fait X%Y
3- Si tu veux connaitre le reste d'une division avec un dividende qui est une puissance de 2, fait X & (Y - 1) (Y est la puissance de 2)
3- Si tu veux faire une division par autre chose que une puissance de 2, fait X/Y
4- Si tu veux faire une division par une puissance de 2, fait X>>log2(puissance) ( log2 étant pas calculer au Runtime sinon pas d’intérêt, donc en 32 bits c'est 0]log2]32 et 64 bits 0]log2]64 )
Tout ceci est énormément utiliser dans les softwares tel Unreal, Unity, 3dsMax, Blender... Également vu dans les APIs fortement multi thread comme les lock-free, qui vont bénéficier de ces "Boosts" en , par exemple, n'allouant que des buffers de taille 'power of 2'.
Alors oui pour un étudiant, il n'y comprendra rien. Mais ce qui me fait vraiment peur sur developpez.com c'est de voir que des gens que l'on pourrais penser expérimenté professionnellement ne savent pas ça, et reste dans leur carcan.
Je suis sur que ce code-ci en fera bouillir plus d'un:
ou ça:Code:
1
2
3
4
5
6
7
8 PtrType* IfAThenAElseB(PtrType* A, PtrType* B) { const intptr intptrA = reinterpret_cast<intptr>(A); const intptr intptrB = reinterpret_cast<intptr>(B); const intptr Mask = -(!intptrA); // Mask which all bit is set if intptrA is zero or all bit unset if intptrA is not zero return reinterpret_cast<PtrType*>(intptrA | (Mask & intptrB)); }
Et pourtant ce sont toutes ces petites choses qui font que vous pouvez avoir du temps réel réaliste.Code:
1
2
3
4
5
6
7
8 PtrType* IfAThenBElseC(PtrTypeTest A, PtrType* B, PtrType* C) { const intptr intptrB = reinterpret_cast<intptr>(B); const intptr intptrC = reinterpret_cast<intptr>(C); const intptr Mask = -(!intptr(A));// Mask which all bit is set if A is zero or all bit unset if A is not zero return reinterpret_cast<PtrType*>((intptrB & ~Mask) | (intptrC & Mask)); }
Oh et ça passe sur tout les X86 et autres... :)
Pour répondre à Kannagi:
La PS4 et XBox One et futurs sont sur des X86. La raison est simple, avoir du CELL sur PS et PowerPC sur Xbox fait exploser le cout de développement si tu veux du multiplateformes et n'apportait pas vraiment grand chose sauf de la complexité.
Ensuite, pour ce qui est du rendu sur le GPU le souci est exactement le même :
https://devtalk.nvidia.com/default/t...lo-/?offset=10
https://devtalk.nvidia.com/default/t...nteger-modulo/
Un petit papier de NVidia également la dessus : http://docs.nvidia.com/cuda/pdf/CUDA...ices_Guide.pdf
Ici la section 8.1.1
Également un peu plus bas 8.1.4:
Et je peux t'assurer que si il y a une diff sur les GPU alors ton CPU sera bien moins performants ;)Citation:
Prefer faster, more specialized math functions over slower, more
general ones when possible.
Ce ne sont pas des choses qu'un débutant à besoin de savoir. Lui montrer des X & (Y - 1) va davantage le perturber qu'autre chose. Et avec des constantes, le compilateur sais très bien transformer en masque binaire et autres joyeusetés.
Si on utilise ce genre d'optimisation sur des variables, alors on travaille implicitement/explicitement en puissance de 2 et je ne considère plus les formules comme une optimisation, mais comme une logique de continuation. Par contre utiliser des puissances de 2 à probablement un but d'optimisation. Le débutant gagnerait plus à faire des petites abstractions qu'il pourra optimiser par la suite.
Cool, mais niveau lisibilité on pourra repasser. Et pas sûr que les versions "optimisées" le soit vraiment: https://godbolt.org/g/ZpAzo1
Quand j'ai commencé à bidouiller il y a plus de 20 ans, c'était vrai sur les PC de l'époque. Jouer avec les bits était plus efficace que des modulos, et je le faisais, comme probablement beaucoup.
Seulement, cela fait un paquet de temps que les compilateurs savent reconnaitre certains patterns pour produire un code strictement identique avec une forme ou l'autre. Je me souviens de cas (exposées par James Kanze sur fclc++ avec Sun CC) où des multiplications se retrouvaient remplacées par additions et décalages, dans notre dos. A se demander quelle pertinence il y a à offusquer du code pour gagner une optim que le compilo produit déjà pour nous.
Dans certains cas (en absence d'intrisincs et/ou de besoin de portabilité), je n'hésite pas à aller faire un tour sur http://graphics.stanford.edu/~seander/bithacks.html et autres archives similaires quand je cherche à éviter des boucles pour travailler sur des bits -- et je laisse une référence pour que les prochains sachent d'où vient le hack illisible. Mais j'avoue, je prends rarement le temps de faire des benchs sérieux pour vérifier si cela vaut toujours, le mot est important, le coup.
Maintenant concernant "&1" VS "%2", la question est de savoir comment la notion de parité est comprise. Dans un monde où le développeur n'a qu'une formation scientifique ou une autre, mais non spécialisée, le "%2" devrait être plus parlant -- en gros le monde des SSII. Dans un monde d'informaticiens de formations, j'espère bien qu'ils sauront lire le "&1" et en connaitre les limitations -- non, je n'attends pas la même choses des scientifiques sur le %2. J'espère aussi qu'ils comprennent la notion de modulo! Dans tous les cas, godbolt nous le montre bien: c'est exactement le même code assembleur qui est généré (*). Reste-t-il des compilos trop exotiques pour avoir suffisamment de financement qui leur permettrait d'être capables de reconnaitre que "&1" et "%2" devraient aboutir au même code assembleur ? Bonne question. J'ai envie d'être idéaliste sur ce cas précis.
NB: Concernant les ternaires VS if..return/break, j'avoue que je suis dans le doute. J'ai observé un assembleur produit différent. Et dans mes derniers tests, le saut conditionnel semblait l'emporter après mesures imparfaites, chose qui contredisait mon intuition.
(*) godbolt n'est pas un site de bench. C'est un ensemble de compilos en ligne qui permettent aussi et surtout de voir l'assembleur généré avec divers compilos, en diverses versions, avec diverses options, pour diverses archis cibles.
Personellement, je vois déjà plein de soucis:
- Ces équivalences ne sont pas vraies pour les nombres négatifs. (le complément à deux n'est pas requis par la norme)
- Ces avis ne s'appliquent qu'aux entiers.
- le right shift >> est défini par l'implémentation, changer de compilateur peut changer sa signification.
cité de cppreference.comCitation:
For unsigned a and for signed a with nonnegative values, the value of a >> b is the integer part of a/2b
For negative a, the value of a >> b is implementation-defined (in most implementations, this performs arithmetic right shift, so that the result remains negative).
- Les contraintes sur les valeurs utilisables ne sont pas les mêmes (surtout pour le shift)
- Et pour diviser par -4, faut-il écrire - (x >> 2) ou (-x) >> 2? Que fais précisément -x >> 2?
Dites les gars!
Je ne suis peut-être plus modérateur, soit.
Mais vous ne pensez pas que vous commencer à digresser un peu beaucoup, surtout que nous sommes dans la section "débuter" :question:
Typiquement, dans cette section, la priorité est de fournir quelque chose qui fonctionne, et non quelque chose qui est rapide.
On se fout pas mal de ce qui sera le plus rapide sur une architecture quelconque, on veut d'abord et avant tout que cela fonctionne!
@Astraya: tu as fait tes expériences, grand bien te fasse. Et si, un jour, j'ai besoin de profiter de ces expériences pour résoudre un problème de performances, ne t'en fais pas, je ferai appel à toi en priorité.
Mais dans le cas présent, on se fout pas mal que x%2 prenne 8 cycles alors que x&1 n'en prend que 4. Ce que l'on veut -- d'abord et avant tout -- c'est que le PO (qui doit se sentir bien perdu dans toute cette discussion) puisse résoudre le problème de débutant auquel il est confronté.
Je vous serais très reconnaissant de recentrer un tout petit peu la discussion sur les problèmes réels de notre ami
Meci Koala, j'ai découpé la discussion.
Il faut remettre les choses dans l'ordre
- Astraya a posé des hypothèses et périmètre d'action qu'il convient de conserver : s'il s'agit d'un nombre non signé pour tester la parité (pair ou impair, donc divisible par 2)
> partant de là, les "et si on veut tester la divisibilité par 3 ?" ou "et s'il s'agit d'un chiffre négatif ?" sont au mieux non avenus
> ces autres tests ont d'autres astuces, ou au pire on utilisera la version classique du modulo
- Que le compilateur sache l'optimiser avec les bonnes options est bien
> que ce simple calcul puisse être optimisé indépendemment des options est un plus non négligeable
> lancer une appli (en l'occurence on parle d'un jeu 3D temps réel) en debug est toujours lent, ces petites aides sont très bénéfiques à l'ensemble de l'équipe
>> quand je veux debugger du réseau, que j'utilise la config debug, si l'affichage ou toute autre partie est moins lente c'est appréciable
>> pour atteindre les 30, 60 ou 120 fps tant vénérées par les joueurs, ce genre d'astuce est importante, surtout dans des éléments clés comme le rendu et engine
>> la lisibilité est quasi secondaire face aux performances, c'est pas un code touché par des rigolos ou débutants
- Dans le monde des consoles, gcc n'existe pas, chaque constructeur a son compilo et son implémentation
> implémentations privées et dont on ne trouve que très peu voire aucune information sur le net, en dehors des sites de support du constructeur (très grosse NDA pour les utiliser)
Mes premiers commentaires avaient justement trait au fait qu'on était sur une question d'un débutant.
Dans le cadre d'un programme hyper optimisé, pour une cible précise, et par un compilateur précis, la question est toute autre.
Et la réponse qui me paraît adaptée est "ce qui est indiqué par des mesures honnètes".
En parlant de mesures honnêtes, cette histoire de bit-hacks m'a rappelé ce post:
The wrong way of benchmarking the most efficient integer comparison function
En gros, ces bit-hacks gênent l'optimiseur plus qu'autre chose.
Tu veux ton &1 et tu es à trois cycles près ? Alors déclare ton entier en unsigned et laisse le compilo remplacer %2 par &1, car au moins dans ce cas il continuera à optimiser.