Quel est le moyen le plus rapide d'implémenter une opération qui renvoie la valeur absolue d'un nombre?
x=root(x²)
ou
if !isPositive(x):
x=x*(-1)
En fait, cette question peut être traduite par: quelle est la rapidité d'une if
(et pourquoi, s'il vous plaît).
Mes professeurs de programme collégial me disaient toujours d’éviter les if
s car ils sont extrêmement lents, mais j’ai toujours oublié de demander quelle lenteur et pourquoi. Est-ce que quelqu'un ici sait?
Les conditions sont plus lentes que des opérations arithmétiques simples, mais beaucoup plus rapides que quelque chose d'aussi stupide que de calculer la racine carrée.
Règles de base de mes jours d'assemblée:
Il y a une bonne astuce pour calculer la valeur absolue d'un entier 2s sans utiliser d'instructions if. La théorie est la suivante: si la valeur est négative, vous souhaitez basculer les bits et en ajouter un, sinon vous souhaitez les transmettre tels quels. Un XOR 1 arrive à basculer vers A et A XOR 0 laisse A laisser intact. Donc, vous voulez faire quelque chose comme ça:
uint32_t temp = value >> 31; // make a mask of the sign bit
value ^= temp; // toggle the bits if value is negative
value += temp & 1; // add one if value was negative
En principe, vous pouvez le faire en seulement trois instructions de montage (sans branche). Et vous aimeriez penser que la fonction abs () que vous obtenez avec math.h le fait de manière optimale.
Aucune branche == meilleure performance. Contrairement à la réponse de @paxdiablo ci-dessus, cela est vraiment important dans les pipelines profonds où plus le nombre de branches dans votre code est grand, plus vous avez de chances que votre prédicteur de branche se trompe et soit obligé de revenir en arrière, etc. Si vous évitez de créer des branches, possible, les choses vont continuer à avancer à fond dans votre coeur :).
Ugh, vos professeurs vous l'ont dit? La règle que la plupart des gens suivent est de rendre votre code lisible d’abord, puis de régler tous les problèmes de performances. après qu'ils se sont avérés être réellement des problèmes. 99,999% du temps, vous ne verrez jamais un problème de performance, car vous en avez utilisé plusieurs instructions if. Knuth a dit le mieux , "l'optimisation prématurée est la racine de tout le mal".
Le calcul de la racine carrée est probablement l'une des pires choses que vous puissiez faire car elle est très lente. Habituellement, il existe une fonction de bibliothèque pour le faire; quelque chose comme Math.Abs (). Multiplier avec -1 est également inutile; retourne juste -x. Donc, une bonne solution serait la suivante.
(x >= 0) ? x : -x
Le compilateur optimisera probablement cela avec une seule instruction. Les conditions d'exécution peuvent être assez coûteuses pour les processeurs modernes en raison de la longueur des pipelines d'exécution: les calculs doivent être annulés si une branche a été mal prédite et que le processeur a commencé à exécuter les instructions à partir du mauvais chemin de code. Mais à cause de l'optimisation du compilateur mentionnée, vous n'avez pas besoin de vous en soucier dans ce cas.
Pour être complet, voici une façon de procéder pour les flotteurs IEEE sur les systèmes x86 en C++:
*(reinterpret_cast<uint32_t*>(&foo)) &= 0xffffffff >> 1;
La variante if
sera presque certainement aveuglante rapide par rapport à la racine carrée, car elle se traduit normalement par une instruction de saut conditionnel au niveau du code machine (à la suite de l'évaluation de l'expression, qui peut être complexe, mais non dans ce cas, c’est une simple vérification pour moins de 0).
Prendre la racine carrée d'un nombre risque d'être beaucoup plus lent (la méthode de Newton, par exemple, utiliserait des instructions plusieursif
au niveau du code machine).
La source probable de confusion est le fait que if
conduit invariablement à modifier le pointeur d'instruction de manière non séquentielle. Cela peut ralentir les processeurs qui prélèvent des instructions dans un pipeline car ils doivent à nouveau remplir le pipeline lorsque l'adresse change de manière inattendue.
Cependant, le coût serait minime comparé à une opération à la racine carrée, par opposition à une simple vérification.
Quel est le moyen le plus rapide d'obtenir la valeur absolue d'un nombre
Je pense que la "bonne" réponse n'est pas ici en fait. Le moyen le plus rapide d’obtenir le nombre absolu consiste probablement à utiliser Intel Intrinsic. Voir https://software.intel.com/sites/landingpage/IntrinsicsGuide/ et recherchez "vpabs" (ou un autre élément intrinsèque qui fait le travail pour votre processeur). Je suis sûr que ça va battre toutes les autres solutions ici.
Si vous n'aimez pas les éléments intrinsèques (ou ne pouvez pas les utiliser ou ...), vous voudrez peut-être vérifier si le compilateur est suffisamment intelligent pour déterminer si un appel à la "valeur absolue native" (std::abs
en C++ ou Math.Abs(x)
en C #) changera automatiquement en intrinsèque - en gros, cela implique de regarder le code désassemblé (compilé). Si vous êtes dans un JIT, assurez-vous que les optimisations JIT ne sont pas désactivées.
Si cela ne vous donne pas non plus les instructions optimisées, vous pouvez utiliser la méthode décrite ici: https://graphics.stanford.edu/~seander/bithacks.html#IntegerAbs .
L'opération modulo est utilisée pour trouver un reste, vous voulez dire une valeur absolue. J'ai modifié la question parce qu'il devrait être si! Pos (x) alors x = x * -1. (pas manquait)
Je ne m'inquiéterais pas de l'efficacité d'une déclaration if. Concentrez-vous plutôt sur la lisibilité de votre code. Si vous identifiez un problème d'efficacité, concentrez-vous sur le profilage de votre code afin de détecter les véritables goulots d'étranglement.
Si vous souhaitez garder un œil sur l'efficacité pendant que vous codez, vous ne devez vous soucier que de la complexité de vos algorithmes.
Si les instructions sont très efficaces, il évalue n'importe quelle expression et modifie ensuite simplement le compteur programme en fonction de cette condition. Le compteur de programme stocke l'adresse de la prochaine instruction à exécuter.
La multiplication par -1 et la vérification si une valeur est supérieure à 0 peuvent être réduites à une seule instruction Assembly.
Trouver la racine d'un nombre et d'abord le mettre au carré est définitivement plus d'opérations que si une négation.
Le temps nécessaire pour créer une racine carrée est beaucoup plus long que celui nécessaire pour effectuer une condition. Si on vous a appris à éviter les conditionnels parce qu'ils sont lents, vous avez été mal informé. Elles sont beaucoup plus lentes que des opérations triviales telles que l’ajout ou la soustraction d’entiers ou le transfert de bits - c’est pourquoi le déroulement des boucles ne peut être avantageux que si vous effectuez de telles opérations. Mais dans le grand schéma des choses, les conditions sont bonnes et rapides, pas mauvaises et lentes. Faire quelque chose d'aussi compliqué que d'appeler une fonction ou de calculer une racine carrée pour éviter une déclaration conditionnelle est dingue.
Aussi, au lieu de (x = x * -1), pourquoi ne pas faire (x = 0 - x)? Peut-être que le compilateur les optimisera de la même manière, mais le second n'est-il pas plus simple de toute façon?
Utilisez-vous l'assemblage 8086? ;-)
; abs value of AX
cwd ; replicate the high bit into DX
xor ax, dx ; take 1's complement if negative; no change if positive
sub ax, dx ; AX is 2's complement if it was negative The standard
: absolute value method works on any register but is much
; slower:
or bx, bx ; see if number is negative
jge notneg ; if it is negative...
neg bx ; ...make it positive
notneg: ; jump to here if positive
(flagrante volée )
Si vous comparez simplement les valeurs absolues de deux nombres (par exemple, vous n'avez pas besoin de la valeur absolue après la comparaison), placez simplement les deux valeurs sur deux pour les rendre positives (supprimez le signe de chaque valeur), le grand carré sera plus grand que le petit carré.
Ce qui est plus rapide dépend beaucoup du compilateur et du processeur que vous ciblez. Sur la plupart des processeurs et tous les compilateurs x = (x> = 0)? x: -x; est le moyen le plus rapide d’obtenir une valeur absolue, mais en réalité, les fonctions standard offrent déjà déjà cette solution (par exemple, fabs ()). Il est compilé en comparaison suivie par une instruction d'assignation conditionnelle (CMOV), et non en saut conditionnel. Certaines plateformes manquent cependant de cette instruction. Bien que, le compilateur Intel (mais pas Microsoft ou GCC) convertirait automatiquement if () en affectation conditionnelle, et essaierait même d’optimiser les cycles (si possible).
Le code de branchement est en général plus lent que l’affectation conditionnelle, si la CPU utilise la prédiction statistique. if () peut être plus lent en moyenne si l'opération est répétée plusieurs fois et que le résultat de la condition change constamment. Les processeurs tels qu'Intel commenceraient à calculer les deux branches et abandonneraient l'invalide. Dans le cas de gros corps if () ou d'un grand nombre de cycles potentiellement critiques.
sqr () et sqrt () sur les processeurs Intel modernes sont des instructions intégrées simples et ne sont pas lentes, mais elles sont imprécises et le chargement des registres prendrait également du temps.
Question connexe: Pourquoi une instruction de branche d'UC est-elle lente?
Très probablement, le professeur voulait que les étudiants fassent des recherches sur ce sujet. C'est une question/tâche semi-provocatrice qui ne ferait que du bien, si les étudiants apprenaient à penser de manière indépendante et à rechercher des sources supplémentaires.
Je fais de la programmation graphique rétro en C pour 8088/8086 et appeler abs()
prend beaucoup de temps; je l'ai donc remplacé par:
/* assuming 'i' is int; this WILL NOT WORK on floating point */
if (i < 0) {
i = ~i + 1;
}
La raison pour laquelle cela est plus rapide est qu’elle échange essentiellement une CALL
en Assemblée contre une JNE
. L'appel d'une méthode modifie deux ou trois registres, en pousse plusieurs autres, place des arguments sur la pile et peut vider la file d'attente de prélecture. De plus, ces actions doivent être inversées à la fin de la fonction et tout cela coûte très cher au processeur.