La façon la plus rapide pour la fixation d'un réel (fixe et virgule flottante) de la valeur?
Est-il un moyen plus efficace pour la fixation de nombres réels que l'aide si les déclarations ou ternaire opérateurs?
Je veux le faire à la fois pour les doubles et un 32 bits fixpoint de mise en œuvre (16.16). Je suis pas demander de code qui peut gérer les deux cas, ils seront traités dans des fonctions séparées.
Évidemment, je peux faire quelque chose comme:
double clampedA;
double a = calculate();
clampedA = a > MY_MAX ? MY_MAX : a;
clampedA = a < MY_MIN ? MY_MIN : a;
ou
double a = calculate();
double clampedA = a;
if(clampedA > MY_MAX)
clampedA = MY_MAX;
else if(clampedA < MY_MIN)
clampedA = MY_MIN;
La fixpoint version utiliser les fonctions/macros pour les comparaisons.
Cela est fait dans un critiques des performances de la partie du code, donc je suis à la recherche d'un moyen efficace de le faire que possible (ce qui je pense serait d'impliquer la manipulation de bits)
EDIT: Il doit être standard/C portable, une plate-forme spécifique fonctionnalité n'est d'aucun intérêt ici. Aussi, MY_MIN
et MY_MAX
sont du même type que la valeur que je veux serré (doubles dans les exemples ci-dessus).
- Je pense que vous pouvez utiliser SSE3 ou une technologie semblable pour cela, mais ne savez pas exactement quelles commandes/comment... Vous pouvez prendre un coup d'oeil sur: Saturation de l'arithmétique
- Désolé, la question n'était pas claire au sujet de la plate-forme d'exigences. J'ai édité la question d'être un peu effacé.
- Je sais qu'il a été deux ans et demi, depuis que vous avez posé cette question, mais j'espère que vous vérifier ma réponse - 3x amélioration est significative.
- Un détail n'est pas spécifié est ce que la précision (relative ou absolue) êtes-vous prêt à échanger pour la vitesse si tout. Si le code exige une gamme
a
être retourné exactement commea
, puis beaucoup de réponses ne répondent pas à cet obstacle. Si la précision est d'aucun intérêt , puis revenant toujours(MY_MAX + MY_MIN)/2
sera certainement un rapide à faible précision de la réponse, et certainement stupide. Recommander à tolérer pas plus que 1 ULP d'erreur. - Comment voulez-vous faire sur SSE4 variable (
__m128
)? - connexes plus-efficace-élégant-moyen-pour-clip-un-nombre
Vous devez vous connecter pour publier un commentaire.
Pour la 16.16 représentation, le simple ternaire est peu susceptible d'être amélioré en termes de vitesse.
Et pour les doubles, parce que vous en avez besoin standard/C portable, peu-manipulation de tout type finira mal.
Même si un peu de violon était possible (ce dont je doute), vous pourriez être en s'appuyant sur la représentation binaire de doubles. CE (et leur taille) EST DÉPENDANT de l'IMPLÉMENTATION.
Vous pourriez "deviner" ce en utilisant sizeof(double), puis en comparant la mise en page de divers double des valeurs à leurs représentations binaires, mais je pense que vous êtes sur une cachette pour rien.
La meilleure règle est de DIRE AU COMPILATEUR CE que VOUS VOULEZ (c'est à dire ternaire), et il l'optimiser pour vous.
EDIT: tarte Humble temps. Je viens de tester quinmars idée (ci-dessous), et il fonctionne - si vous avez IEEE-754 flotteurs. Cela a donné un gain d'environ 20% sur le code ci-dessous. IObviously non-portable, mais je pense qu'il y a peut-être une façon standardisée de demander à votre compilateur, s'il utilise des float IEEE754 formats avec un #IF...?
int64_t
donnera de mauvais résultats lorsque les deuxFMIN
et*pfvalue
sont inférieurs à zéro, par exemple, FMIN=-1, FMAX=1, (*pfvalue)=-0.1; voir ma réponse stackoverflow.com/questions/427477/...Vieille question, mais j'ai été de travailler sur ce problème aujourd'hui (avec des doubles/flotteurs).
La meilleure approche est d'utiliser de l'ESS MINSS/MAXSS pour des flotteurs et SSE2 MINSD/MAXSD pour les doubles. Ce sont dépourvu de branches et de prendre un seul cycle d'horloge chaque, et sont faciles à utiliser grâce à compilateur intrinsèques. Ils confèrent à plus d'un ordre de grandeur de l'augmentation des performances par rapport à serrage avec std::min/max.
Vous pouvez trouver que surprenant. Je n'ai certainement! Malheureusement VC++ 2010 utilise des comparaisons simples pour std::min/max, même lorsque /arch:SSE2 et /FP:rapide sont activés. Je ne peux pas parler pour les autres compilateurs.
Voici le code nécessaire pour ce faire, dans VC++:
La double précision, le code est le même, sauf avec xxx_sd à la place.
Edit: j'ai d'Abord écrit la pince fonction commenté. Mais en regardant l'assembleur de sortie, j'ai remarqué que le compilateur VC++ n'était pas assez intelligent pour choisir la redondant déplacer. Un de moins instruction. 🙂
__builtin_ia32_storess
,__builtin_ia32_maxss
, __builtin_ia32_minss " sont l'équivalent des fonctions et de laxmmintrin.h
- tête pour SSE1 instructions. Passer-mmmx -msse
pour le compilateur, vous pouvez avoir besoin-mfpmath=sse(,x87)
ainsi. Intrinsèques sont également disponibles pour ARM Neon et AltiVec. Voir X86 fonctions intégrées pour plus de détails.std::min
etstd::max
avec le intrinsèques dans le cas général, parce que la intrinsèques fournir les IEEE754 résultat déterminé pourmin(2.0, NaN)
etmin(NaN, 2.0)
(qui est2.0
dans les deux cas), tandis qu'un naïf mise en œuvre, basé sur une simple comparaison sera de retour à un résultat incohérent en fonction du paramètre d'ordre. C99 et C++11 fournirfmax
etfmin
, et un habile compilateur remplace avec efficacité inline implémentations.GCC et clang générer beau montage pour la suivante, simple, simple, portable code:
> gcc -O3 -march=native -Wall -Wextra -Wc++-compat -S -fverbose-asm clamp_ternary_operator.c
GCC-assembly généré:
> clang -O3 -march=native -Wall -Wextra -Wc++-compat -S -fverbose-asm clamp_ternary_operator.c
Bruit généré par l'assemblée:
Trois instructions (sans compter le ret), pas de branches. Excellent.
Cela a été testé avec GCC 4.7 et clang 3.2 sur Ubuntu 13.04 avec un Core i3 M 350.
Sur une note côté, le simple code C++ appel std::min et std::max généré de la même assemblée.
C'est pour les doubles. Et pour les int, GCC et clang générer de l'assemblée avec cinq instructions (sans compter le ret) et pas de branches. Aussi excellent.
Je ne suis pas actuellement l'utilisation de la virgule fixe, donc je ne vais pas donner un avis sur un point fixe.
min
et/oumax
quand un ou les deux ne sont Pas Un nombre. Il a également préserver signe avecd = -0.0
!if (d < min)
etif (d > max)
me donne aussi le même code assembleur. Il est intéressant de constater, cependant, que l'utilisation deif (d < min)
etelse if (d > max)
génère une sortie différente (il y a une instruction de saut).Si votre processeur a une rapide instruction pour valeur absolue (comme le x86 n'), vous pouvez faire un sans branches min et max qui sera plus rapide qu'un
if
déclaration ou ternaire de l'opération.Si l'un des termes est égale à zéro (comme c'est souvent le cas lorsque vous êtes de serrage) le code simplifie un peu plus loin:
Lorsque vous êtes en combinant les deux opérations que vous pouvez remplacer les deux
/2
en un seul/4
ou*0.25
pour enregistrer une étape.Le code suivant est plus de 3x plus rapide que le ternaire sur mon Athlon II X2, lors de l'utilisation de l'optimisation pour FMIN=0.
abs(a)
n'est pas inline/optimisé bien...fabs(value-FMAX)
plutôt queint abs(int j)
.FMAX
des valeurs supérieures àvalue
peut perdre de la précision dans le résultat. SiFMAX
est 10xvalue
, puis 1 décimale peut être perdu. Pire des cas, serrées valeur de retour est toujours 0.0.min(a,b) = (a + b - abs(static_cast<int>(a-b))) / 2
pour des valeurs non signées? Sans le static_cast<int>,a-b
pourrait être d'une très grande valeur sib > a
, qui serait à son tour donner une valeur incorrecte.Opérateur ternaire est vraiment la voie à suivre, parce que la plupart des compilateurs sont capables de les compiler en natif le fonctionnement matériel qui utilise un conditionnel déplacer au lieu d'une branche (et évite donc le mispredict peine et du pipeline de bulles et ainsi de suite). De manipulation de bits est susceptible de causer une charge-hit-magasin.
En particulier, PPC et x86 avec SSE2 matériel op qui pourrait être exprimée comme une valeur intrinsèque de quelque chose comme ceci:
L'avantage est qu'il n'est présent à l'intérieur de la canalisation, sans provoquer une branche. En fait, si votre compilateur utilise la valeur intrinsèque, vous pouvez l'utiliser pour mettre en œuvre votre pince directement:
Je vous suggère fortement de éviter de manipulation de bits en double en utilisant les opérations sur entiers. Sur la plupart des Processeurs modernes il n'y a aucun moyen direct de déplacement de données entre le double et int registres autres que de prendre un aller-retour à la dcache. Ce sera la cause d'une des données de danger appelé une charge-hit-store qui, en gros, vide le PROCESSEUR pipeline jusqu'à ce que la mémoire d'écriture est terminée (généralement autour de 40 cycles ou plus).
La seule exception est si le double des valeurs sont déjà en mémoire et non pas dans un registre: dans ce cas, il n'y a aucun danger d'une charge-hit-store. Toutefois, votre exemple indique que vous avez juste calculé le double et l'a retourné à partir d'une fonction qui signifie qu'il est probablement encore en XMM1.
A > B ? A : B
toujours généré un MAX d'instruction, maisA < B ? B : A
n'a pas.a == -0.0
! Seules les valeurs/les limites, j'ai eu quelques souci impliqué une certaine asymétrie avec Pas-un-nombre - Permetmin
être un Pas-un-nombre et joliment ignore lamin
. Pourtant, simax
est NAN, le résultat est NAN. Pourrait être fait symétrique avec un code différent dereturn fsel( a - max, max, a );
Les bits de la norme IEEE 754 à virgule flottante sont agencés de telle sorte que si l'on compare les bits interprété comme un entier, vous obtenez les mêmes résultats que si vous ne comparer que des flotteurs directement. Donc, si vous trouvez ou connaissez un moyen de serrage entiers que vous pouvez l'utiliser pour (IEEE 754) flotte ainsi. Désolé, je ne connais pas un moyen plus rapide.
Si vous avez les chars stockées dans un des tableaux, vous pouvez envisager d'utiliser certaines extensions de CPU comme SSE3, comme rkj dit. Vous pouvez prendre un coup d'oeil à liboil il fait tout le sale boulot pour vous. Maintient votre programme portable et utilise plus rapide cpu instructions si possible. (Je ne suis pas sûr tho comment OS/compilateur indépendant liboil est).
Plutôt que de tester et de branchement, j'ai l'habitude d'utiliser ce format pour le serrage:
Bien que je n'ai jamais fait aucune analyse de la performance sur le code compilé.
De façon réaliste, aucun décent compilateur fera la différence entre un if() et d'un état ?: de l'expression. Le code est assez simple, qu'ils vont être en mesure de repérer les chemins possibles. Cela dit, vos deux exemples ne sont pas identiques. L'équivalent du code à l'aide ?: serait
comme éviter que l'Un < MIN de test lors de l'a > MAX. Maintenant que pourrait faire une différence, que le compilateur, autrement, aurait pour tache de la relation entre les deux tests.
Si le serrage est rare, vous pouvez tester la nécessité de serrage, avec un seul test:
E. g. avec MIN=6 et MAX=10, ce sera la première équipe à une baisse de 8, puis de vérifier si elle se situe entre -2 et +2. Si cela permet d'économiser tout dépend beaucoup du coût relatif de la ramification.
fabs()
queint abs(int)
2) de Bord de la condition des problèmes avec la perte de précision due àfabs(a - (MAX+MIN)/2) > ((MAX-MIN)/2)
. La première méthode n'a pas ces problèmes.Ici est peut-être plus rapidement la mise en œuvre similaire à @Roddy réponse:
Voir Calculer le minimum (min) et maximum (max) de deux entiers sans ramification et La comparaison des nombres à virgule flottante
Un programme de test:
Dans la console:
Il imprime:
is_negative_zero
, Est-il une raison pourquoi vous n'avez pas l'utilisation du C99 mathématiques.hsignbit
fonction (combiné avecx == 0
), et plutôt utilisé1.0 / x < 0
pour vérifier le signe de zéro?signbit
semble aussi travail.J'ai essayé de l'ESS approche à moi-même, et l'assemblée de sortie de l'air un peu plus propre, j'ai donc été encouragés au premier abord, mais après le calendrier des milliers de fois, c'était en fait un peu plus lent. Il semble en effet comme le VC++ compilateur n'est pas assez intelligent pour savoir ce que vous êtes vraiment l'intention, et il semble faire bouger les choses avant en arrière entre les registres XMM et de la mémoire quand il ne devrait pas. Cela dit, je ne sais pas pourquoi le compilateur n'est pas assez intelligent pour utiliser l'ESS min/max des instructions sur l'opérateur ternaire quand il semble utiliser les instructions SSE pour tous les calculs en virgule flottante de toute façon. D'autre part, si vous êtes à la compilation pour processeurs PowerPC, vous pouvez utiliser le fsel intrinsèque sur le FP registres, et c'est beaucoup plus rapide.
Si je comprends bien, vous voulez limiter la valeur de "a" à une fourchette entre MY_MIN et MY_MAX. Le type de "a" est un double. Vous n'avez pas spécifié le type de MY_MIN ou MY_MAX.
La simple expression:
devrait faire l'affaire.
Je pense qu'il y a peut-être une petite optimisation si MY_MAX et MY_MIN arriver à être des entiers:
En changeant entier comparaisons, il est possible que vous pourriez obtenir un léger avantage en termes de vitesse.
MY_MIN,MY_MAX
commeint
, cette approche ne tient pas sia
n'est pas près de laint
gamme(int)a
est un problème.Si vous souhaitez utiliser rapide, en valeur absolue, des instructions, consultez cette aide de l'extrait de code que j'ai trouvé dans mini ordinateur, qui coince un flotteur à l'intervalle [0,1]
(J'ai simplifié un peu le code). On peut penser que la prise de deux valeurs, qui se reflète à >0
et l'autre réfléchi à 1.0 pour être <1.0
Et nous prenons la moyenne d'entre eux. Si elle est à portée, puis les deux valeurs seront les mêmes que x, de sorte que leur moyenne sera de nouveau x. Si elle est hors de portée, alors l'une des valeurs de x, et l'autre sera x retournée sur le dos de la "frontière" du point, de sorte que leur moyenne sera précisément le point de démarcation.
x < 0.25
. Avecvalues < DBL__EPSILON
, résultat perd toute précision.Comme l'a souligné ci-dessus, fmin/fmax fonctions fonctionnent bien (dans gcc, avec -ffast-math). Bien que gfortran a des modèles à utiliser IA instructions correspondant à max/min, g++ ne prend pas. Dans la cpi on doit utiliser au lieu de std::min/max, parce que la cpi n'autorise pas à court de la coupe de la spécification de la façon dont fmin/fmax travailler avec des non-finis opérandes.
Mes 2 cents en C++. Probablement pas différent que d'utiliser les opérateurs ternaires et, espérons-le pas de ramification code est généré