Frage

Ich möchte mehr über die Verwendung des SSE .

lernen

Welche Möglichkeiten gibt es, zu lernen, neben den offensichtlichen Lesen der Intel® 64 und IA-32 Architectures Software Developer Manuals ?

Vor allem mit der GCC X86 Integrierte Funktionen .

War es hilfreich?

Lösung

Zuerst Ich empfehle auf nicht mit dem integrierten Funktionen - sie nicht tragbar sind (über Compiler aus dem gleichen Bogen).

Verwenden Sie intrinsics , GCC hat eine wunderbare Arbeit intrinsics in noch mehr optimierten Code SSE zu optimieren. Sie können immer einen Blick auf die Montage haben und sehen, wie SSE volle Potenzial der es zu benutzen.

Intrinsics sind einfach - wie normale Funktionsaufrufe:

#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;
}

Mit _mm_load_ps oder _mm_loadu_ps zum Laden von Daten aus Arrays.

Natürlich gibt es viel mehr Möglichkeiten gibt, SSE sind wirklich mächtig und meiner Meinung nach relativ leicht zu erlernen.

Siehe auch https://stackoverflow.com/tags/sse/info für einige Links zu Führungen.

Andere Tipps

Da Sie für Ressourcen gefragt:

Eine praktische Anleitung zur Verwendung von SSE mit C ++ : Good konzeptionellen Überblick darüber, wie SSE effektiv zu nutzen mit Beispielen.

MSDN Auflistung der Compiler Intrinsics : Comprehensive Referenz für alle Ihre inneren Bedürfnisse. Es ist MSDN, aber so ziemlich alle Spezifika hier aufgeführt sind, von GCC und ICC ebenfalls unterstützt.

Christopher Wright SSE Seite : Kurzübersicht über die Bedeutung des SSE Opcodes. Ich denke, die Intel-Handbücher können die gleiche Funktion erfüllen, aber das ist schneller.

Es ist wahrscheinlich am besten, die meisten Ihren Code in intrinsics zu schreiben, aber die objdump Ihres Compilers Ausgabe prüft, um sicherzustellen, dass es effizienten Code ist produziert. SIMD-Code-Generierung ist immer noch eine relativ neue Technologie, und es ist sehr gut möglich, dass die Compiler es falsch in einigen Fällen bekommen könnten.

Ich finde Dr. Agner Fog Forschung und Optimierung Führungen sehr wertvoll! Er hat auch einige Bibliotheken und Test-Tools, die ich noch nicht ausprobiert haben. http://www.agner.org/optimize/

Schritt 1: Schreiben Sie eine Montage von Hand

Ich empfehle, dass Sie zunächst versuchen, Ihre eigenen zu schreiben Montage von Hand zu sehen und Steuerung genau das, was passiert, wenn Sie anfangen zu lernen.

Dann wird die Frage, wie zu beobachten, was im Programm geschieht, und die Antworten sind:

  • GDB
  • verwenden, um die C-Standardbibliothek zu print und assert Dingen

Mit der C-Standardbibliothek selbst erfordert ein wenig Arbeit, aber nicht viel. Ich habe zum Beispiel dieser Arbeit gut für Sie unter Linux in den folgenden Dateien meines Testaufbaus getan:

jene Helfer verwenden, beginne ich dann mit den Grundlagen herum spielen, wie zum Beispiel:

  • Laden und Speichern von Daten in / aus dem Speicher in SSE-Register
  • in ganzen Zahlen und Gleitkommazahlen in verschiedenen Größen
  • assert, dass die Ergebnisse sind das, was ich erwarte, dass

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 Upstream .

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 Upstream .

Schritt 2: schreiben Sie einige Spezifika

Für die Produktion Code jedoch Sie wollen wahrscheinlich die bereits bestehenden intrinsics statt roh Montage verwenden, wie bei erwähnt: https: / /stackoverflow.com/a/1390802/895245

So, jetzt versuche ich die vorherigen Beispiele in mehr oder weniger gleichwertigen C-Code mit intrinsics zu konvertieren.

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 Upstream .

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 Upstream .

Schritt 3: Gehen Sie und einige Codes und Benchmark optimiert

Der letzte und wichtigste und harter Schritt ist natürlich, um tatsächlich die Spezifika zu verwenden, um Ihren Code schnell zu machen, und dann vergleichen Sie Ihre Verbesserung.

Dabei benötigen Sie wahrscheinlich ein wenig über die x86-Mikroarchitektur zu lernen, die ich selbst nicht kennen. CPU vs IO gebunden wahrscheinlich eines der Dinge sein, die aufkommt: Was die Bedingungen tun "CPU gebunden" und "I / O" mean gebunden?

erwähnt Stand: https://stackoverflow.com/a/12172046/895245 dies fast zwangsläufig Lesen beinhaltet Agner Fog in der Dokumentation, die besser zu sein scheinen als alles, was Intel selbst veröffentlicht hat.

aber hoffentlich die Schritte 1 und 2 wird als Grundlage mindestens Experiment mit funktionellen Nicht-Leistungsaspekten dienen und schnell sehen, welche Anweisungen tun.

ERLEDIGEN:. Erzeugen einen minimalen interessantes Beispiel für eine solche Optimierung hier

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top