[Défit !] Normaliser un vecteur

Normaliser un vecteur [Défit !] - ASM - Programmation

Marsh Posté le 26-03-2006 à 12:44:04    

Salut,
 
J'essaies d'écrire le code le plus rapide pour normaliser un vecteur. Après pas mal de tatonnement je suis arrivé au code suivant :
void  Normalize(TDVector3D &V)
{
  __asm
  {
    mov    eax,[ebp+0x8]
    fld    qword ptr [eax]        // ST0=X       X
    fld    ST                     // ST1=X ST0=X
    fmul   ST,ST                  // ST1=X ST0=X²
    fld    qword ptr [eax+0x8]    // ST2=X ST1=X² ST0=Y      Y
    fld    ST                     // ST3=X ST2=X² ST1=Y ST0=Y
    fmul   ST,ST                  // ST3=X ST2=X² ST1=Y ST0=Y²
    faddp  ST(2),ST               // ST2=X ST1=X²+Y² ST0=Y
    fld    qword ptr [eax+0x10]   // ST3=X ST2=X²+Y² ST1=Y ST0=Z     Z
    fld    ST                     // ST4=X ST3=X²+Y² ST2=Y ST1=Z ST0=Z
    fmul   ST,ST                  // ST4=X ST3=X²+Y² ST2=Y ST1=Z ST0=Z²
    faddp  ST(3),ST               // ST3=X ST2=X²+Y²+Z² ST1=Y ST0=Z
    fxch   ST(2)                  // ST3=X ST2=Z ST1=Y ST0=X²+Y²+Z²
    fsqrt                         // ST3=X ST2=Z ST1=Y ST0=Sqrt(X²+Y²+Z²)
    fld1                          // ST4=X ST3=Z ST2=Y ST1=Sqrt(X²+Y²+Z²) ST0=1
    fdivrp                        // ST3=X ST2=Z ST1=Y ST0=1/Sqrt(X²+Y²+Z²)=N
    fmul   ST(3),ST               // ST3=X*N ST2=Z ST1=Y ST0=N
    fmul   ST(2),ST               // ST3=X*N ST2=Z*N ST1=Y ST0=N
    fmulp                         // ST2=X*N ST1=Z*N ST0=Y*N
    fstp   qword ptr [eax+0x8]    // ST1=X*N ST0=Z*N     Y
    fstp   qword ptr [eax+0x10]   // ST0=X*N      Z
    fstp   qword ptr [eax]        // vide    X
    fwait
  }
}
 
ou j'ai défini le type vecteur :
typedef struct
{
  double X, Y, Z;
} TDVector3D;
 
Cela marche super bien, mais je voudrais encore plus rapide !!!
 
Je suis tombé que le bout de code suivant :
float fast_invSqrt(float x)
{
  float xhalf = 0.5f*x;
  int i = *(int*)&x; // get bits for floating value
  i = 0x5f3759df - (i>>1); // give initial guess y0
  x = *(float*)&i; // convert bits back to float
  x *= 1.5f - xhalf*x*x; // newton step, repeating this step increases accuracy
  x *= 1.5f - xhalf*x*x;
 
  return x;
}
 
qui calcule une approximation rapide de 1/Sqrt(x). J'ai voulu traduire ce code en assembleur pour l'intégrer dans le mien mais je n'y suis pas arrivé. Je ne sais pas comment traduire tous ces transtypages ....
 
J'ai aussi voulu mettre juste avant l'appel de fsqrt un test de nullité et d'égalité à 1. Le code suivant refuse de fonctionner :
    fldz
    fcomp
    fstsw  AX
    fwait
    jnz @Fin
 
idem avec un fld1. @Fin est l'étiquette de sortie de la routine puisqu'il n'y a rien à faire si la norme du vecteur vaut 0 ou 1.
 
Est-ce qu'une bonne âme pourrait m'aider.
 
Cordialement,
Lionel

Reply

Marsh Posté le 26-03-2006 à 12:44:04   

Reply

Marsh Posté le 26-03-2006 à 13:26:45    

il y a une code similaire dans le source de quake 3, fichier "code/game/q_math.c", ligne 532
 

Code :
  1. /*
  2. ** float q_rsqrt( float number )
  3. */
  4. float Q_rsqrt( float number )
  5. {
  6. long i;
  7. float x2, y;
  8. const float threehalfs = 1.5F;
  9. x2 = number * 0.5F;
  10. y  = number;
  11. i  = * ( long * ) &y;      // evil floating point bit level hacking
  12. i  = 0x5f3759df - ( i >> 1 );               // what the fuck?
  13. y  = * ( float * ) &i;
  14. y  = y * ( threehalfs - ( x2 * y * y ) );   // 1st iteration
  15. // y  = y * ( threehalfs - ( x2 * y * y ) );   // 2nd iteration, this can be removed
  16. #ifndef Q3_VM
  17. #ifdef __linux__
  18. assert( !isnan(y) ); // bk010122 - FPE?
  19. #endif
  20. #endif
  21. return y;
  22. }


 

