سؤال

أريد أن أتعلم المزيد حول استخدام SSE.

ما هي الطرق التي يجب تعلمها ، إلى جانب القراءة الواضحة أدلة مطور برامج Intel® 64 و IA-32?

أنا مهتم بشكل رئيسي بالعمل مع وظائف GCC X86 مدمجة.

هل كانت مفيدة؟

المحلول

أولاً ، لا أوصي باستخدام الوظائف المدمجة - فهي ليست محمولة (عبر المجمعين من نفس القوس).

يستخدم الجوهارات, ، مجلس التعاون الخليجي يقوم بعمل رائع تحسين الجوهارات SSE في رمز أكثر تحسينًا. يمكنك دائمًا الحصول على نظرة خاطفة على التجميع ومعرفة كيفية استخدام SSE إلى إمكاناتها الكاملة.

الجوهارات سهلة - تمامًا مثل مكالمات الوظائف العادية:

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

يستخدم _mm_load_ps أو _mm_loadu_ps لتحميل البيانات من المصفوفات.

بالطبع هناك المزيد من الخيارات ، SSE قوية حقًا وفي رأيي من السهل نسبيًا التعلم.

أنظر أيضا https://stackoverflow.com/tags/sse/info لبعض الروابط إلى الأدلة.

نصائح أخرى

منذ أن طلبت الموارد:

دليل عملي لاستخدام SSE مع C ++: نظرة عامة جيدة على كيفية استخدام SSE بفعالية ، مع أمثلة.

MSDN قائمة الجوهبات المترجمة: مرجع شامل لجميع احتياجاتك الجوهرية. إنها MSDN ، ولكن إلى حد كبير جميع الجوهارات المدرجة هنا مدعومة من قبل GCC و ICC أيضًا.

صفحة SSE كريستوفر رايت: مرجع سريع على معاني الرموز sse. أعتقد أن أدلة Intel يمكن أن تخدم نفس الوظيفة ، ولكن هذا أسرع.

من الأفضل أن تكتب معظم التعليمات البرمجية الخاصة بك في المواد الداخلية ، ولكن تحقق من OBJDump لإخراج المترجم الخاص بك للتأكد من أنه ينتج رمزًا فعالًا. لا يزال توليد رمز SIMD تقنية جديدة إلى حد ما ، ومن المحتمل جدًا أن يكون المترجم خاطئًا في بعض الحالات.

أجد أدلة الأبحاث والتحسين الدكتورة أجنر فوج ذات قيمة كبيرة! لديه أيضًا بعض المكتبات وأدوات الاختبار التي لم أجربها بعد.http://www.agner.org/optimize/

الخطوة 1: اكتب بعض التجميع يدويًا

أوصي بأن تحاول أولاً كتابة التجميع الخاص بك يدويًا لرؤية والتحكم في ما يحدث بالضبط عند البدء في التعلم.

ثم يصبح السؤال كيفية ملاحظة ما يحدث في البرنامج ، والإجابات هي:

  • GDB
  • استخدم المكتبة القياسية C إلى print و assert أشياء

يتطلب استخدام مكتبة C القياسية بنفسك القليل من العمل ، ولكن لا شيء كثيرًا. لقد قمت على سبيل المثال بهذا العمل بشكل جيد لك على Linux في الملفات التالية لإعداد الاختبار الخاص بي:

باستخدام هؤلاء المساعدين ، أبدأ بعد ذلك في اللعب مع الأساسيات ، مثل:

  • تحميل وتخزين البيانات إلى / من الذاكرة إلى سجلات SSE
  • أضف الأعداد الصحيحة وأرقام النقاط العائمة بأحجام مختلفة
  • تؤكد أن النتائج هي ما أتوقع

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.

الخطوة 2: اكتب بعض الجوهارات

ومع ذلك ، فإن رمز الإنتاج ، من المحتمل أن ترغب في استخدام الجوهارات الموجودة مسبقًا بدلاً من التجميع الخام كما هو مذكور في: https://stackoverflow.com/a/1390802/895245

حتى الآن أحاول تحويل الأمثلة السابقة إلى رمز C أكثر أو أقل مع الجوهارات.

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.

الخطوة 3: اذهب وتحسين بعض التعليمات البرمجية وقياسها

الخطوة النهائية ، والأهم والصعبة ، هي بالطبع استخدام الجوهارات في الواقع لجعل التعليمات البرمجية الخاصة بك بسرعة ، ثم لتقييم تحسنك.

القيام بذلك ، من المحتمل أن تتطلب منك أن تتعلم قليلاً عن الهندسة المعمارية الدقيقة X86 ، والتي لا أعرف نفسي. من المحتمل أن تكون وحدة المعالجة المركزية مقابل IO Bound واحدة من الأشياء التي تظهر: ماذا تعني مصطلحات "CPU ملزمة" و "I/O ملزمة"؟

كما ذكر في: https://stackoverflow.com/a/12172046/895245 سيتضمن هذا حتماً تقريبًا قراءة وثائق Agner Fog ، والتي يبدو أنها أفضل من أي شيء نشرته Intel نفسه.

ومع ذلك ، نأمل أن تكون الخطوتين 1 و 2 بمثابة أساس لتجربة جوانب وظيفية على الأقل وترى بسرعة الإرشادات التي تقوم بها.

TODO: إنتاج مثال مثير للاهتمام على هذا التحسين هنا.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top