Author Topic: Classe mathématique  (Read 5227 times)

0 Members and 1 Guest are viewing this topic.

Offline ponce

Re : Classe mathématique
« Reply #15 on: 18 February 2013 à 09:57:28 »
En tout cas pour les trucs qui sont genre une instruction FPU, c'est plus lent que les intrinsics.
(et sinon je rejoins h0bby1 sur le fait que c'est mieux d'optimiser des boucles que des petites opérations genre additions de vecteur toutes seules)

Offline h0bby1

  • Base
    • View Profile
Re : Classe mathématique
« Reply #16 on: 18 February 2013 à 13:35:08 »
aussi pour faire une lib de math, pour que ca vaille le coup, c'est quand meme mieux d'avoir tout un framework de fonctions qui utilisent les fonctions de base, type operation matrices, DCT/FFT, operation geometrique, physique ou autre, qui peut tirer avantage du framework en particulier, si c''est juste pour recoder la libC ca a pas grand interet, car les fonction de la libC desfois c'est plus semantique des que t'actives les optims, par example, si t'utilise un cos et sin dans la meme fonction, le compilo il va coller un fsincos, ce qu'il ne vas pas pouvoir faire si le programme n'utilise pas les fonction standard, je suis meme pas sur si en inlinant un fcos et un fsin en asm le compilo il va bien les reconnaitre et etre capable de remplacer par un fsincos, et il doit pouvoir detecter les chaines de dependences des instructions pour en paralelliser un maximum, ce qu'il ne pourra pas faire en utilisant des appels vers des fonction extern, a moins d'activer les optimisations complete du programme et le laisser inliner tout ce qu'il peut

et la plupart des moteurs utilisent deja la libC et on deja leur propre routine de math et de vecteurs, donc pour que ca vaille le coup d'utiliser une lib de base pour les math, qui doivent changer tout les appels vers la libC, ou sinon la lib dois les remplacer de facon transparente, et recoder un runtime C entierement transparent, ca peut vite devenir aussi gallere, car il faut gerer tout un tas de trucs niveau etat du module qui l'utilise, le multithread, et pouvoir remplacer entierement la libC du compilo, sachant dans pas mal de cas, le compilo il va meme pas utiliser strictement un appel vers la fonction exacte du runtime, il va souvent coller des trucs en inline et faire sa popotte, les compilos modernes ils reconnaissent les fonctions de libC et pas mal de type d'algos de boucles et compagine, et il sait deja optimiser les operation math des instruction qu'il connait, dans la plupart des cas ca peut devenir assez difficile d'avoir un tel niveau de flexibilité sur le code assembleur generé avec des fonctions personalisée meme si elle sont equivalente en elle meme, le compilo offre deja tout un tas de parametres et d'optimisations multi thread safe qui tireront meilleur partie des fonction de sa propre libC que d'appel vers des fonctions extern personalisée

