11
votes

Éviter les appels vers l'étage ()

Je travaille sur un code de code où j'ai besoin de traiter des UVS (coordonnées de texture 2D) qui ne sont pas nécessairement dans la plage de 0 à 1. Par exemple, je vais parfois obtenir une UV avec un composant U 1.2. Afin de gérer cela, je mettez en place une emballage qui provoque le carrelage en procédant comme suit: xxx pré>

faisant ces causes 1.2 Pour devenir 0,2, ce qui est le résultat souhaité. Il gère également des cas négatifs, tels que -0,4 devenant 0,6. P>

Cependant, ces appels à plancher sont plutôt lents. J'ai profilé ma candidature à l'aide d'Intel Vtutune et je passe une énorme quantité de cycles faisant simplement cette opération de plancher. P>

Ayant effectué des antécédents de lecture sur le problème, je suis arrivé à la fonction suivante qui est un peu plus rapide mais laisse encore beaucoup à désirer (je supporte toujours des pénalités de conversion de type, etc.). p>

int inline fasterfloor( const float x ) { return x > 0 ? (int) x : (int) x - 1; }


3 commentaires

Pourriez-vous réparer tout ce qui vous assure des valeurs non valides?


Utilisation de * REINIERPRET_CAST (& U) et une sorte de bits magique (en supposant qu'un format de flotteur IEEE) serait probablement le plus rapide que vous puissiez faire à nu C ++, mais qui perd une certaine portabilité.


Les coordonnées peuvent-elles jamais être négatives? En outre, lorsque vous n'avez pas trouvé quelque chose qui a «une amélioration de la vitesse significative», a-t-elle franchi votre esprit que cela pourrait être simplement parce que si une méthode nettement plus rapide existait, le compilateur l'utiliserait pour commencer? ;)


9 Réponses :


0
votes

Quelle est la plage d'entrée maximale de vos valeurs U, V? Si c'est une gamme assez petite, par exemple -5,0 à +5,0, alors il sera plus rapide d'ajouter / soustraire de manière répétée 1,0 jusqu'à ce que vous obteniez de la plage, plutôt que d'appeler des fonctions coûteuses telles que le sol.


5 commentaires

Sera probablement plus lent que sa fonction actuelle "FasterFloor" dans de nombreux cas.


PROBABLEMENT PAS - INT <-> La conversion à flotteur est assez coûteuse sur la plupart des CPU - l'ajout / soustrait 1,0 est un cycle d'horloge.


Oui, mais avec les conditionnels, cela pourrait ne pas être aussi efficace. si (u> 1) u - = 1 est au moins 2 instructions - la comparaison, la soustraction et éventuellement une instruction supplémentaire en fonction de la manière dont l'architecture gère les conditionnels.


Quelques commentaires: des boucles de ce type (lorsque la condition finale dépend de quelque chose de calculé dans la boucle) ne peut généralement pas être pipeline, ce qui est une performance importante sur de nombreux systèmes. Par comparaison, INT <-> La conversion de flotteur est facilement pipiable s'il y a du code environnant pour le pipeline avec. Cependant, en général, vous ne pouvez pas obtenir de réponses fiables à ces sortes de choses avec la théorie; Pour obtenir de vraies réponses, l'affiche originale doit exécuter des repères sur les différentes versions avec des données typiques.


Quelle méthode est plus rapide dépendra de nombreux facteurs - la répartition des valeurs d'entrée, le coût relatif des différentes instructions sur une CPU donnée, combien d'autre code environnant peut être entrelacé d'absorber les latences d'instruction, etc. Comme cela a déjà été signalé Dans ce cas, dans ce cas, la seule chose à faire est d'essayer les diverses solutions offertes et de les comparer, de sorte que vous puissiez faire une décision d'optimisation fondée sur des preuves plutôt qu'un spéculatif.



1
votes

Si la plage de valeurs pouvant survenir est suffisamment petite, vous pouvez peut-être effectuer une recherche binaire sur la valeur du plancher. Par exemple, si les valeurs -2 <= x <2 peuvent se produire ...

 XXX  

Je ne fais aucune garantie à ce sujet - je ne sais pas comment l'efficacité des comparaisons se compare au sol - mais Cela vaut peut-être la peine d'être essayé.


0 commentaires

2
votes

