Question

Je veux en savoir plus sur l'utilisation du SSE .

Quels sont les moyens sont là pour apprendre, en plus de l'évidence lire Intel® 64 et IA-32 Manuels d'architectures Software Developer ?

Je suis principalement intéressé à travailler avec le GCC X86 Fonctions intégrées .

Était-ce utile?

La solution

D'abord, je ne recommande pas d'utiliser les fonctions intégrées - ils ne sont pas portables (à travers les compilateurs du même arc).

Utilisez intrinsics, GCC fait un excellent travail optimisation intrinsics SSE dans le code encore plus optimisé. Vous pouvez toujours avoir un coup d'oeil à l'assemblée et voir comment utiliser SSE à son plein potentiel.

Intrinsics sont faciles - comme appelle la fonction normale:

#include <immintrin.h>  // portable to all x86 compilers

int main()
{
    __m128 vector1 = _mm_set_ps(4.0, 3.0, 2.0, 1.0); // high element first, opposite of C array order.  Use _mm_setr_ps if you want "little endian" element order in the source.
    __m128 vector2 = _mm_set_ps(7.0, 8.0, 9.0, 0.0);

    __m128 sum = _mm_add_ps(vector1, vector2); // result = vector1 + vector 2

    vector1 = _mm_shuffle_ps(vector1, vector1, _MM_SHUFFLE(0,1,2,3));
    // vector1 is now (1, 2, 3, 4) (above shuffle reversed it)
    return 0;
}

Utilisation _mm_load_ps ou _mm_loadu_ps pour charger des données à partir des tableaux.

Bien sûr qu'il ya beaucoup plus d'options, SSE est vraiment puissant et à mon avis, relativement facile à apprendre.

Voir aussi https://stackoverflow.com/tags/sse/info pour des liens vers des guides.

Autres conseils

Depuis que vous avez demandé des ressources:

Un guide pratique à l'utilisation de l'ESS avec C ++ : bonne vue d'ensemble conceptuel sur la façon d'utiliser efficacement SSE , avec des exemples.

MSDN Liste des compilateur Intrinsics : complète référence pour tous vos besoins intrinsèques. Il est MSDN, mais à peu près tous les intrinsics listés ici sont pris en charge par GCC et ICC ainsi.

SSE Christopher Wright : référence rapide sur les significations de l'ESS opcodes. Je suppose que les manuels Intel peut servir la même fonction, mais cela est plus rapide.

Il est probablement préférable d'écrire la plupart de votre code dans intrinsics, mais ne vérifiez la objdump de la sortie de votre compilateur pour vous assurer qu'il est la production du code efficace. la génération de code SIMD est encore une technologie relativement nouvelle et il est très possible que le compilateur peut se tromper dans certains cas.

Je trouve le Dr Agner guides de recherche et d'optimisation de brouillard très précieux! Il a aussi des bibliothèques et des outils de test que je ne l'ai pas encore essayé. http://www.agner.org/optimize/

Etape 1: écrire un certain assemblage manuellement

Je vous recommande d'essayer d'abord d'écrire votre propre assemblage manuellement pour voir et contrôler exactement ce qui se passe lorsque vous commencez à apprendre.

Alors la question est de savoir comment observer ce qui se passe dans le programme, et les réponses sont:

  • GDB
  • utilisez la bibliothèque standard C pour print et les choses assert

En utilisant la bibliothèque standard C vous demande un peu de travail, mais pas grand-chose. J'ai par exemple fait ce travail bien pour vous sur Linux dans les fichiers suivants de ma configuration de test:

En utilisant ces aides, je commence alors à jouer avec les bases, telles que:

  • charge et de stocker des données vers / depuis la mémoire dans les registres SSE
  • ajouter des entiers et des nombres à virgule flottante de différentes tailles
  • affirment que les résultats sont ce que j'attends

addpd.S

#include <lkmc.h>

LKMC_PROLOGUE
.data
    .align 16
    addps_input0: .float 1.5, 2.5,  3.5,  4.5
    addps_input1: .float 5.5, 6.5,  7.5,  8.5
    addps_expect: .float 7.0, 9.0, 11.0, 13.0
    addpd_input0: .double 1.5, 2.5
    addpd_input1: .double 5.5, 6.5
    addpd_expect: .double 7.0, 9.0
.bss
    .align 16
    output:       .skip 16
.text
    /* 4x 32-bit */
    movaps addps_input0, %xmm0
    movaps addps_input1, %xmm1
    addps %xmm1, %xmm0
    movaps %xmm0, output
    LKMC_ASSERT_MEMCMP(output, addps_expect, $0x10)

    /* 2x 64-bit */
    movaps addpd_input0, %xmm0
    movaps addpd_input1, %xmm1
    addpd %xmm1, %xmm0
    movaps %xmm0, output
    LKMC_ASSERT_MEMCMP(output, addpd_expect, $0x10)
LKMC_EPILOGUE

