Question

J'ai une boucle écrite en C++ qui est exécutée pour chaque élément d'un grand tableau de nombres entiers.À l’intérieur de la boucle, je masque quelques bits de l’entier puis je trouve les valeurs min et max.J'ai entendu dire que si j'utilise des instructions SSE pour ces opérations, elles s'exécuteront beaucoup plus rapidement qu'une boucle normale écrite en utilisant des conditions AND au niveau du bit et if-else.Ma question est la suivante : dois-je suivre ces instructions SSE ?De plus, que se passe-t-il si mon code s'exécute sur un autre processeur ?Est-ce que cela fonctionnera toujours ou ces instructions sont spécifiques au processeur ?

Était-ce utile?

La solution

  1. les instructions SSE sont processeur spécifique. Vous pouvez rechercher quel processeur prend en charge la version SSE sur wikipedia.
  2. Si le code SSE sera plus rapide ou non dépend de nombreux facteurs: Le premier est bien sûr si le problème est lié-mémoire ou CPU liée. Si le bus mémoire est le goulot d'étranglement SSE ne va pas aider beaucoup. Essayez de simplifier vos calculs entiers, si cela rend le code plus rapide, il est probablement lié CPU, et vous avez une bonne chance d'accélérer.
  3. Sachez que l'écriture SIMD code est beaucoup plus difficile que d'écrire C ++ - code, et que le code résultant est beaucoup plus difficile à changer. Toujours garder le code C ++ à jour, vous voulez comme un commentaire et de vérifier l'exactitude de votre code assembleur.
  4. Pensez à utiliser une bibliothèque comme l'IPP, qui met en oeuvre les opérations courantes SIMD bas niveau optimisés pour différents processeurs.

Autres conseils

SIMD, dont l'ESS est un exemple, vous permet de faire la même opération sur plusieurs morceaux de données. Donc, vous n'obtiendrez aucun avantage à utiliser SSE en remplacement direct pour les opérations entières, vous n'obtenir des avantages si vous pouvez effectuer les opérations sur plusieurs éléments de données à la fois. Cela implique le chargement des valeurs de données qui sont contiguës à la mémoire, en faisant le traitement nécessaire, puis marcher à la prochaine série de valeurs dans le tableau.

Problèmes:

1 Si le chemin de code dépend des données en cours de traitement, SIMD devient beaucoup plus difficile à mettre en œuvre. Par exemple:

a = array [index];
a &= mask;
a >>= shift;
if (a < somevalue)
{
  a += 2;
  array [index] = a;
}
++index;

est pas facile à faire comme SIMD:

a1 = array [index] a2 = array [index+1] a3 = array [index+2] a4 = array [index+3]
a1 &= mask         a2 &= mask           a3 &= mask           a4 &= mask
a1 >>= shift       a2 >>= shift         a3 >>= shift         a4 >>= shift
if (a1<somevalue)  if (a2<somevalue)    if (a3<somevalue)    if (a4<somevalue)
  // help! can't conditionally perform this on each column, all columns must do the same thing
index += 4

2 Si les données ne sont pas Contigous charger ensuite les données dans les instructions SIMD est lourde

3 Le code est processeur spécifique. SSE est uniquement sur IA32 (Intel / AMD) et non tout le soutien de CPUs IA32 SSE.

Vous devez analyser l'algorithme et les données pour voir si elle peut être SSE'd et qui exige de savoir comment fonctionne SSE. Il y a beaucoup de documentation sur le site Web d'Intel.

Ce genre de problème est un exemple parfait d'où un bon niveau bas profileur est essentiel. (Quelque chose comme VTune) Il peut vous donner une idée beaucoup plus éclairée de l'endroit où vos hotspots se trouvent.

Je pense, de ce que vous décrivez est que votre point d'accès sera probablement des échecs de prévision branche résultant de calculs min / max en utilisant if / else. Par conséquent, l'utilisation intrinsics SIMD devrait vous permettre d'utiliser les instructions min / max, cependant, il pourrait être utile juste essayer d'utiliser un min sans agence / max caluculation à la place. Cela peut atteindre la plupart des gains avec moins de douleur.

Quelque chose comme ceci:

inline int 
minimum(int a, int b)
{
  int mask = (a - b) >> 31;
  return ((a & mask) | (b & ~mask));
}