Une autre idée stupide qui pourrait simplement fonctionner si la plage est petite ...

Extraire l'exposant du flotteur à l'aide des opérations bitwises, puis utilisez une table de recherche pour trouver un masque qui élimine les bits indésirables de la Mantissa. Utilisez ceci pour trouver le sol (essuyer les bits en dessous du point) pour éviter les problèmes de renomalisation.

Modifier J'ai supprimé cela comme "trop ​​idiot, plus avec un problème de + ve vs. -ve". Depuis que cela s'est levé de toute façon, c'est undrotled et je laisserai les autres de décider à quel point c'est idiot.


1 commentaires

Pas si stupide; L'une des implémentations de la FMOD à NewLib (de Sun) est-ce que c'était donc considéré comme une chose raisonnable à faire au moins à un moment donné. Et c'était avec un module arbitraire plutôt que 1,0! Nasty Code compliqué, cependant.



2
votes

Si vous utilisez Visual C ++, vérifiez le réglage du compilateur "Activer les fonctions intrinsèques". Si cela est activé, il devrait faire la plupart des fonctions mathématiques plus rapidement (y compris le sol). L'inconvénient est que la manipulation des cas de bord (comme Nan) pourrait être incorrecte, mais pour une partie, vous ne vous souciez pas.


0 commentaires

3
votes

L'opération que vous souhaitez être exprimée à l'aide de la fonction FMOD (FMODF pour les flotteurs plutôt que sur les doubles):

u = (u + 16.0);  // Does not affect fractional part aside from roundoff errors.
u -= (int)u;     // Recovers fractional part if positive.


0 commentaires

12
votes

Donc, vous voulez un très rapide en fonction du flottant> int conversion? AFAIK int> conversion de flotteur est rapide, mais au moins MSVC ++ une fonction du flottant> int de conversion d'une invoque petite fonction d'aide, FTOL (), qui fait un peu de choses compliquées pour assurer une conformité aux normes de conversion se fait. Si vous n'avez pas besoin d'une telle conversion stricte, vous pouvez faire une carriole de montage, en supposant que vous êtes sur un processeur compatible x86.

Voici une fonction pour un rapide flotteur à int qui arrondit vers le bas, en utilisant MSVC ++ assembleur en ligne syntaxe (il devrait vous donner la bonne idée de toute façon): p>

#include <emmintrin.h>

inline int ftoi_sse1(float f)
{
    return _mm_cvtt_ss2si(_mm_load_ss(&f));     // SSE1 instructions for float->int
}


4 commentaires

