2010-11-13 33 views
11

J'optimise un peu de code pour une micro-architecture Intel x86 Nehalem en utilisant l'intrinsèque SSE.Méthode la plus efficace pour stocker des produits 4 points dans un tableau contigu en C en utilisant les intrinsèques SSE

Une partie de mon programme calcule 4 produits points et ajoute chaque résultat aux valeurs précédentes dans un bloc contigu d'un tableau. Plus précisément,

tmp0 = _mm_dp_ps(A_0m, B_0m, 0xF1); 
tmp1 = _mm_dp_ps(A_1m, B_0m, 0xF2); 
tmp2 = _mm_dp_ps(A_2m, B_0m, 0xF4); 
tmp3 = _mm_dp_ps(A_3m, B_0m, 0xF8); 

tmp0 = _mm_add_ps(tmp0, tmp1); 
tmp0 = _mm_add_ps(tmp0, tmp2); 
tmp0 = _mm_add_ps(tmp0, tmp3); 
tmp0 = _mm_add_ps(tmp0, C_0n); 

_mm_storeu_ps(C_2, tmp0); 

Notez que je m'y prends en utilisant 4 registres xmm temporaires pour conserver le résultat de chaque produit scalaire. Dans chaque registre XMM, le résultat est placé dans un 32 bits uniques par rapport aux autres registres XMM temporaires de telle sorte que le résultat final ressemble à ceci:

tmp0 = R0-zéro-zéro-zéro

tmp1 = zéro -R1-zéro-zéro

tmp2 = zéro-zéro-R2-zéro

TMP3 = zéro-zéro-zéro-R3

Je combine les valeurs contenues dans chaque variable tmp dans une variable de XMM par les résumer avec les instructions suivantes:

tmp0 = _mm_add_ps(tmp0, tmp1); 
tmp0 = _mm_add_ps(tmp0, tmp2); 
tmp0 = _mm_add_ps(tmp0, tmp3); 

Enfin, j'ajouter le registre contenant les 4 résultats des produits de points à une partie contiguë d'un tableau de telle sorte que les indices du tableau sont incrémentés par un produit scalaire, comme si (C_0n sont les 4 valeurs actuellement en le tableau qui doit être mis à jour; C_2 est l'adresse pointant vers ces 4 valeurs):

tmp0 = _mm_add_ps(tmp0, C_0n); 
_mm_storeu_ps(C_2, tmp0); 

Je veux savoir s'il y a un moyen de prendre moins rond-point, plus efficace les résultats des produits de point et les ajouter au bloc contigu de le tableau. De cette façon, je fais 3 additions entre les registres qui ont seulement 1 valeur non nulle en eux. Il semble qu'il devrait y avoir un moyen plus efficace d'y parvenir.

J'apprécie toute aide. Je vous remercie.

Répondre

6

Pour un code comme celui-ci, j'aime stocker la "transposition" des A et des B, de sorte que {A_0m.x, A_1m.x, A_2m.x, A_3m.x} sont stockés dans un vecteur, etc. Ensuite, vous pouvez faire le produit scalaire en multipliant et en ajoutant, et quand vous avez terminé, vous avez tous les 4 produits de points dans un vecteur sans aucun mélange.

Ceci est fréquemment utilisé en raytracing, pour tester 4 rayons à la fois contre un plan (par exemple en traversant un kd-tree). Si vous n'avez pas le contrôle sur les données d'entrée, cependant, le surcoût de la transposition pourrait ne pas en valoir la peine. Le code fonctionnera également sur les machines pré-SSE4, bien que cela ne soit pas un problème.


Une petite note d'efficacité sur le code existant: au lieu de ce

tmp0 = _mm_add_ps(tmp0, tmp1); 
tmp0 = _mm_add_ps(tmp0, tmp2); 
tmp0 = _mm_add_ps(tmp0, tmp3); 
tmp0 = _mm_add_ps(tmp0, C_0n); 

Il est peut-être légèrement mieux faire:

tmp0 = _mm_add_ps(tmp0, tmp1); // 0 + 1 -> 0 
tmp2 = _mm_add_ps(tmp2, tmp3); // 2 + 3 -> 2 
tmp0 = _mm_add_ps(tmp0, tmp2); // 0 + 2 -> 0 
tmp0 = _mm_add_ps(tmp0, C_0n); 

Comme les deux premiers mm_add_ps « s sont complètement indépendant maintenant. De plus, je ne connais pas les horaires relatifs de l'ajout et du mélange, mais cela pourrait être légèrement plus rapide.


Espérons que ça aide.

1

Vous pouvez essayer de laisser le résultat du produit scalaire dans le mot faible et utiliser le magasin scalaire op _mm_store_ss pour enregistrer ce flottant à partir de chaque registre m128 dans l'emplacement approprié du tableau. Le tampon du magasin de Nehalem devrait accumuler des écritures consécutives sur la même ligne et les vider à L1 par lots.

Le moyen le plus pratique est de l'approche transposition de celion. La macro _MM_TRANSPOSE4_PS de MSVC fera la transposition pour vous.

+0

Vous devez toujours ajouter l'ancienne valeur (C_0n) à chaque produit scalaire avant le magasin. Ils seraient tous indépendants, donc ce ne serait peut-être pas trop lent, mais ce n'est pas beaucoup plus joli :) – celion

3

Il est également possible d'utiliser le hadd SSE3. Il s'est avéré plus rapide que l'utilisation de _dot_ps, dans certains tests triviaux. Ceci renvoie les produits 4 points qui pourraient être ajoutés.

static inline __m128 dot_p(const __m128 x, const __m128 y[4]) 
{ 
    __m128 z[4]; 

    z[0] = x * y[0]; 
    z[1] = x * y[1]; 
    z[2] = x * y[2]; 
    z[3] = x * y[3]; 
    z[0] = _mm_hadd_ps(z[0], z[1]); 
    z[2] = _mm_hadd_ps(z[2], z[3]); 
    z[0] = _mm_hadd_ps(z[0], z[2]); 

    return z[0]; 
} 
1

Je réalise que cette question est ancienne, mais pourquoi utiliser _mm_add_ps? Remplacez-le par:

tmp0 = _mm_or_ps(tmp0, tmp1); 
tmp2 = _mm_or_ps(tmp2, tmp3); 
tmp0 = _mm_or_ps(tmp0, tmp2); 

Vous pouvez probablement cacher une partie de la latence _mm_dp_ps. Le premier _mm_or_ps n'attend pas non plus les produits à 2 points finaux, et c'est une opération de bits (rapide). Enfin:

_mm_storeu_ps(C_2, _mm_add_ps(tmp0, C_0));