سؤال

أنا جديد جدًا على SIMD/SSE وأحاول القيام ببعض تصفية الصور البسيطة (غير واضحة). يقوم الرمز أدناه بتصفية كل بكسل من صورة نقطية رمادية 8 بت مع ترجيح بسيط [1 2 1] في الاتجاه الأفقي. أقوم بإنشاء مبالغ 16 بكسل في وقت واحد.

ما يبدو سيئًا للغاية بشأن هذا الرمز ، على الأقل بالنسبة لي ، هو أن هناك الكثير من الإدراج/الاستخراج ، وهو أمر غير أنيق للغاية وربما يبطئ كل شيء أيضًا. هل هناك طريقة أفضل لالتفاف البيانات من ريج إلى آخر عند التحول؟

BUF هي بيانات الصورة ، محاذاة 16 بايت. W/H هي العرض والارتفاع ، مضاعفات 16.

__m128i *p = (__m128i *) buf;
__m128i cur1, cur2, sum1, sum2, zeros, tmp1, tmp2, saved;
zeros = _mm_setzero_si128();
short shifted, last = 0, next;

// preload first row
cur1 = _mm_load_si128(p);
for (x = 1; x < (w * h) / 16; x++) {
    // unpack
    sum1 = sum2 = saved = cur1;
    sum1 = _mm_unpacklo_epi8(sum1, zeros);
    sum2 = _mm_unpackhi_epi8(sum2, zeros);
    cur1 = tmp1 = sum1;
    cur2 = tmp2 = sum2;
    // "middle" pixel
    sum1 = _mm_add_epi16(sum1, sum1);
    sum2 = _mm_add_epi16(sum2, sum2);
    // left pixel
    cur2 = _mm_slli_si128(cur2, 2);
    shifted = _mm_extract_epi16(cur1, 7);
    cur2 = _mm_insert_epi16(cur2, shifted, 0);
    cur1 = _mm_slli_si128(cur1, 2);
    cur1 = _mm_insert_epi16(cur1, last, 0);
    sum1 = _mm_add_epi16(sum1, cur1);
    sum2 = _mm_add_epi16(sum2, cur2);
    // right pixel
    tmp1 = _mm_srli_si128(tmp1, 2);
    shifted = _mm_extract_epi16(tmp2, 0);
    tmp1 = _mm_insert_epi16(tmp1, shifted, 7);
    tmp2 = _mm_srli_si128(tmp2, 2);
    // preload next row
    cur1 = _mm_load_si128(p + x);
    // we need the first pixel of the next row for the "right" pixel
    next = _mm_extract_epi16(cur1, 0) & 0xff;
    tmp2 = _mm_insert_epi16(tmp2, next, 7);
    // and the last pixel of last row for the next "left" pixel
    last = ((uint16_t) _mm_extract_epi16(saved, 7)) >> 8;
    sum1 = _mm_add_epi16(sum1, tmp1);
    sum2 = _mm_add_epi16(sum2, tmp2);
    // divide
    sum1 = _mm_srli_epi16(sum1, 2);
    sum2 = _mm_srli_epi16(sum2, 2);
    sum1 = _mm_packus_epi16(sum1, sum2);
    mm_store_si128(p + x - 1, sum1);
}
هل كانت مفيدة؟

المحلول

أقترح الحفاظ على البيكسلات المجاورة في سجل SSE. وهذا يعني ، حافظ على نتيجة _mm_slli_si128 / _mm_srli_si128 في متغير SSE ، وقم بإزالة كل الإدراج والاستخراج. تفكيري هو أنه في وحدات المعالجة المركزية الأقدم ، تتطلب تعليمات الإدراج/الاستخراج التواصل بين وحدات SSE والوحدات للأغراض العامة ، والتي تكون أبطأ بكثير من الحفاظ على الحساب داخل SSE ، حتى لو تمسكها إلى ذاكرة التخزين المؤقت L1.

عندما يتم ذلك ، يجب أن يكون هناك أربعة نوبات 16 بت فقط (_mm_slli_si128 ، _mm_srli_si128 ، لا تحسب نوبة الانقسام ). اقتراحي هو القيام بوضعية مع الكود الخاص بك ، لأنه بحلول ذلك الوقت قد يكون الرمز الخاص بك قد وصل بالفعل إلى حد عرض النطاق الترددي للذاكرة .. مما يعني أنه لا يمكنك التحسين بعد الآن.

إذا كانت الصورة كبيرة (أكبر من حجم L2) ولن تتم قراءة الإخراج قريبًا ، فحاول استخدام MovNTDQ (_mm_stream_si128) للكتابة مرة أخرى. وفقًا للعديد من مواقع الويب ، فهو في SSE2 ، على الرغم من أنك قد ترغب في التحقق المزدوج.

البرنامج التعليمي SIMD:

بعض مواقع SIMD Guru:

نصائح أخرى

كان هذا النوع من عمليات الحي دائمًا مؤلمًا مع SSE ، حتى جاء SSE3.5 (ويعرف أيضًا باسم SSSE3) ، وتم تقديم Palignr (_mm_alignr_epi8).

إذا كنت بحاجة إلى توافق متخلف مع SSE2/SSE3 ، فيمكنك كتابة ماكرو مكافئ أو دالة مضمنة يحاكي _mm_alignr_epi8 لـ SSE2/SSE3 والتي تنخفض إلى _mm_alignr_epi8 عند استهداف SSE3.5/SSE4.

هناك طريقة أخرى تتمثل في استخدام الأحمال غير المحسّنة للحصول على البيانات التي تم تحويلها - وهذا مكلف نسبيًا على وحدات المعالجة المركزية القديمة (تقريبًا ضعف الكمون ونصف إنتاجية الأحمال المحاذاة) ولكن قد يكون هذا مقبولًا اعتمادًا على الكثير من الحساب الذي تقوم به لكل حمولة. كما أنه يتمتع بمزايا أن أحمال Intel CPU (Core i7) الحالية ليس لها عقوبة مقارنة بالأحمال المحاذاة ، لذلك سيكون رمزك فعالًا تمامًا في Core i7 وآخرون.

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