Citation :


Je ne sais pas comment traduire tous ces transtypages ....


 
ca doit se faire assez facilement en asm
 

Code :
  1. int i = *(int*)&x


ca mappe l'image memoire du flottant x dans l'entier i
 

Citation :

x = *(float*)&i


meme chose mais dans l'autre sens
 
le but est de permettre d'appliquer des opérations "bit à bit" sur l'image mémoire d'un flottant

Reply

Marsh Posté le 27-03-2006 à 00:03:16    

Merci Skelter de ta réponse mais elle ne m'aide pas beaucoup. J'ai bien compris qu'il y avait un bidouillage sur les bits du nombre pour obtenir la première approximation pour les itérations de l'algorithme de Newton.
Je doute pas que cela se fasse facilement en asm, j'aimerais savoir comment ;)
 
A++
Lionel

Reply

Marsh Posté le 30-03-2006 à 10:16:23    

Bon j'ai trouvé, cela se résume à :
mov       eax, DWORD PTR [esp]    
sar       eax, 1                  
neg       eax                    
add       eax, 1597463007        
mov       DWORD PTR [esp+4], eax
 
J'ai aussi trouvé avec les instruction SSE, encore plus rapide !

Reply

Marsh Posté le 11-04-2006 à 16:21:04    

Citation :

J'ai aussi trouvé avec les instruction SSE, encore plus rapide !


 
Sauf que tu ne vas pas gagner grand chose si tu ne bascules pas en SoA, cad de { x,y,z,0 } à { x,x,x,x }/{ y,y,y,y }/{ z,z,z,z }; sinon tu gaspilles 1/4 des calculs.
 
Disons que tu persistes, il te faudra aussi user de rsqrt*s pour obtenir un quelconque gain. Vu le peu d'ulp que procurent ces instructions reciproques, il te faudra coller par dessus une iteration à la Newton-Raphson: x0 = rsqrtss(val), rsqrt = 0.5f*x0*(3.f-(val * x0²)).
Sauf qu'il y a une légere embrouille en 0 et au lieu de +/- inf tu te retrouveras avec un chouette NaN; la aussi ça se corrige sans branche aucune mais la latence continue de s'envoler.
 
Note: "float y; i  = * ( long * ) &y;" -> aliasing -> caca.

Reply

Marsh Posté le 11-06-2006 à 16:36:32    

Salut, Je me joint a la reflexion ...
 
J'ai trouvédu code sse qui fait (bien ?) le travail.

Code :
  1. movaps xmm2, xmm0
  2.  mulps xmm0, xmm0
  3.  movaps xmm1, xmm0
  4.  shufps xmm0, xmm1, 0x4e
  5.  addps xmm0, xmm1
  6.  movaps xmm1, xmm0
  7.  shufps xmm1, xmm1, 0x11
  8.  addps xmm0, xmm1
  9.  rsqrtps xmm0, xmm0
  10.  mulps xmm2, xmm0


Le probleme c'est qu'il faut de je donne a manger a xmm0.
Comme 'st une fonction de la classe, j'ai codé ca:

Code :
  1. inline void float4::NormalizeSSE()
  2. {
  3. cVector *p=this;
  4. __asm {
  5.  movups xmm0, [p]
  6.  movaps xmm2, xmm0
  7.  mulps xmm0, xmm0
  8.  movaps xmm1, xmm0
  9.  shufps xmm0, xmm1, 0x4e
  10.  addps xmm0, xmm1
  11.  movaps xmm1, xmm0
  12.  shufps xmm1, xmm1, 0x11
  13.  addps xmm0, xmm1
  14.  rsqrtps xmm0, xmm0
  15.  mulps xmm2, xmm0
  16.  movups [p], xmm2
  17. }
  18. }


 
Cependant je me doute bien qe c'est pas optimal mais je ne sais pas comme mette this dans xmm0 ...
 
Et c'est super long aussi :(
 
Allez, Salut tous


Message édité par Palito le 12-06-2006 à 10:44:16
Reply

Marsh Posté le 21-11-2006 à 22:54:09    

J'avais raté cette réponse.
 
Non, Palito, rsqrtps n'est pas à utiliser en lieu et place d'un 1/sqrt pour la bonne et simple raison que cela ne procure que ~12bits de précision. Comme indiqué plus haut, il faut 1 iteration de Newton-Raphson pour obtenir ~23bits sur la mantisse mais une implémentation naive ne recouvre pas le même domaine etc... au final, une fois tous les trous bouchés le gain est loin d'etre substantiel.

Reply

Marsh Posté le 21-11-2006 à 23:14:18    

oui je me suis rendu compte que c'etait pas super precis comme calcul, mais j'ai depuis zappé le truc (laisssant une normalisation en bon vieux c++), il faudrait que je m'y remette

Reply

Sujets relatifs:

Leave a Replay

Make sure you enter the(*)required information where indicate.HTML code is not allowed