8
votes

Comment puis-je élier un appel si une condition de bord est connue au moment de la compilation?

J'ai la situation suivante: il y a un énorme ensemble de modèles tels que std :: vecteur qui appellera memmove () pour déplacer des parties de la matrice. Parfois, ils voudront "déplacer" des parties de longueur zéro - par exemple, si la queue de tableau est supprimée (comme std :: vecteur :: effacer () ), ils voudront bouger Le reste de la matrice qui arrivera à avoir une longueur zéro et que zéro sera connu à l'heure de la compilation (j'ai vu le démontage - le compilateur est conscient) mais le compilateur émettra toujours un Memmove () < / code> appel.

Si fondamentalement, je pourrais avoir un wrapper: xxx

mais cela introduirait une vérification d'exécution supplémentaire dans les cas comptage est non connu dans la compilation de compilation que je ne veux pas.

est-il possible d'utiliser __ assume l'indice Pour indiquer au compilateur que si elle sait certainement que est zéro, il devrait éliminer le Memmove () ?


13 commentaires

Qu'espérez-vous économiser avec cela? Semble comme une optimisation micro-micro-micro? Vous allez enregistrer deux branches (en regardant la mise en œuvre de base GNU de MemMove )?


@Nim: la ramification, l'appel à Memmove () ainsi que (la partie la plus importante) permettant d'optimiser un code autour du code Memmove () appel - non L'appel signifie que sa préparation des arguments n'est pas nécessaire. Oui, c'est micro, mais cela enregistre des microsecondes.


Essayez-vous de mettre en œuvre votre propre vecteur?


@David Rodríguez - Dribesas: Sorte de - à des fins de formation.


Oh, venez, les gens, Sharmpooth semble avoir suffisamment d'expérience pour savoir que "l'optimisation prématurée est la racine du mal" et que vous ne devriez pas mettre en œuvre votre propre vecteur à moins que vous n'ayez des raisons sérieuses. Il y a des cas où il y a des raisons pour les deux, pensons-nous maintenant que c'est l'une d'entre elles et essayons de résoudre le problème, ne pas le déclarer non problématique.


Il est étrange que le compilateur doit émettre un appel à Memmove lorsqu'il a déjà détecté la longueur 0. En réalité, l'appel doit être inliné, la boucle de taille zéro doit être détectée et élue. Pourquoi cela ne se produit-il pas? Êtes-vous en train de relier contre un runtime dynamique? Si tel est le cas, écrivez un wrapper pour MemMove qui ressemble à ce que vous avez écrit ci-dessus.


@Konrad Rudolph: afaik La raison est que Memmove () est implémenté dans l'assemblage dans les sources d'exécution Visual C ++ et non présentées au compilateur.


@Konradrudolph Memcpy est inliné (en fait "intrinsèces") et le compilateur est suffisamment intelligent pour l'éliminer, mais Memmove n'est pas.


@Suma hm. Une raison pour laquelle? Je me rends compte qu'il est probablement mis en œuvre dans l'assemblage, mais l'optimisation du temps de liaison devrait alors s'occuper de l'affranchissement nécessaire.


@KonraDrudolph Link Time CG ne peut faire aucune optimisation sur les fonctions de montage. Même l'affranchissement d'une fonction d'assemblage n'est pas possible. 1) Vous ne pouvez pas modifier la manière dont les arguments sont transmis, 2) les fonctions se termine déjà avec RET ou éventuellement avec plusieurs RETS, il n'existe pas de manière fiable comment "couper" ce rebut. La mise en œuvre MEMCY est complètement différente, elle est non seulement inline, elle est gérée comme intrinsèque par le compilateur et le compilateur peut utiliser tout ce qu'elle sait de décider de la compilation.


Dupliqué possible de Détection constante de la compilation C ++


