3 votes

FPU, SSE simple virgule flottante. Lequel est le plus rapide ? sub ou mul

Dis-moi juste lequel est le plus rapide : sub o mul ?

Ma plate-forme cible est X86 ; FPU et SSE.

exemple :

LerpColorSolution1 utilise la multiplication.

LerpColorSolution2 utilise la soustraction.

lequel est le plus rapide ?

void LerpColorSolution1(const float* a, const float* b, float alpha, float* out)
{
    out[0] = a[0] + (b[0] - a[0]) * alpha;
    out[1] = a[1] + (b[1] - a[1]) * alpha;
    out[2] = a[2] + (b[2] - a[2]) * alpha;
    out[3] = a[3] + (b[3] - a[3]) * alpha;
}

void LerpColorSolution2(const float* a, const float* b, float alpha, float* out)
{
    float f = 1.0f - alpha;
    out[0] = a[0]*f + b[0] * alpha;
    out[1] = a[1]*f + b[1] * alpha;
    out[2] = a[2]*f + b[2] * alpha;
    out[3] = a[3]*f + b[3] * alpha;
}

Merci à tous ;)

10voto

Stephen Canon Points 58003

Juste pour le plaisir : en supposant que vous (ou votre compilateur) vectorisez vos deux approches (parce que bien sûr vous le feriez si vous recherchez la performance), et que vous ciblez un processeur x86 récent...

Une traduction directe de "LerpColorSolution1" en instructions AVX est la suivante :

VSUBPS  dst,   a,     b        // a[] - b[]
VSHUFPS alpha, alpha, alpha, 0 // splat alpha
VMULPS  dst,   alpha, dst      // alpha*(a[] - b[])
VADDPS  dst,   a,     dst      // a[] + alpha*(a[] - b[])

La chaîne de latence longue pour cette séquence est le sous-mul-add, qui a une latence totale de 3+5+3 = 11 cycles sur les processeurs Intel les plus récents. Le débit (en supposant que vous ne faites rien d'autre que ces opérations) est limité par l'utilisation du port 1, avec un pic théorique d'un LERP tous les deux cycles. (Je néglige intentionnellement le trafic de chargement/stockage et me concentre uniquement sur l'opération mathématique effectuée ici).

Si nous regardons votre "LerpColorSolution2" :

VSHUFPS alpha, alpha, alpha, 0 // splat alpha
VSUBPS  dst,   one,   alpha    // 1.0f - alpha, assumes "1.0f" kept in reg.
VMULPS  tmp,   alpha, b        // alpha*b[]
VMULPS  dst,   dst,   a        // (1-alpha)*a[]
VADDPS  dst,   dst,   tmp      // (1-alpha)*a[] + alpha*b[]

Maintenant, la chaîne de latence longue est shuffle-sub-mul-add, qui a une latence totale de 1+3+5+3 = 12 cycles ; le débit est maintenant limité par les ports 0 et 1, mais a toujours un pic d'un LERP tous les deux cycles. Vous devez retirer un µop supplémentaire pour chaque opération LERP, ce qui peut rendre le débit légèrement plus lent selon le contexte environnant.

Votre première solution est donc légèrement meilleure (ce qui n'est pas surprenant - même sans cette analyse détaillée, la règle approximative "moins d'opérations, c'est mieux" est une bonne règle empirique).


Haswell fait pencher les choses de manière significative en faveur de la première solution ; en utilisant la FMA, elle ne nécessite qu'un µop sur chacun des ports 0, 1 et 5, ce qui permet un débit théorique d'un LERP par cycle ; si la FMA améliore également la solution 2, elle nécessite toujours quatre µops, dont trois qui doivent être exécutés sur le port 0 ou 1. Cela limite la solution 2 à un pic théorique d'un LERP tous les 1,5 cycles -- 50% plus lent que la solution 1.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X