mais ya certains trucs qui pourrait quand meme s'averer util, mais ca se jouerai plutot a la rigueur au niveau de fonction de plus haut niveaus, qui a la rigueur meme utilises les fonctions de la libc et coder entierement en C en tirant partie un maximum des optimisations que le compilateur peut deja faire lui meme, mais c'est vrai que le runtime C peut introduire aussi une certaine couche de complexité au niveau de la gestion des registres de status de controle ( http://www.website.masmforum.com/tutorials/fptute/fpuchap1.htm#cword )  , pour la precision, les options de rounding, comment il reagi aux division par zero, comment il gere les exeptions etc, qui doit maintenir ces etats dans chaque dll et/ou thread independement, ce qui peut aussi interferer avec des fonction du fpu si tu les codes toi meme, car le crt manipule plus au moins automatiquement certains registre d'etat en fonction des options de comipilations et gerer tout un tas de trucs, qu'il faudrai aussi pouvoir prendre en compte pour etre sur que ca interfere pas, ou verifier les registres de controles a chaque operation

ya des libs de math comme fftw ou meme les codecs comme xvid ou ffmpeg, ce sont de bons examples de libs qui sont sensées executer des operations mathematiques et vectorielle de facon optimisée avec de l'assembler en interfacant avec du C, mais la synthaxe peut rapidement devenir assez chiante a utiliser, si faut utiliser des marcos pour declarer des variable ou fonction alignées a chaque fois, ou des macros pour declarer les prototypes des fonctions, ca peut vite devenir bien gallere a debugger et utiliser en tant que lib generale, avec de la synthaxe c++ et/ou des templates et de operateurs, ya moyen de faire des trucs pour avoir des fonctions mathematiques generiques, que ce soit pour des fonction d'algebre, de fft, de geometrie, avoir des bonne fonctions helpers pour calculer des fonction mathematique utile, genre nombre complexes et quaternions, de la trigo, mais pas toujours evident de bien pouvoir gerer les operations float sans utiliser la libC de facon optimale, mais dans l'absolue si l'application hote peut dependre entierement sur la lib de facon transparente pour ses operation mathematiques de haut niveau, elle a meme pas besoin d'utiliser elle meme les appels vers la libC et le runtine du compilo du tout, mais a ce moment, faut aussi recoder tout les appels systemes, fichier,allocation memoire, random, fonctions strings, exeptions etc qui sont normalement gérés par la libC

a mon avis dans la plupart des cas c'est plus interressant d'utiliser un bon compilateur et tirer un maximum de ses optims que recoder les fonctions de la libC, et a la rigueur redistribuer un fichier binaire precompilé avec les bonnes options, ou systeme avec une API qui permet autant de flexbilité que les options du compilateur, mais ca me parait difficile

le compilateur intel il est dispo gratuitement sous linux, et il s'integre entiermeent dans visual studio, et il optimise super bien deja, ils disent qu'il gagne 25% sur une compilation de mysql avec icc vs gcc sous linux, et il connai bien tout les cpu, il a un bon cpu dispactcher et plein d'options d'optimisation, gcc il optimise relativement bien, mais son probleme c'est sa libC, la libC de gcc c'est un vrai cauchemar ambulant , c'est pour la portabilité, pour que la libC puisse se compiler sur le plus de plateforme possible, mais niveau optimisation c'est pas trop ca, et la synthaxe assembler de gcc elle est hardcore, mais il gere les intrinseques sse, ou ya moyen de l'interfacer avec des routines compilées par nasm
« Last Edit: 18 February 2013 à 14:19:05 by h0bby1 »

Offline h0bby1

  • Base
    • View Profile
Re : Classe mathématique
« Reply #17 on: 18 February 2013 à 15:52:12 »

Offline h0bby1

  • Base
    • View Profile
Re : Classe mathématique
« Reply #18 on: 19 February 2013 à 07:02:29 »
par ex un compilo intelligent comme le compilateur intel il arrive bien a optimiser avec des inlines et les intrinsec

   
Code: [Select]
#include <xmmintrin.h>

typedef float _CRT_ALIGN (16) mat3x3f_t[16];
typedef float _CRT_ALIGN (16) vec3f_t[4];

static __inline void zero_vec3 (vec3f_t v)
{
_mm_store_ps(v,_mm_setzero_ps());
}
static __inline void copy_vec3 (vec3f_t v,const vec3f_t v2)
{
_mm_store_ps(v,_mm_load_ps(v2));
}
static __inline void cross_vec3(const vec3f_t v,const vec3f_t v2,vec3f_t out ) {
__m128 a ;
__m128 b;
__m128 ea , eb;
__m128 xa ;
__m128 xb ;
a=_mm_load_ps(v);
b=_mm_load_ps(v2);
// set to a[1][2][0][3] , b[2][0][1][3]
ea = _mm_shuffle_ps( a, a, _MM_SHUFFLE(3,0,2,1) );
eb = _mm_shuffle_ps( b, b, _MM_SHUFFLE(3,1,0,2) );
// multiply
xa = _mm_mul_ps( ea , eb );
// set to a[2][0][1][3] , b[1][2][0][3]
a = _mm_shuffle_ps( a, a, _MM_SHUFFLE(3,1,0,2) );
b = _mm_shuffle_ps( b, b, _MM_SHUFFLE(3,0,2,1) );
// multiply
xb = _mm_mul_ps( a , b );
// subtract
_mm_store_ps(out,_mm_sub_ps( xa , xb ));
}



static __inline float dot_vec3(const vec3f_t v,const vec3f_t vec2)
{
return (v[0]*vec2[0]+v[1]*vec2[1]+v[2]*vec2[2]);
}


static __inline void normalize_vec3(vec3f_t v)
{
float d;
__m128 scl;
d = v[0]*v[0]+v[1]*v[1]+v[2]*v[2];
scl  =_mm_rsqrt_ss(_mm_load_ss(&d));
_mm_store_ps(v,_mm_mul_ps(_mm_load_ps(v),_mm_shuffle_ps(scl,scl,0x00)));
}

static __inline void mul_vec3x3(vec3f_t v,const mat3x3f_t m)
{
__m128 vec,mul;

vec = _mm_load_ps(v);

mul = _mm_mul_ps(_mm_shuffle_ps(vec,vec,0x00),_mm_load_ps(&m[0]));
mul = _mm_add_ps(mul,_mm_mul_ps (_mm_shuffle_ps(vec,vec,0x55),_mm_load_ps(&m[4])));
mul = _mm_add_ps(mul,_mm_mul_ps (_mm_shuffle_ps(vec,vec,0xAA),_mm_load_ps(&m[8])));

_mm_store_ps(v,mul);
/*

float x,y,z;
x = v[0];
y = v[1];
z = v[2];

v[0]=x*m[0]+y*m[4]+z*m[8];
v[1]=x*m[1]+y*m[5]+z*m[9];
v[2]=x*m[2]+y*m[6]+z*m[10];
*/
}

static __inline void sub_scale_vec3_o(const vec3f_t v,const vec3f_t dr,float d,vec3f_t out)
{
__m128 _d;
_d = _mm_load_ss(&d);
_mm_store_ps (out,_mm_sub_ps(_mm_load_ps(v),_mm_mul_ps(_mm_load_ps(dr),_mm_shuffle_ps(_d,_d,0x00))));

/*
out[0] = v[0]-dr[0]*d;
out[1] = v[1]-dr[1]*d;
out[2] = v[2]-dr[2]*d;
*/

}

   
Code: [Select]
vec3f_t normVec;
                {
vec3f_t     binormal;
vec3f_t     tangent;
vec3f_t     test_norm;
                vec3f_t     tNorm;
mat3x3f_t mat;
float        dist,plane_det;

        sub_scale_vec3_o   (test_norm,normVec,dist,tangent);
normalize_vec3 (tangent);
cross_vec3 (normVec, tangent,binormal );
normalize_vec3 (binormal);

copy_vec3(&mat[0],tangent);
copy_vec3(&mat[4],binormal);
copy_vec3(&mat[8],normVec);
mul_vec3x3 (tNorm,mat);
copy_vec3 (normVec,tNorm);
                }

avec les optims activées, il sort ca :

   
Code: [Select]
                         movaps      xmm3,xmmword ptr [esp+2D0h]
movaps      xmm2,xmmword ptr [esp+280h]
movaps      xmmword ptr [esp+30h],xmm3
shufps      xmm1,xmm1,0
mulps       xmm1,xmm3
subps       xmm2,xmm1
movaps      xmmword ptr [esp+290h],xmm2
movaps      xmm4,xmm2
movaps      xmm1,xmm3
unpckhps    xmm4,xmm2
movaps      xmm5,xmm2
mulss       xmm5,xmm5
movss       xmm6,dword ptr [esp+294h]
mulss       xmm6,xmm6
shufps      xmm1,xmm3,0C9h
mulss       xmm4,xmm4
addss       xmm6,xmm4
addss       xmm6,xmm5
movaps      xmm5,xmm3
shufps      xmm5,xmm3,0D2h
rsqrtss     xmm6,xmm6
shufps      xmm6,xmm6,0
mulps       xmm2,xmm6
movaps      xmmword ptr [esp+290h],xmm2
movaps      xmmword ptr [esp+20h],xmm2
movaps      xmm7,xmm2
movaps      xmm4,xmm2
shufps      xmm7,xmm2,0D2h
mulps       xmm1,xmm7
shufps      xmm4,xmm2,0C9h
mulps       xmm5,xmm4
subps       xmm1,xmm5
movaps      xmmword ptr [esp+2A0h],xmm1
movaps      xmm6,xmm1
movss       xmm5,dword ptr [esp+2A4h]
mulss       xmm5,xmm5
unpckhps    xmm6,xmm1
movaps      xmm4,xmm1
mulss       xmm4,xmm4
mulss       xmm6,xmm6
addss       xmm5,xmm6
addss       xmm5,xmm4
rsqrtss     xmm5,xmm5
shufps      xmm5,xmm5,0
mulps       xmm1,xmm5
movaps      xmmword ptr [esp+2A0h],xmm1
movaps      xmm1,xmm0
shufps      xmm1,xmm0,0
mulps       xmm1,xmm2
movaps      xmm2,xmm0
shufps      xmm2,xmm0,55h
mulps       xmm2,xmm3
shufps      xmm0,xmm0,0AAh
mulps       xmm0,xmmword ptr [esp+40h]
addps       xmm1,xmm2
addps       xmm1,xmm0
movaps      xmmword ptr [esp+270h],xmm1
movaps      xmmword ptr [esp+2D0h],xmm1

il garde bien les valeurs sur les registres, ya pas trop d'acces memoire, comparé a des appels avec la pile ou bien stocker/relire le resultat a chaque fois en memoire

et on peut aussi utiliser ca dans une classe de vecteur avec des operateurs avec le corps declaré dans la classe en inline, ce qui peut quand meme permettre assez simplement d'avoir du code relativement bien optimisé generé par le compilateur, mais ca elimine aussi le cpu dispatching, donc par example sur un pentium 2 l'exe crashera lamentablement, donc apres faut eventuellement coder plusieurs fonction et utiliser des pointeurs de fonction avec une detection cpu, ou bien le determiner au moment de la compile comme il font souvent sous linux, soit en utilisant un pointer de fonction pour faire les operations dans la classe qui est initialisé au runtime, soit avec des classes virtuelles qui sont instanciée avec les bonnes fonction au runtime, mais ca empeche le inline, et les classes virtuelle et l'optimisation ca fait pas toujours bon menage, ya moyen sinon avec des templates en compilant le template avec une classe qui contient les pointeurs de fonction inlinable et utiliser le bon template au moment du runtime ca doit pouvoir fonctionner pour pouvoir avoir plusieurs versions des classes de vecteurs avec du code asm specifique inliné selectioné au runtime

« Last Edit: 19 February 2013 à 08:04:37 by h0bby1 »

Offline h0bby1

  • Base
    • View Profile
Re : Classe mathématique
« Reply #19 on: 19 February 2013 à 13:57:28 »
pour avoir la meme chose en c++ avec les operateurs en utilisant les fonction C en static inline du post precedent

ca c'est la classe avec le code assembleur specifique, qui fait des appels vers les fonction C en sse en inline

donc faire une classe comme ca pour tout les differentes archis

Code: [Select]

class vec3SSE
{
public:
vec3f_t v;

protected:

__forceinline  vec3SSE & normalize()
{
normalize_vec3(v);
return (*this);
}

__forceinline  vec3SSE &copy(const vec3SSE & v2)
{
copy_vec3(v,v2.v);
return (*this);
}
__forceinline  vec3SSE &copy(const vec3f_t v2)
{
copy_vec3(v,v2);
return (*this);
}
__forceinline  void zero()
{
zero_vec3(v);
}
__forceinline   void mul(const vec3SSE & v2)
{
mul_vec3(v,v2.v);
}
__forceinline   vec3SSE &scale(float d)
{
scale_vec3(v,d);
return (*this);
}
__forceinline   vec3SSE &mul_mat3x3(const mat3x3f_t m)
{
mul_vec3x3(v,m);
return (*this);
}
__forceinline  vec3SSE get_normalized()const
{
vec3SSE Out;
normalize_vec3_o(v,Out.v);
return Out;
}
__forceinline   void add(const vec3SSE & v2,vec3SSE & out)const
{
add_vec3(v,v2.v,out.v);
}

__forceinline   vec3SSE scale_o(float d)const
{
vec3SSE Out;
scale_vec3_o(v,d,Out.v);
return Out;
}
__forceinline   vec3SSE sub (const vec3SSE &v2)const
{
vec3SSE out;
sub_vec3 (v,v2.v,out.v);
return out;
}
__forceinline   void sub_scale_o(const vec3SSE & dr,float d,vec3SSE & out)const
{
sub_scale_vec3_o(v,dr.v,d,out.v);
}
__forceinline  vec3SSE cross(const vec3SSE &v2) const
{
vec3SSE out;
cross_vec3(v,v2.v,out.v);
return out;
}
__forceinline   float dot(const vec3SSE & vec2)const
{
return (v[0]*vec2.v[0]+v[1]*vec2.v[1]+v[2]*vec2.v[2]);
}
__forceinline   float dot(const vec3f_t vec2)const
{
return (v[0]*vec2[0]+v[1]*vec2[1]+v[2]*vec2[2]);
}
__forceinline   float dot_self()const
{
return (v[0]*v[0]+v[1]*v[1]+v[2]*v[2]);
}
__forceinline   float sq_dist(const vec3SSE & vec2)const
{
float d;
vec3SSE dv;
sub_vec3(vec2.v,v,dv.v);
d = libc_fabsf(dv.dot_self());
return d;
}
__forceinline   float dist(vec3SSE & vec2)const
{
vec3SSE dv;
sub_vec3(vec2.v,v,dv.v);
return  libc_sqrtf(dv.dot_self());
}
__forceinline   float length()const
{
return libc_sqrtf(dot_self());
}
__forceinline   double length(vec3d_t v)const
{
return libc_sqrtd(v[0]*v[0]+v[1]*v[1]+v[2]*v[2]);
}
__forceinline   vec3SSE mul_mat3x3_o(const mat3x3f_t m)const
{
vec3SSE  out;
mul_vec3x3_o(v,m,out.v);
return out;
}
};


le template qui doit etre compilé et instancié au runtime par l'application qui contient les operateurs c++ pour utiliser la classe qui contient las routines assembleur specifiques

Code: [Select]
template <typename m_type>
class vec3:public m_type
{
public:
vec3()
{
zero();
}
vec3(float x,float y,float z)
{
v[0]=x;
v[1]=y;
v[2]=z;
}
//copy
__forceinline  m_type &operator=(const m_type &in)
{
return this->copy(in);
}
__forceinline m_type &operator=(const vec3f_t &in)
{
return (*this=in);
}
__forceinline float &operator[](int idx)
{
return v[idx];
}


//Matrices operator on self
__forceinline m_type &operator*=(const CMat3f &op)
{
return this->mul_mat3x3(op.m);
}
__forceinline  m_type &operator*=(const mat3x3f_t op)
{
return this->mul_mat3x3(op);
}

//scale self
__forceinline  m_type &operator*=(float d)
{
return this->scale(d);
}
//normalize self
__forceinline void lengthTo1()
{
this->normalize();
}

//const Matrix operator
__forceinline m_type operator*(const CMat3f &op)const
{
return this->mul_mat3x3_o(op.m,out.v);
}
__forceinline m_type operator*(const mat3x3f_t op)const
{
return this->mul_mat3x3_o(op);
}

//const normalize
__forceinline  m_type getlengthTo1()const
{
return this->get_normalized();
}

//scale
__forceinline  m_type operator*(float d)const
{
return this->scale_o(d);
}
//subtract
__forceinline  m_type operator-(const m_type &vec)const
{
return this->sub(vec);
}

//corss product
__forceinline  m_type operator|(const m_type &vec)const
{
return this->cross(vec);
}

//dot product
__forceinline  float operator^(const m_type &vec)const
{
return this->dot(vec);
}
//addittion
__forceinline m_type operator+(const m_type &vec)const
{
return this->add (vec.v,v,ret.v);
}
};





le code avec les operateurs c++

Code: [Select]
                vec3<vec3SSE> tNorm;
                vec3<vec3SSE> normVec;
vec3<vec3SSE> tangent,nt;
vec3<vec3SSE> binormal,nb;
vec3<vec3SSE> test_norm;
mat3x3f_t mat;
float dist,plane_det;


tNorm *= 0.5f; //scale

plane_det = -(normVec^rayPos); //dot
test_norm = rayPos;

test_norm.v[0] = test_norm.v[0]+1.0;

dist = (test_norm^normVec)+plane_det;         //dot

tangent = test_norm-normVec*dist; //sub scaled
binormal = normVec|tangent.getlengthTo1();         //normalize + cross

copy_vec3 (&mat[0],tangent.v);
copy_vec3 (&mat[4],binormal.v);
copy_vec3 (&mat[8],normVec.v);

tNorm *=mat;         //3x3 matrix mul
normVec =tNorm.getlengthTo1();                                  //normalize

le compilateur compile

Code: [Select]
              movaps      xmm3,xmmword ptr [esp+280h]
movss       xmm4,dword ptr [esp+290h]
movss       xmm0,dword ptr [esp+280h]
movss       xmm6,dword ptr [esp+284h]
mulss       xmm6,dword ptr [esp+294h]
movss       xmm7,dword ptr [esp+288h]
mulss       xmm7,dword ptr [esp+298h]
movss       xmm2,dword ptr [esp+4]
addss       xmm2,dword ptr [esp+280h]
mulss       xmm0,xmm4
mulss       xmm4,xmm2
addss       xmm0,xmm6
addss       xmm0,xmm7
pxor        xmm7,xmm7
mov         dword ptr [esp],3F000000h
movss       xmm5,dword ptr [esp]
movaps      xmmword ptr [esp+240h],xmm3
movaps      xmm3,xmmword ptr [esp+290h]
movss       xmm1,dword ptr [esp+244h]
mulss       xmm1,dword ptr [esp+294h]
movss       xmm6,dword ptr [esp+248h]
mulss       xmm6,dword ptr [esp+298h]
movss       dword ptr [esp+240h],xmm2
shufps      xmm5,xmm5,0
mulps       xmm5,xmmword ptr [esp+10h]
subss       xmm7,xmm0
addss       xmm7,xmm1
movss       xmm1,dword ptr [esp+244h]
movss       xmm0,dword ptr [esp+24Ch]
addss       xmm7,xmm6
addss       xmm7,xmm4
movss       xmm4,dword ptr [esp+248h]
unpcklps    xmm1,xmm0
shufps      xmm7,xmm7,0
mulps       xmm7,xmm3
unpcklps    xmm2,xmm4
unpcklps    xmm2,xmm1
movaps      xmm1,xmm3
subps       xmm2,xmm7
movaps      xmmword ptr [esp+230h],xmm2
movss       xmm4,dword ptr [esp+234h]
movaps      xmm6,xmm2
mulss       xmm4,xmm4
shufps      xmm1,xmm3,0C9h
unpckhps    xmm6,xmm2
movaps      xmm0,xmm2
mulss       xmm0,xmm0
mulss       xmm6,xmm6
addss       xmm4,xmm6
addss       xmm4,xmm0
movaps      xmm0,xmm3
shufps      xmm0,xmm3,0D2h
rsqrtss     xmm4,xmm4
shufps      xmm4,xmm4,0
mulps       xmm4,xmm2
movaps      xmm7,xmm4
shufps      xmm7,xmm4,0D2h
mulps       xmm1,xmm7
shufps      xmm4,xmm4,0C9h
mulps       xmm0,xmm4
subps       xmm1,xmm0
movaps      xmm0,xmm5
shufps      xmm0,xmm5,0
mulps       xmm0,xmm2
movaps      xmm2,xmm5
shufps      xmm2,xmm5,55h
mulps       xmm2,xmm1
shufps      xmm5,xmm5,0AAh
mulps       xmm5,xmm3
addps       xmm0,xmm2
addps       xmm0,xmm5
movaps      xmmword ptr [esp+10h],xmm0
movaps      xmm5,xmm0
movss       xmm2,dword ptr [esp+14h]
mulss       xmm2,xmm2
movaps      xmm1,xmm0
mulss       xmm1,xmm1
unpckhps    xmm5,xmm0
mulss       xmm5,xmm5
addss       xmm2,xmm5
addss       xmm2,xmm1
rsqrtss     xmm2,xmm2
shufps      xmm2,xmm2,0
mulps       xmm0,xmm2
movaps      xmmword ptr [esp+290h],xmm0


meme avec la synthaxe c++ il arrive a bien inliner et garder les valeurs sur les registres et bien ordonner les instructions

et apres il faut utiliser                 

vec3<vec3SSE>         tNorm;

ou                 

vec3<vec3FPU>         tNorm;


pour avoir les operateurs qui utilisent les bonnes fonctions


apres il faut coder une version des fonction par type de vecteur dans l'application et utiliser la bonne au runtime, ca permet d'avoir les operateurs codé qu'une seule fois qui utilise les fonctions assembleur specifiques, qui la utilisent les fonction static inline C, ou le code assembleur specifique , et l'utiliser comme classe de base (non virtuelle) du template qui contient les operateurs, comme ca le compilateur peut bien inliner les fonctions en assembleur et optimiser la fonction specifique sans utiliser de fonctions virtuelles

Code: [Select]
template <typename m_type>
class TangantComputer
{
public:

void computeTangeant(vec3<m_type> &normVec, const vec3<m_type> &rayPos, const vec3<m_type> &texNorm)
{
vec3<m_type> tNorm;
vec3<m_type> test_norm;
vec3<m_type> tangent;
vec3<m_type> binormal;
mat3x3f_t mat;
float dist,plane_det;


tNorm *= 0.5f; //scale

plane_det = -(normVec^rayPos); //dot
test_norm = rayPos;

test_norm.v[0] = test_norm.v[0]+1.0;

dist = (test_norm^normVec)+plane_det;         //dot

tangent = test_norm-normVec*dist; //sub scaled
binormal = normVec|tangent.getlengthTo1();         //normalize + cross

copy_vec3 (&mat[0],tangent.v);
copy_vec3 (&mat[4],binormal.v);
copy_vec3 (&mat[8],normVec.v);

tNorm *=mat;         //3x3 matrix mul
normVec =tNorm.getlengthTo1();                                  //normalize
}

};

TangantComputer<vec3SSE>   TangeantSSE;
TangantComputer<vecFPU>    TangeantFPU;

void compute_tangeants(int has_sse)
{
unsigned char pixNorm[4];          //normale map en tangeant space

if(has_sse)
{
vec3<vec3SSE> normVec(1.0f,0.0f,0.0f);                 //normale du fragment
vec3<vec3SSE> posVec (255.6f,28.45f,12.2f);        //position du fragment
vec3<vec3SSE> texNorm;                                       //normale transformée en object space

        texNorm[0]= (pixNorm[0]-128.0);
          texNorm[1]= (pixNorm[1]-128.0);
            texNorm[2]= (pixNorm[2]-128.0);

TangeantSSE .computeTangeant(normVec,posVec,texNorm);
              //normVec contient la normale transformée
}
else
{
vec3<vec3FPU> normVec(1.0f,0.0f,0.0f);              //normale du fragment
vec3<vec3FPU> posVec (255.6f,28.45f,12.2f);     //position du fragment
vec3<vec3FPU> texNorm;                                    //normale transformée en object space

        texNorm[0]= (pixNorm[0]-128.0);
          texNorm[1]= (pixNorm[1]-128.0);
            texNorm[2]= (pixNorm[2]-128.0);


TangeantFPU .computeTangeant(normVec,posVec,texNorm);
              //normVec contient la normale transformée
}


}


meme si la synthaxe c++ a l'air un peu porcas, le compilo il va inliner tout l'assembleur dans la fonction et il utilise aucun objet temporaire ni meme la pile il garde tout bien sur ses registres et il suffi de copier les données d'entrée dans avec le constructeur ou un operateur de copie optimisé, voir utiliser des directives openMP pour paraleliser la boucle si c'est a faire sur un array et ya moyen d'avoir une synthaxe relativement intuitive avec les operateurs c++ pour coder les operations qui utilisent les fonctions optimisées, ca bypass entierement le runtime C et ca permet de sortir du code compact bien optimisé par le compilo pour different cpus de facon portable et l'algorythme principal avec les operateurs est reutilisable pour compiler les differentes version de l'assembleur inliné, et en mode debug il suffi de pas activer les inlines dans les options de compilation pour pouvoir debugger avec les appels de fonction normaux, donc ca garde la souplesse des options de compiles pour generer l'assembleur tout en generant du code assembleur specifique optimisé
« Last Edit: 19 February 2013 à 17:54:08 by h0bby1 »

Offline h0bby1

  • Base
    • View Profile
Re : Classe mathématique
« Reply #20 on: 20 February 2013 à 07:45:47 »
apres ca depend strategiquement ou tu veux interfacer la lib de math

si c'est pour principlament juste faire les fonction de bases, genre operations sur les vecteurs et operations trigos de base, a ce moment il faut que l'application utilise directement ces fonctions ou classes de la lib pour faire ses propres algorythmes avec, et si tu veux pouvoir garder l'inlining qui est quand meme important dans ce cas de figure ou l'algorythm de l'application va consister en une serie d'appel vers ces fonctions simples, dans ce cas il faut mieux inliner le plus possible ces fonctions pour que le compilo elimine tout les objets temporaire et les appels, mais il faut que l'application fasse elle meme le cpu dispatching si besoin est, et il faut que l'application puisse utiliser ces operations de facon le plus optimale possible en partant de ses données avec les fonctions ou classes de vecteurs , donc logiquement que l'application utilise les types de donnée de la librairie en interne le plus possible pour eviter les converstion a chaque operation, donc ca peut devenir gallere pour faire un cpu dispatching efficace

ou alors il faut coder un maximum toute les fonctions de haut niveau dans la lib de math, et que la lib de math puisse gerer un niveau d'abstraction assez elevé, qu'elle puisse gerer des classes d'objet 3D, mesh, texture en haut niveau et l'application n'as pas a connaitre du tout les types de donnée internes des pixels/vertex et autre vecteurs, et la lib peut s'occuper de gerer le cpu dispatching elle meme, et ca peut etre quasiment transparent pour l'application si la lib utilise en interne les bonne classes pour faire tel ou tel operation sur tel type de donnée en prenant en compte le cpu, ce que fait globalement la lib math kernel d'intel et l'application manipule uniquement des classes de haut niveau pour effectuer des operations dessus, mais ca peut empecher l'application d'utiliser les optimisation sur des calculs personalisés

par example le sse4 a une instruction qui permet de faire un dot product sur un vecteur 3D en une instruction, c'est clair que ca peut valoir le coup de faire une classe qui utilise cette instruction pour le dot_vec3 quand c'est present, mais du coup faut pouvoir avoir une version de chaque fonction compilée avec le bon assembleur et la selectionner

sinon il doit yavoir moyen avec des classes virtuelle, avec une classe de base qui a tout les operations en fonction virtuelle pure, et la libraire de math instancie la bonne classe de vecteur ou autre qui contient les bonnes instruction et renvoi un pointeur sur la classe de base a l'application, mais ca empeche de pouvoir avoir l'inlining, meme si ca permet d'avoir un code unique pour les fonctions de plus haut niveau qui font des appels vers les methodes virtuelle, si ces methodes virtuelles sont des operations simples de add/mul qui peuvent creer des objets temporaire, ca perd quand meme pas mal d'interet, meme si ca permet surement de gagner un peu en taille car il n'y a qu'une seule version des operations de haut niveau qui font des appels sur une classe virtuelle ou des pointeurs de fonctions, niveau performance c'est pas vraiment optimal, comparé a la version toute inlinée sans aucun objet temporaire, pour les operations simples de base qui doivent etre typiquement executée un certain nombre de fois les unes a la suite des autre en prenant en compte le resultat de l'operation precedente, faut mieux faire comme ca avec des templates inlinés et choisir la bonne classe au runtime

a la rigueur c'est possible d'utiliser des classes virtuelles et/ou pointeurs de fonctions pour les fonctions de haut niveau qui contiennent le code optimisé pour tel ou tel cpu pour eviter d'avoir du branching a faire pour chaque appel, dans le cas ou les fonctions sont relativement independante les unes des autres et utilisent pas le resultat de retour ou qu'il n'y pas un grand interet a ce que le compilo puisse optimiser les appels successifs, par ex faire une classe de texture virtuelle avec 2 classes qui la derive en utilisant les classes de vecteurs specifiques pour faire ses operations, et instancier la bonne classe en fonction du cpu et renvoyer un pointeur vers la classe virtuelle de base avec ses methodes virtuelle instanciées sur la classe qui contient les routines optimisées

du style

class meshBase
{
   void computeTangeant()=0;
};


class meshSSE:meshBase
{

   TangantComputer<vec3SSE>   TangeantSSE;
   void computeTangeant(...)
  {
          vec3<vec3SSE>         normVec;
        TangeantSSE.computeTangeant(..);
  }
};



class meshFPU:meshBase
{

   TangantComputer<vec3FPU>   TangeantFPU;
   void computeTangeant(...)
  {
          vec3<vec3FPU>         normVec;
        TangeantFPU.computeTangeant(..);
  }
};



meshBase *getMeshClass()
{
   if(has_sse)
      return new meshSSE();
   else
     return new meshFPU();
}


//---------------------------------------
meshBase *MyMesh;

MyMesh=getMeshClass();


MyMesh->computetangeant();


et comme ca il fera automatiquement l'appel vers la bonne fonction optimisée dans l'appli en utilisant une classe virtuelle de base pour faire les appels vers les fonction de haut niveau avec le cpu dispatching sans branching, et les classes templates seront compilées avec le bon code inlinée

mais pour des fonctions simples de math de base qui doivent etre appelées en relation les unes des autre a la suite, ca vaut pas le coup si chaque operation doit etre precédée d'un appel de fonction virtuelle, ca perd pas mal d'interet niveau performance, et le compilo il peut rien optimiser du tout sur l'algorythm general qui utilise ces fonctions, et les fonction de la libC , le compilo il sait deja ce qu'il ya dedans et il connait les intructions fpu et comment optimiser la plupart des algorythmes avec, ce qu'il ne pourra pas faire dans le cas d'appel virtuels ou sur des pointeurs fonction, il peut gerer les appels externe si les options precise 'inline anything possible' et 'optimisation du programme complet' et qu'il peut coller le code des appels de fonction d'un fichier C different, ce qui implique qu'il doit prendre en compte tout les fichiers du programme pour compiler le fichier ou ya les appels, et c'est pas sur que le comportement soit vraiment previsible
« Last Edit: 20 February 2013 à 08:37:35 by h0bby1 »