Si vous utilisez des instructions SSE, vous êtes évidemment limité aux processeurs qui prennent en charge ces derniers. Cela signifie que x86, datant du Pentium 2 ou si (ne me souviens pas exactement quand ils ont été introduits, mais il est depuis longtemps)

SSE2, qui, pour autant que je me souvienne, est celui qui offre des opérations entières, est un peu plus récent (Pentium 3? Bien que les premiers processeurs AMD Athlon ne les ont pas pris en charge)

Dans tous les cas, vous avez deux options pour l'utilisation de ces instructions. Soit écrire tout le bloc de code dans l'assemblage (probablement une mauvaise idée. Cela rend pratiquement impossible pour le compilateur d'optimiser votre code, et il est très difficile pour un être humain d'écrire assembleur efficace).

Vous pouvez également utiliser les intrinsics disponibles avec votre compilateur (si ma mémoire est bonne, ils sont généralement définis dans xmmintrin.h)

Mais encore une fois, la performance ne peut pas améliorer. Code SSE pose des exigences supplémentaires des données qu'il traite. Principalement, celui de garder à l'esprit est que les données doivent être alignées sur les limites de 128 bits. Il devrait également y avoir peu ou pas de dépendances entre les valeurs chargées dans le même registre (un registre SSE 128 bits peut contenir 4 ints. Ajout du premier et le second ensemble est pas optimale. Mais en ajoutant quatre ints aux 4 ints correspondants dans un autre registre sera rapide)

Il peut être tentant d'utiliser une bibliothèque qui enveloppe tout le bas niveau tripoter SSE, mais cela pourrait aussi ruiner tout avantage potentiel de performance.

Je ne sais pas à quel point le soutien de fonctionnement entier de l'ESS est, de sorte que peut aussi être un facteur qui peut limiter les performances. SSE est principalement destiné à accélérer les opérations en virgule flottante.

Si vous avez l'intention d'utiliser Visual C ++ Microsoft, vous devriez lire ceci:

http://www.codeproject.com/KB/recipes/sseintro.aspx

Nous avons mis en place un code de traitement d'image, semblable à ce que vous décrivez, mais sur un tableau d'octets, Dans SSE. Le rapport au code speedup C est considérable, en fonction de l'algorithme exact plus d'un facteur de 4, même en ce qui concerne le compilateur Intel. Cependant, comme vous l'avez mentionné que vous avez les inconvénients suivants:

  • La portabilité. Le code fonctionnera sur tous les CPU Intel comme, donc aussi AMD, mais pas sur d'autres processeurs. Ce n'est pas un problème pour nous parce que nous contrôlons le matériel cible. Mise compilateurs et même à un système d'exploitation 64 bits peut également être un problème.

  • Vous avez une courbe d'apprentissage abrupte, mais je trouve que après avoir saisir les principes d'écriture de nouveaux algorithmes est pas difficile.

  • maintenabilité. La plupart des C ou C ++ programmeurs ont aucune connaissance de réunion / SSE.

Mon conseil pour vous d'aller pour que si vous avez vraiment besoin de l'amélioration de la performance, et vous ne pouvez pas trouver une fonction de votre problème dans une bibliothèque comme le processeur Intel IPP, et si vous pouvez vivre avec les problèmes de portabilité .

Je peux dire de mon experince que SSE apporte un énorme (4x et plus) speedup sur une version simple c du code (pas asm en ligne, pas intrinsics utilisé) mais assembleur optimisé à la main peut battre l'assemblage généré par le compilateur si le compilateur ne peut pas comprendre ce que le programmeur prévu (croyez-moi, les compilateurs ne couvre pas toutes les combinaisons de code possibles et ils ne le sera jamais). Oh, et le compilateur ne peut pas la mise en page à chaque fois que les données qu'il fonctionne à la vitesse la plus rapide possible. Mais vous avez besoin pour beaucoup experince un sur un processeur Intel speedup compilateur (si possible).

instructions SSE étaient à l'origine uniquement sur des puces Intel, mais récemment (depuis Athlon?) AMD les prend en charge aussi bien, donc si vous faites le code contre le jeu d'instructions SSE, vous devriez être portable à la plupart des procs x86.