@Suma Link-Time Optimisation réécrit le code (il doit, afin de s'intégrer!). Je ne vois pas comment cela diffère de C ++ à l'assemblage. Les deux ne sont que des fichiers d'objet (avec des informations supplémentaires). À moins que, bien sûr, VC ++ ne fournisse pas de distributions de bibliothèque appropriées. Ce serait boiteux.


@Konradrudolph Il est très différent et il y a des informations supplémentaires. LTCG ne fonctionne pas avec un "code natif", mais avec une représentation symbolique du code (c'est pourquoi il doit être activé également lors de la compilation d'objets, non seulement lors de la liaison). Cela ne peut pas être fait de l'assemblage. Voir par exemple msdn.microsoft.com/en-us/magazine/ccc301698.aspx Pour plus d'informations.


4 Réponses :


3
votes

Le point du __ suppose est de dire au compilateur d'ignorer des portions du code lors de l'optimisation. Dans le lien que vous avez fourni à l'exemple est donné avec la clause par défaut du commutateur . Là, l'indice indique au compilateur que la clause ne sera jamais atteinte, même si théoriquement qu'elle pourrait . Vous dites à l'optimiseur, fondamentalement, «Hé, je sais mieux, jetez ce code».

pour par défaut Vous ne pouvez pas l'écrire dans (sauf si vous couvrez toute la plage de la plage de S, qui est parfois problématique) car cela entraînerait une erreur de compilation. Donc, vous avez besoin de l'indice pour optimiser le code vous savoir qui est inutile.

Dans votre cas - le code peut être atteint , mais pas toujours, donc le __ assume indice ne vous aidera pas beaucoup. Vous devez vérifier si le nombre est vraiment 0. Sauf si vous êtes sûr qu'il ne peut jamais être rien d'autre que 0, puis ne l'écrivez pas.


0 commentaires

3
votes

Cette solution utilise une astuce décrite dans Détection constante de la compilation C ++ - le Trick utilise le temps de compilation entier zéro peut être converti en un pointeur, ce qui peut être utilisé avec une surcharge pour vérifier la propriété "Durée de compilation connue".

#define is_const_0(X) (sizeof(chkconst::chk2(X))<sizeof(chkconst::Big))
#define is_const_pos(X) is_const_0( int(X)^(int(X)&INT_MAX) )
#define is_const(X) (is_const_pos(X)|is_const_pos(-int(X))|is_const_pos(-(int(X)+1)))


5 commentaires

Dans toutes les opérations qui prennent des itérateurs externes, la mise en œuvre ne peut pas savoir que les itérateurs remis ne sont pas du même conteneur, et vous ne pouvez donc pas utiliser memcpy , et il va de même avec Effacer (Si vous effacez un élément au milieu et qu'il y a plus de deux éléments au-delà de cela, les gammes sont garanties de chevauchement). Vous pouvez utiliser memcpy d'autre part lorsque vous développez le tampon, car cela garantit que la source et les destinations ne sont pas superposées.


Non, il s'agit d'une résolution de fonction ordinaire, il n'y a même pas de modèle. X (void * A) est utilisé lorsque la valeur est une constante zéro, X (TEMP A) sinon (TEMP peut être construit à partir d'un int, mais ce n'est pas une surcharge préférée pour zéro). Je trouve aussi supercool aussi. La source originale de l'idée semble être Encode.ru/threads/396 -C-compile-time-constant-détection


@shachartooth qui n'est pas Sfinae mais pourrait potentiellement être utilisé à Sfinae. À propos de la réponse, je pense que c'est cool, mais je ne vois pas vraiment comment cela aide le problème. Le principe (comme je l'ai compris) est que le compilateur n'a pas supprimé le si (compte> 0) dans un cas où il était connu au moment de la compilation pour être 0, Comment changer compile_time_constant_0> 0 à Tailleof (x)> Tailleof (y) affecte comment le compilateur génère le code? (En supposant que 0 est connu à l'heure de la compilation, les deux devraient être également faciles à optimiser) la prochaine question serait la suivante: comment Sharpoth est-il venu à cette conclusion ...


@ Davidrodríguez-Dribeas non, la question était différente - lorsque la condition a été utilisée, si (compte> 0) et Memmove était élue correctement pour connaître zéro, mais si la valeur était encore laissée lorsque la valeur n'était pas compilée, présentant des frais généraux inutiles. . Avec cette solution, il n'y a pas de frais généraux, mais zéro mouvements sont éliminés.


Pourquoi la version GCC est-elle plus complexe?



1
votes

Je pense que vous avez mal compris la signification de __ assumer . Il ne dit pas au compilateur de changer son comportement quand il sait quelles sont les valeurs, mais cela lui dit plutôt quelles seront les valeurs quand il ne peut pas en déduire lui-même.

Dans votre cas, si vous l'avez dit à __ supposer que comptez> 0 Il saute le test, comme vous l'avez déjà dit que le résultat sera toujours True , il supprimera la condition et appelle MemMove toujours , ce que vous voulez éviter.

Je ne connais pas la intrinsèque de VS, mais dans GCC, il y a un probable / improbable intrinsèque intrinsèque ( __ intégré_Expect ((x), 1) ) qui peut être Utilisé pour indice le compilateur sur lequel est le résultat le plus probable du test. Cela ne supprimera pas le test, mais le code de la mise en page de sorte que le plus probable (comme dans par votre branche de définition ) est plus efficace (ne sera pas une branche).


5 commentaires

Vs n'a rien de probable / peu probable et c'est plutôt triste.


Il me semble que je me souvienne que par défaut, il suppose que la première branche (la si) est prise, ce qui signifie que, si c'est la branche la moins attendue, vous pourriez avoir une incidence sur le code généré en revenant la condition et les clauses if / else


@ David Rodríguez - Dribesas: Cette astuce ne fonctionne pas lorsque vous avez un si sans sinon .


si (pas condition) {} else {corps} ? Ou vous voulez dire que le compilateur générera le même code que dans si (condition) {corps} ? Du point de vue de l'emplacement du code, ils généreront la même chose, le code sera juste où le cas échéant est, et il y aura un saut à la fin, mais je ne suis pas sûr que le test / saut sera le même. et / ou si la CPU le traitera différemment


@ David Rodríguez - Dribesas: le compilateur générera en effet le même code pour un si sans sinon et pour un if-ele avec vide Si branche.



1
votes

Si possible de renommer le Memmove, je pense que quelque chose comme ça ferait - http://codepad.org/s974fp9k xxx

Je pense La même chose est probablement possible sans la classe - doit regarder les règles de conversion Pour le confirmer.

Aussi, il devrait être possible de surcharger le Memmove d'origine (), par exemple. en passant un objet (comme Temp (Tailleof (a)) en tant que 3ème argument.

Vous n'êtes pas sûr de quelle manière serait plus pratique.


0 commentaires