GitHub amont .

paddq.S

#include <lkmc.h>

LKMC_PROLOGUE
.data
    .align 16
    input0:       .long 0xF1F1F1F1, 0xF2F2F2F2, 0xF3F3F3F3, 0xF4F4F4F4
    input1:       .long 0x12121212, 0x13131313, 0x14141414, 0x15151515
    paddb_expect: .long 0x03030303, 0x05050505, 0x07070707, 0x09090909
    paddw_expect: .long 0x04030403, 0x06050605, 0x08070807, 0x0A090A09
    paddd_expect: .long 0x04040403, 0x06060605, 0x08080807, 0x0A0A0A09
    paddq_expect: .long 0x04040403, 0x06060606, 0x08080807, 0x0A0A0A0A
.bss
    .align 16
    output:       .skip 16
.text
    movaps input1, %xmm1

    /* 16x 8bit */
    movaps input0, %xmm0
    paddb %xmm1, %xmm0
    movaps %xmm0, output
    LKMC_ASSERT_MEMCMP(output, paddb_expect, $0x10)

    /* 8x 16-bit */
    movaps input0, %xmm0
    paddw %xmm1, %xmm0
    movaps %xmm0, output
    LKMC_ASSERT_MEMCMP(output, paddw_expect, $0x10)

    /* 4x 32-bit */
    movaps input0, %xmm0
    paddd %xmm1, %xmm0
    movaps %xmm0, output
    LKMC_ASSERT_MEMCMP(output, paddd_expect, $0x10)

    /* 2x 64-bit */
    movaps input0, %xmm0
    paddq %xmm1, %xmm0
    movaps %xmm0, output
    LKMC_ASSERT_MEMCMP(output, paddq_expect, $0x10)

LKMC_EPILOGUE

GitHub amont .

Étape 2: écrire quelques intrinsics

Pour le code de production cependant, vous voudrez probablement utiliser les intrinsics pré-existant au lieu de l'assemblage brut comme mentionné à: https: / /stackoverflow.com/a/1390802/895245

Alors maintenant, je tente de convertir les exemples précédents en plus ou moins équivalent code C avec intrinsics.

addpq.c

#include <assert.h>
#include <string.h>

#include <x86intrin.h>

float global_input0[] __attribute__((aligned(16))) = {1.5f, 2.5f, 3.5f, 4.5f};
float global_input1[] __attribute__((aligned(16))) = {5.5f, 6.5f, 7.5f, 8.5f};
float global_output[4] __attribute__((aligned(16)));
float global_expected[] __attribute__((aligned(16))) = {7.0f, 9.0f, 11.0f, 13.0f};

int main(void) {
    /* 32-bit add (addps). */
    {
        __m128 input0 = _mm_set_ps(1.5f, 2.5f, 3.5f, 4.5f);
        __m128 input1 = _mm_set_ps(5.5f, 6.5f, 7.5f, 8.5f);
        __m128 output = _mm_add_ps(input0, input1);
        /* _mm_extract_ps returns int instead of float:
        * * https://stackoverflow.com/questions/5526658/intel-sse-why-does-mm-extract-ps-return-int-instead-of-float
        * * https://stackoverflow.com/questions/3130169/how-to-convert-a-hex-float-to-a-float-in-c-c-using-mm-extract-ps-sse-gcc-inst
        * so we must use instead: _MM_EXTRACT_FLOAT
        */
        float f;
        _MM_EXTRACT_FLOAT(f, output, 3);
        assert(f == 7.0f);
        _MM_EXTRACT_FLOAT(f, output, 2);
        assert(f == 9.0f);
        _MM_EXTRACT_FLOAT(f, output, 1);
        assert(f == 11.0f);
        _MM_EXTRACT_FLOAT(f, output, 0);
        assert(f == 13.0f);

        /* And we also have _mm_cvtss_f32 + _mm_shuffle_ps, */
        assert(_mm_cvtss_f32(output) == 13.0f);
        assert(_mm_cvtss_f32(_mm_shuffle_ps(output, output, 1)) == 11.0f);
        assert(_mm_cvtss_f32(_mm_shuffle_ps(output, output, 2)) ==  9.0f);
        assert(_mm_cvtss_f32(_mm_shuffle_ps(output, output, 3)) ==  7.0f);
    }

    /* Now from memory. */
    {
        __m128 *input0 = (__m128 *)global_input0;
        __m128 *input1 = (__m128 *)global_input1;
        _mm_store_ps(global_output, _mm_add_ps(*input0, *input1));
        assert(!memcmp(global_output, global_expected, sizeof(global_output)));
    }

    /* 64-bit add (addpd). */
    {
        __m128d input0 = _mm_set_pd(1.5, 2.5);
        __m128d input1 = _mm_set_pd(5.5, 6.5);
        __m128d output = _mm_add_pd(input0, input1);
        /* OK, and this is how we get the doubles out:
        * with _mm_cvtsd_f64 + _mm_unpackhi_pd
        * https://stackoverflow.com/questions/19359372/mm-cvtsd-f64-analogon-for-higher-order-floating-point
        */
        assert(_mm_cvtsd_f64(output) == 9.0);
        assert(_mm_cvtsd_f64(_mm_unpackhi_pd(output, output)) == 7.0);
    }

    return 0;
}