Cela étant dit, il ne peut pas être utile de votre temps pour apprendre le codage SSE à moins que vous êtes déjà familier avec l'assembleur sur x86 de - une option plus facile pourrait être de vérifier votre compilateur docs et voir s'il y a des options pour permettre au compilateur de autogenerate Code SSE pour vous. Certains compilateurs très bien vectorisation boucles de cette façon. (Vous n'êtes probablement pas surpris d'apprendre que les compilateurs Intel font un bon travail de ceci:)

écrire du code qui aide le compilateur à comprendre ce que vous faites. GCC comprendre et optimiser le code SSE comme celui-ci:

typedef union Vector4f
{
        // Easy constructor, defaulted to black/0 vector
    Vector4f(float a = 0, float b = 0, float c = 0, float d = 1.0f):
        X(a), Y(b), Z(c), W(d) { }

        // Cast operator, for []
    inline operator float* ()
    { 
        return (float*)this;
    }

        // Const ast operator, for const []
    inline operator const float* () const
    { 
        return (const float*)this;
    }

    // ---------------------------------------- //

    inline Vector4f operator += (const Vector4f &v)
    {
        for(int i=0; i<4; ++i)
            (*this)[i] += v[i];

        return *this;
    }

    inline Vector4f operator += (float t)
    {
        for(int i=0; i<4; ++i)
            (*this)[i] += t;

        return *this;
    }

        // Vertex / Vector 
        // Lower case xyzw components
    struct {
        float x, y, z;
        float w;
    };

        // Upper case XYZW components
    struct {
        float X, Y, Z;
        float W;
    };
};

Il suffit de ne pas oublier d'avoir -msse2 -msse sur vos paramètres de construction!

Bien qu'il soit vrai que l'ESS est spécifique à certains processeurs (SSE peut être relativement sûr, SSE2 beaucoup moins dans mon expérience), vous pouvez détecter la CPU lors de l'exécution, et charger le code dynamiquement en fonction de la CPU cible.

SIMD (tels que intrinsics SSE2) peuvent accélérer ce genre de chose, mais prendre en expertise pour utiliser correctement. Ils sont très sensibles à l'alignement et la latence du pipeline; une utilisation imprudente peut faire des performances encore pire que ce qu'elle aurait été sans eux. Vous aurez beaucoup plus facile et plus immédiat de speedup utilisant simplement le préchargement du cache pour vous assurer que tous vos ints sont en L1 dans le temps pour vous d'opérer sur eux.

À moins que votre fonction a besoin d'un débit de plus de 100.000.000 entiers par seconde, SIMD ne vaut probablement pas la peine pour vous.

Il suffit d'ajouter brièvement ce qui a été dit sur les différentes versions de l'ESS étant disponible sur différents processeurs: Cela peut être vérifié en regardant les indicateurs de fonctionnalité respectifs renvoyés par l'instruction CPUID (voir par exemple la documentation d'Intel pour plus de détails)

Jetez un oeil à assembleur en ligne pour C / C ++, voici un DDJ article . À moins que vous êtes 100% certain que votre programme sera exécuté sur une plate-forme compatible, vous devez suivre les recommandations beaucoup ont donné ici.

Je suis d'accord avec les commentaires précédents. Les avantages peuvent être assez grand, mais pour l'obtenir peut exiger beaucoup de travail. la documentation Intel sur ces instructions est plus pages de 4 Ko. Vous pouvez consulter EasySSE (c bibliothèque de wrappers ++ sur + intrinsics exemples) sans Ocali Inc.

Je suppose que mon appartenance à ce EasySSE est clair.

Je ne recommande pas de le faire vous-même à moins que vous ne maîtrisiez assez bien l'assemblage.L'utilisation de SSE nécessitera très probablement une réorganisation minutieuse de vos données, car Skiz souligne, et le bénéfice est souvent, au mieux, discutable.

Il serait probablement bien préférable pour vous d'écrire de très petites boucles et de garder vos données très étroitement organisées et de compter uniquement sur le compilateur pour le faire à votre place.Le compilateur Intel C et GCC (depuis 4.1) peuvent auto-vectoriser votre code et feront probablement un meilleur travail que vous.(Ajoutez simplement -ftree-vectorize à votre CXXFLAGS.)

Modifier:Une autre chose que je dois mentionner est que plusieurs compilateurs prennent en charge intrinsèques de l'assemblage, ce qui serait probablement, IMO, plus facile à utiliser que la syntaxe asm() ou __asm{}.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top