Tous Excellent conseils pour 32 bits (X86) construit. La fonction ftoi_fast est significativement plus rapide que de laisser le compilateur générer le code automatiquement, si vous pouvez vivre avec ses limitations (c'est-à-dire à l'aide du mode d'arrondi FPU actuel, qui est probablement rond même).


Cependant, les choses sont beaucoup plus faciles pour 64 bits (X64). Étant donné que tous les systèmes cibles prennent en charge les instructions SSE / SSE2, le compilateur émettra automatiquement le code qui les utilise, au lieu d'appeler la fonction ftol () . Donc, vous n'avez pas besoin de faire tout le travail d'utilisation d'un fichier ASM externe pour les constructions 64 bits; En fait, cela va probablement entraîner un code légèrement plus lent que celui généré par le compilateur!


Notez que X87 est obsolète maintenant. En outre, les deux fonctions données sont la troncature, pas le sol.


J'ai dû bownvote :( parce que l'assemblage en ligne provoque des pessimisations massives. (Barrières de réorganisation de la réorganisation des barrières, des clôtures MEM, une vectorisation anti-automobile et de tels ..)



0
votes

Celui-ci ne résout pas le coût de la coulée, mais doit être correct mathématiquement: xxx


0 commentaires

0
votes

Si vous bouclez et que vous utilisez U et V en tant que coordonnées d'index, au lieu de revêtir un flotteur pour obtenir les coordonnées, gardez à la fois un flotteur et un intégration de la même valeur et les incrémentez-les. Cela vous donnera un entier correspondant à utiliser si nécessaire.


1 commentaires

Pouvez-vous fournir un exemple de code pour illustrer ce que vous décrivez?



11
votes

Une ancienne question, mais je suis tombé sur elle et cela m'a fait convulser légèrement qu'il n'a pas été répondu de manière satisfaisante.

TL; DR: * NE PAS ** Utilisez l'assemblage en ligne, l'intrinsique ou l'une des autres solutions données pour cela! Au lieu de cela, compilez avec des optimisations de mathématiques rapides / dangereuses («-ffast-math-math-Math-Math-Optimizations -fno-math-errno» dans g ++). La raison pour laquelle le plancher () est si lent est que cela change d'état global si la distribution déborderait (FLT_max ne correspondait pas à un type d'entier scalaire de toutes tailles), ce qui rend également impossible à vectoriser, sauf si vous désactivez la compatibilité Strict IEEE-754 , que vous devriez probablement ne pas compter sur de toute façon. La compilation avec ces drapeaux désactive le comportement du problème.

Certaines remarques:

  1. Assemblée en ligne avec registres scalaires n'est pas vectorible, ce qui inhibe considérablement la performance lors de la compilation avec des optimisations. Il exige également que toutes les valeurs pertinentes actuellement stockées dans des registres vectoriels soient renversées à la pile et rechargées dans des registres scalaires, qui défaitent l'objectif de l'optimisation des mains.

  2. Assemblage en ligne Utilisation de la SSE CVTTSSSI avec la méthode que vous avez décrite est effectivement plus lente sur ma machine qu'une simple boucle avec optimisations de compilateur. Ceci est probable parce que votre compilateur allouera des registres et évitera mieux les étals de pipeline si vous le permettez de vectoriser ensemble des blocs de code ensemble. Pour une courte partie de code comme celle-ci avec peu de chaînes à charge internes et presque aucune chance d'enregistrer Spillage, il a très peu de chance de faire pire que le code optimisé à la main entouré d'ASM ().

  3. L'assemblage en ligne est inutilisable, non supporté dans les bâtiments Visual Studio 64 bits et incroyablement difficile à lire. Les intrinsèques souffrent des mêmes mises en garde ainsi que celles énumérées ci-dessus.

  4. Tous les autres manières répertoriées sont tout simplement incorrectes, ce qui est sans doute pire que d'être lent, et ils donnent dans chaque cas une telle amélioration de la performance marginale qu'elle ne justifie pas la grosseur de l'approche. (int) (x + 16.0) -16.0 est si mauvais que je ne le toucherai jamais, mais votre méthode est également fausse car elle donne le sol (-1) sous -2. C'est également une très mauvaise idée d'inclure des succursales en code mathématiques lorsque cela est donc essentiel que la bibliothèque standard ne fasse pas le travail pour vous. Donc, votre voie (incorrecte) devrait ressembler davantage à ((int) x) - (x <0,0), peut-être avec un intermédiaire afin de ne pas avoir à effectuer le déménagement FPU deux fois. Les branches peuvent causer une cache Miss, qui annulera complètement toute augmentation de la performance; De plus, si MATH ERRNO est désactivé, le casting sur INT est le plus gros goulot d'étranglement restant de toute implémentation de plancher (). Si vous / vraiment / ne vous souciez pas de recevoir des valeurs correctes pour des entiers négatifs, il peut s'agir d'une approximation raisonnable, mais je ne risquerais pas de cela à moins que vous sachiez très bien votre cas d'utilisation.

  5. J'ai essayé d'utiliser la coulée des bits et l'arrondi via-bitmask, comme la mise en œuvre de NewLib de SunLib dans FMODF, mais il a fallu très longtemps pour avoir raison et plusieurs fois plus lentement sur ma machine, même sans le Drapeaux d'optimisation du compilateur. Très probablement, ils ont écrit ce code pour un processeur ancien où les opérations de points flottants étaient relativement très chères et qu'il n'y avait pas d'extensions de vecteur, encore moins d'opérations de conversion de vecteur; Ce n'est plus le cas sur des architectures communes afaik. Le soleil est également le lieu de naissance de la routine SQRT inverse () rapide utilisée par le Quake 3; Il y a maintenant une instruction pour cela sur la plupart des architectures. L'un des plus grands pièges de micro-optimisations est qu'ils deviennent obsolètes rapidement.


1 commentaires

quelqu'un l'obtient. Je souhaite que l'on puisse sacrifier le représentant de voter +10 à la fois.