سؤال

أنا معيار بعض سند الرمز (مضاعفة 4 يطفو من 4 أومان) ضد رمز C التقليدي الذي يفعل نفس الشيء. أعتقد أنه يجب أن يكون كود القياسي الخاص بي غير صحيح بطريقة ما لأنه يقول إنه يقول إن رمز غير SSE هو أسرع من SSE بعامل 2-3.

هل يمكن لأي شخص أن يقول لي ما هو الخطأ في رمز القياس أدناه؟ وربما اقترح نهج آخر يظهر بدقة السرعات لكل من رمز SSE وغير SSE.

#include <time.h>
#include <string.h>
#include <stdio.h>

#define ITERATIONS 100000

#define MULT_FLOAT4(X, Y) ({ \
asm volatile ( \
    "movaps (%0), %%xmm0\n\t" \
    "mulps (%1), %%xmm0\n\t" \
    "movaps %%xmm0, (%1)" \
    :: "r" (X), "r" (Y)); })

int main(void)
{
    int i, j;
    float a[4] __attribute__((aligned(16))) = { 10, 20, 30, 40 };
    time_t timer, sse_time, std_time;

    timer = time(NULL);
    for(j = 0; j < 5000; ++j)
        for(i = 0; i < ITERATIONS; ++i) {
            float b[4] __attribute__((aligned(16))) = { 0.1, 0.1, 0.1, 0.1 };

            MULT_FLOAT4(a, b);

        }
    sse_time = time(NULL) - timer;

    timer = time(NULL);
    for(j = 0; j < 5000; ++j)
        for(i = 0; i < ITERATIONS; ++i) {
            float b[4] __attribute__((aligned(16))) = { 0.1, 0.1, 0.1, 0.1 };

            b[0] *= a[0];
            b[1] *= a[1];
            b[2] *= a[2];
            b[3] *= a[3];

    }
    std_time = time(NULL) - timer;

    printf("sse_time %d\nstd_time %d\n", sse_time, std_time);

    return 0;
}
هل كانت مفيدة؟

المحلول

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

رمز غير SSE من جسم حلقة الأعمق:

movl    $0x3dcccccd, %eax
movl    %eax, -80(%rbp)
movl    $0x3dcccccd, %eax
movl    %eax, -76(%rbp)
movl    $0x3dcccccd, %eax
movl    %eax, -72(%rbp)
movl    $0x3dcccccd, %eax
movl    %eax, -68(%rbp)
movss   -80(%rbp), %xmm1
movss   -48(%rbp), %xmm0
mulss   %xmm1, %xmm0
movss   %xmm0, -80(%rbp)
movss   -76(%rbp), %xmm1
movss   -44(%rbp), %xmm0
mulss   %xmm1, %xmm0
movss   %xmm0, -76(%rbp)
movss   -72(%rbp), %xmm1
movss   -40(%rbp), %xmm0
mulss   %xmm1, %xmm0
movss   %xmm0, -72(%rbp)
movss   -68(%rbp), %xmm1
movss   -36(%rbp), %xmm0
mulss   %xmm1, %xmm0
movss   %xmm0, -68(%rbp)

رمز SSE من جسم حلقة الأعمق:

movl    $0x3dcccccd, %eax
movl    %eax, -64(%rbp)
movl    $0x3dcccccd, %eax
movl    %eax, -60(%rbp)
movl    $0x3dcccccd, %eax
movl    %eax, -56(%rbp)
movl    $0x3dcccccd, %eax
movl    %eax, -52(%rbp)
leaq    -48(%rbp), %rax
leaq    -64(%rbp), %rdx
movaps (%rax), %xmm0
mulps (%rdx), %xmm0
movaps %xmm0, (%rdx)

لست متأكدا من هذا، ولكن هنا تخميني:

كما ترى المترجم يخزن فقط القيم العائمة 4 بواسطة محلات 32 بت. ثم يتم قراءة ذلك مرة أخرى بواسطة تحميل 16 بايت. هذا يسبب كشك إعادة توجيه المتجر وهو مكلف عند حدوثه. يمكنك البحث عن هذا في كتيبات إنتل. لا يحدث في إصدار العددية وهذا يجعل فرق الأداء.

لجعلها أسرع تحتاج إلى التأكد من أن هذا المماطلة لا يحدث. إذا كنت تستخدم مجموعة مستمرة من 4 طوائف، اجعلها CONTER وتخزين النتائج في صفيف محاذي آخر. وبهذه الطريقة، نأمل التحويل البرمجي، نأمل أن يجعل تلك التحركات التي لا لزوم لها 4 البايت قبل الحمل. أو، إذا كنت بحاجة إلى ملء الصفيف الناتج، فقم بذلك مع أمر مخزن 16 بايت. إذا لم تتمكن من تجنب هذه التحركات الأربعة البايتة، فأنت بحاجة إلى القيام بشيء آخر بعد المتجر ولكن قبل الحمل (على سبيل المثال حساب شيء آخر).

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