GitHub en amont .

paddq.c

#include <assert.h>
#include <inttypes.h>
#include <string.h>

#include <x86intrin.h>

uint32_t global_input0[] __attribute__((aligned(16))) = {1, 2, 3, 4};
uint32_t global_input1[] __attribute__((aligned(16))) = {5, 6, 7, 8};
uint32_t global_output[4] __attribute__((aligned(16)));
uint32_t global_expected[] __attribute__((aligned(16))) = {6, 8, 10, 12};

int main(void) {

    /* 32-bit add hello world. */
    {
        __m128i input0 = _mm_set_epi32(1, 2, 3, 4);
        __m128i input1 = _mm_set_epi32(5, 6, 7, 8);
        __m128i output = _mm_add_epi32(input0, input1);
        /* _mm_extract_epi32 mentioned at:
        * https://stackoverflow.com/questions/12495467/how-to-store-the-contents-of-a-m128d-simd-vector-as-doubles-without-accessing/56404421#56404421 */
        assert(_mm_extract_epi32(output, 3) == 6);
        assert(_mm_extract_epi32(output, 2) == 8);
        assert(_mm_extract_epi32(output, 1) == 10);
        assert(_mm_extract_epi32(output, 0) == 12);
    }

    /* Now from memory. */
    {
        __m128i *input0 = (__m128i *)global_input0;
        __m128i *input1 = (__m128i *)global_input1;
        _mm_store_si128((__m128i *)global_output, _mm_add_epi32(*input0, *input1));
        assert(!memcmp(global_output, global_expected, sizeof(global_output)));
    }

    /* Now a bunch of other sizes. */
    {
        __m128i input0 = _mm_set_epi32(0xF1F1F1F1, 0xF2F2F2F2, 0xF3F3F3F3, 0xF4F4F4F4);
        __m128i input1 = _mm_set_epi32(0x12121212, 0x13131313, 0x14141414, 0x15151515);
        __m128i output;

        /* 8-bit integers (paddb) */
        output = _mm_add_epi8(input0, input1);
        assert(_mm_extract_epi32(output, 3) == 0x03030303);
        assert(_mm_extract_epi32(output, 2) == 0x05050505);
        assert(_mm_extract_epi32(output, 1) == 0x07070707);
        assert(_mm_extract_epi32(output, 0) == 0x09090909);

        /* 32-bit integers (paddw) */
        output = _mm_add_epi16(input0, input1);
        assert(_mm_extract_epi32(output, 3) == 0x04030403);
        assert(_mm_extract_epi32(output, 2) == 0x06050605);
        assert(_mm_extract_epi32(output, 1) == 0x08070807);
        assert(_mm_extract_epi32(output, 0) == 0x0A090A09);

        /* 32-bit integers (paddd) */
        output = _mm_add_epi32(input0, input1);
        assert(_mm_extract_epi32(output, 3) == 0x04040403);
        assert(_mm_extract_epi32(output, 2) == 0x06060605);
        assert(_mm_extract_epi32(output, 1) == 0x08080807);
        assert(_mm_extract_epi32(output, 0) == 0x0A0A0A09);

        /* 64-bit integers (paddq) */
        output = _mm_add_epi64(input0, input1);
        assert(_mm_extract_epi32(output, 3) == 0x04040404);
        assert(_mm_extract_epi32(output, 2) == 0x06060605);
        assert(_mm_extract_epi32(output, 1) == 0x08080808);
        assert(_mm_extract_epi32(output, 0) == 0x0A0A0A09);
    }

    return 0;

GitHub en amont .

Étape 3: allez et optimiser du code et le benchmark

La finale, et le plus important et difficile étape, est bien sûr d'utiliser réellement les valeurs intrinsèques pour rendre votre code rapide, puis de comparer votre amélioration.

Ce faisant, sera probablement vous avez besoin d'apprendre un peu plus sur le x86 microarchitecture, que je ne me connais pas. CPU vs IO lié sera probablement l'une des choses qui revient: Que signifient les termes "CPU lié" et "I / O lié" signifie?

Comme mentionné à: https://stackoverflow.com/a/12172046/895245 cela impliquera presque inévitablement la lecture la documentation Agner Fog, qui semblent être mieux que tout Intel lui-même a publié.

Il faut espérer cependant les étapes 1 et 2 serviront de base à expérimenter au moins des aspects fonctionnels non performants et de voir rapidement quelles instructions sont en train de faire.

TODO:. Produire un exemple minimal intéressant d'une telle optimisation ici

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