Question

J'ai cette fonction qui utilise SSE2 pour ajouter quelques valeurs ensemble, il est censé ajouter LHS rhs ensemble et stocker le dos de résultat dans LHS:

template<typename T>
void simdAdd(T *lhs,T *rhs)
{
    asm volatile("movups %0,%%xmm0"::"m"(lhs));
    asm volatile("movups %0,%%xmm1"::"m"(rhs));

    switch(sizeof(T))
    {
        case sizeof(uint8_t):
        asm volatile("paddb %%xmm0,%%xmm1":);
        break;

        case sizeof(uint16_t):
        asm volatile("paddw %%xmm0,%%xmm1":);
        break;

        case sizeof(float):
        asm volatile("addps %%xmm0,%%xmm1":);
        break;

        case sizeof(double):
        asm volatile("addpd %%xmm0,%%xmm1":);
        break;

        default:
        std::cout<<"error"<<std::endl;
        break;
    }

    asm volatile("movups %%xmm0,%0":"=m"(lhs));
}

et mon code utilise la fonction comme ceci:

float *values=new float[4];
float *values2=new float[4];

values[0]=1.0f;
values[1]=2.0f;
values[2]=3.0f;
values[3]=4.0f;

values2[0]=1.0f;
values2[1]=2.0f;
values2[2]=3.0f;
values2[3]=4.0f;

simdAdd(values,values2);
for(uint32_t count=0;count<4;count++) std::cout<<values[count]<<std::endl;

Cependant, cela ne fonctionne pas parce que lorsque le code exécute sorties 1,2,3,4 au lieu de 2,4,6,8

Était-ce utile?

La solution

J'ai trouvé que le support de montage en ligne n'est pas fiable dans la plupart des compilateurs modernes (comme dans les implémentations sont juste poussette simple). Vous êtes généralement mieux d'utiliser compilateur intrinsics qui sont des déclarations que ressembler à des fonctions C, mais en fait la compilation à un opcode spécifique.

Intrinsics vous permet de spécifier une séquence exacte de opcodes, mais laisser le registre coloration au compilateur. Il est beaucoup plus fiable que d'essayer de transférer des données entre les variables C et registres asm, qui est où assembleurs inline ont toujours tombé pour moi. Il permet également le calendrier du compilateur vos instructions, ce qui peut offrir de meilleures performances si cela fonctionne autour . -À-dire, dans ce cas, vous pouvez faire

void simdAdd(float *lhs,float *rhs)
{
   _mm_storeu_ps( lhs, _mm_add_ps(_mm_loadu_ps( lhs ), _mm_loadu_ps( rhs )) );
}

Dans votre cas, de toute façon, vous avez deux problèmes:

  1. La terrible syntaxe assembleur en ligne de GCC qui fait une grande confusion de la différence entre les pointeurs et les valeurs. Utilisez *lhs et *rhs au lieu de seulement LHS et RHS; apparemment les « = m » moyens de syntaxe « utilisent implicitement un pointeur vers cette chose que je vous passe au lieu de la chose elle-même. »
  2. GCC a une source, la syntaxe de destination -. Les addps magasins de son résultat dans le second paramètre, vous devez vous xmm1 de sortie, pas xmm0

J'ai mis un exemple fixé sur CodePad (pour éviter d'encombrer cette réponse, et de démontrer que travaux).

Autres conseils

choses dont je vois mal ici. Tout d'abord, vos déclarations charger les registres XMM et stocker des valeurs de retour à votre variable sont mal.

asm volatile("movups %0,%%xmm0"::"m"(lhs));
asm volatile("movups %0,%%xmm1"::"m"(rhs));
...
asm volatile("movups %%xmm0,%0":"=m"(lhs));

Une lecture obligatoire

asm volatile("movups %0,%%xmm0"::"m"(*lhs));
asm volatile("movups %0,%%xmm1"::"m"(*rhs));
...
asm volatile("movups %%xmm0,%0":"=m"(*lhs));

Notez les * 's. Vous étiez en chargeant et en ajoutant les valeurs de pointeur, puis de les stocker de nouveau dans un temporaire qui a été utilisé pour transmettre l'argument pointeur (qui est par conséquent oublié sans écrire à la mémoire lors du retour d'appel de fonction).

Même avec ces correctifs, en général, ce n'est pas une bonne technique. Je l'avais écrit mon propre exemple avec des déclarations asm, mais il était viciée parce que j'oublié de compte de la nature non alignée des paramètres étant transmis. Il devient très lourd à faire des déclarations asm et beaucoup plus facile et plus lisible en utilisant des fonctions intrinsèques. Il suffit de faire preuve de prudence pour utiliser les types de données correctes:

template<typename T>
void simdAdd(T *lhs,T *rhs)
{
    switch(sizeof(T))
    {
        case sizeof(uint8_t):
        {
          __m128i lh128;
          lh128 = _mm_add_epi8( _mm_loadu_si128( (__m128i *)lhs ),
                                _mm_loadu_si128( (__m128i *)rhs ) );
          _mm_storeu_si128( (__m128i *)lhs, lh128 );
        }
        break;

        case sizeof(uint16_t):
        {
          __m128i lh128;
          lh128 = _mm_add_epi16( _mm_loadu_si128( (__m128i *)lhs ),
                                 _mm_loadu_si128( (__m128i *)rhs ) );
          _mm_storeu_si128( (__m128i *)lhs, lh128 );
        }
        break;

        case sizeof(float):
        {
          __m128 lh128;
          lh128 = _mm_add_ps( _mm_loadu_ps( (float *)lhs ),
                              _mm_loadu_ps( (float *)rhs ) );
          _mm_storeu_ps( (float *)lhs, lh128 );
        }
        break;

        case sizeof(double):
        {
          __m128d lh128;
          lh128 = _mm_add_pd( _mm_loadu_pd( (double *)lhs ),
                              _mm_loadu_pd( (double *)rhs ) );
          _mm_storeu_pd( (double *)lhs, lh128 );
        }
        break;

        default:
        std::cout<<"error"<<std::endl;
        break;
    }
}

Quelque chose à prendre en compte est la taille de vos types de données ne suffit pas de savoir quel type vous passé des données. Tout simplement parce que un type de modèle partage la même taille que les types de base que vous archivez, ne signifie pas qu'il est du même type. Alors je force le casting pour couvrir ce cas dans mon exemple. Cela peut généralement être une pratique dangereuse, sauf si vous êtes certain que cette fonction ne jamais être utilisé avec les types que vous avez spécifiés. Par exemple, en utilisant un nombre entier de taille flottante se traduira par une réponse inattendue mauvaise, et le compilateur ne sera pas en mesure de vous mettre en garde à ce sujet.

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