سؤال

أقوم بإجراء قراءة متناثرة من بيانات 8 بت من ملف (إزالة ملف موجة 64 قناة). ثم أجمع بينهم ليكونوا مجرى بايت واحد. المشكلة التي أواجهها مع إعادة بناء البيانات الخاصة بي للكتابة.

في الأساس أنا أقرأ في 16 بايت ثم بناءها في متغير __m128i واحد ثم استخدام _mm_stream_ps لكتابة القيمة مرة أخرى إلى الذاكرة. ومع ذلك، لدي بعض نتائج الأداء الفردي.

في مخططي الأول، أستخدم جوائز _mm_set_epi8 لتعيين __m128i كما يلي:

    const __m128i packedSamples = _mm_set_epi8( sample15,   sample14,   sample13,   sample12,   sample11,   sample10,   sample9,    sample8,
                                                sample7,    sample6,    sample5,    sample4,    sample3,    sample2,    sample1,    sample0 );

في الأساس، أترك الأمر كله حتى التحويل البرمجي لتحديد كيفية تحسينه لإعطاء أفضل أداء. هذا يعطي أسوأ أداء. اختباري يعمل في ~ 0.195 ثانية.

ثانيا حاولت الاندماج باستخدام تعليمات 4 _mm_set_epi32 ثم تعبئتها

    const __m128i samples0      = _mm_set_epi32( sample3, sample2, sample1, sample0 );
    const __m128i samples1      = _mm_set_epi32( sample7, sample6, sample5, sample4 );
    const __m128i samples2      = _mm_set_epi32( sample11, sample10, sample9, sample8 );
    const __m128i samples3      = _mm_set_epi32( sample15, sample14, sample13, sample12 );

    const __m128i packedSamples0    = _mm_packs_epi32( samples0, samples1 );
    const __m128i packedSamples1    = _mm_packs_epi32( samples2, samples3 );
    const __m128i packedSamples     = _mm_packus_epi16( packedSamples0, packedSamples1 );

هذا يحسن الأداء إلى حد ما. يعمل الاختبار الخاص بي الآن في ~ 0.15 ثانية. يبدو مضادا بديهيا أن الأداء سيحسن من خلال القيام بذلك كما أفترض أن هذا هو بالضبط ما يفعله _mm_set_epi8 على أي حال ...

كانت محاولتي النهائية هي استخدام القليل من التعليمات البرمجية لدي من صنع أربع CCS طريقة الطراز القديم (مع التحولات أو ORS) ثم وضعها في __m128i باستخدام واحد _mm_set_epi32.

    const GCui32 samples0       = MakeFourCC( sample0, sample1, sample2, sample3 );
    const GCui32 samples1       = MakeFourCC( sample4, sample5, sample6, sample7 );
    const GCui32 samples2       = MakeFourCC( sample8, sample9, sample10, sample11 );
    const GCui32 samples3       = MakeFourCC( sample12, sample13, sample14, sample15 );
    const __m128i packedSamples = _mm_set_epi32( samples3, samples2, samples1, samples0 );

هذا يعطي أداء أفضل. أخذ ~ 0.135 ثانية لتشغيل الاختبار الخاص بي. أنا حقا بدأت في الخلط.

لذلك حاولت نظام بايت كتابة بايت بايت بايت، وهذا هو أسرع قليلا بشكل طفيف من الطريقة الأخيرة.

ماذا يحدث؟ هذا كل شيء يبدو مضادا بديهية لي.

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

أي شخص لديه أي أفكار سوف يحدث موضع تقدير كبير! :د

تحرير: أبعد من التعليق أدناه توقفت عن تحميل البايتات على أنها ثوابت وتتغير إلى هذا:

    const __m128i samples0      = _mm_set_epi32( *(pSamples + channelStep3), *(pSamples + channelStep2), *(pSamples + channelStep1), *(pSamples + channelStep0) );
    pSamples    += channelStep4;
    const __m128i samples1      = _mm_set_epi32( *(pSamples + channelStep3), *(pSamples + channelStep2), *(pSamples + channelStep1), *(pSamples + channelStep0) );
    pSamples    += channelStep4;
    const __m128i samples2      = _mm_set_epi32( *(pSamples + channelStep3), *(pSamples + channelStep2), *(pSamples + channelStep1), *(pSamples + channelStep0) );
    pSamples    += channelStep4;
    const __m128i samples3      = _mm_set_epi32( *(pSamples + channelStep3), *(pSamples + channelStep2), *(pSamples + channelStep1), *(pSamples + channelStep0) );
    pSamples    += channelStep4;

    const __m128i packedSamples0    = _mm_packs_epi32( samples0, samples1 );
    const __m128i packedSamples1    = _mm_packs_epi32( samples2, samples3 );
    const __m128i packedSamples     = _mm_packus_epi16( packedSamples0, packedSamples1 );

وهذا الأداء المحسن إلى ~ 0.143 ثانية. Sitll ليس جيدا مثل التنفيذ C مستقيم ...

تحرير مرة أخرى: أفضل أداء أحصل عليه حتى الآن

    // Load the samples.
    const GCui8 sample0     = *(pSamples + channelStep0);
    const GCui8 sample1     = *(pSamples + channelStep1);
    const GCui8 sample2     = *(pSamples + channelStep2);
    const GCui8 sample3     = *(pSamples + channelStep3);

    const GCui32 samples0   = Build32( sample0, sample1, sample2, sample3 );
    pSamples += channelStep4;

    const GCui8 sample4     = *(pSamples + channelStep0);
    const GCui8 sample5     = *(pSamples + channelStep1);
    const GCui8 sample6     = *(pSamples + channelStep2);
    const GCui8 sample7     = *(pSamples + channelStep3);

    const GCui32 samples1   = Build32( sample4, sample5, sample6, sample7 );
    pSamples += channelStep4;

    // Load the samples.
    const GCui8 sample8     = *(pSamples + channelStep0);
    const GCui8 sample9     = *(pSamples + channelStep1);
    const GCui8 sample10    = *(pSamples + channelStep2);
    const GCui8 sample11    = *(pSamples + channelStep3);

    const GCui32 samples2       = Build32( sample8, sample9, sample10, sample11 );
    pSamples += channelStep4;

    const GCui8 sample12    = *(pSamples + channelStep0);
    const GCui8 sample13    = *(pSamples + channelStep1);
    const GCui8 sample14    = *(pSamples + channelStep2);
    const GCui8 sample15    = *(pSamples + channelStep3);

    const GCui32 samples3   = Build32( sample12, sample13, sample14, sample15 );
    pSamples += channelStep4;

    const __m128i packedSamples = _mm_set_epi32( samples3, samples2, samples1, samples0 );

    _mm_stream_ps( pWrite + 0,  *(__m128*)&packedSamples ); 

هذا يعطيني المعالجة في ~ 0.095 ثانية أفضل بكثير. لا يبدو أنني قادر على الاقتران مع SSE على الرغم من ... ما زلت في حيرة من ذلك ولكن .. HO همهمة.

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

المحلول

ربما يحاول المترجم وضع جميع الحجج إلى جوهره في السجلات في وقت واحد. لا تريد الوصول إلى العديد من المتغيرات في وقت واحد دون تنظيمها.

بدلا من إعلان معرف منفصل لكل عينة، حاول وضعها في char[16]. وبعد سيقوم المحول البرمجي بتعزيز القيم ال 16 للتسجيل حيث يرى مناسبا، طالما أنك لا تأخذ عنوان أي شيء داخل الصفيف. يمكنك إضافة __aligned__ العلامة (أو أيا كان استخدام VC ++) وربما تجنب جوهرها تماما. خلاف ذلك، استدعاء جوهري ( sample[15], sample[14], sample[13] … sample[0] ) يجب أن تجعل وظيفة التحويل البرمجي أسهل أو لا تقل ضرر.


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

أساسا، تقوم بتدفق البيانات في ملف التسجيل ثم بثته مرة أخرى. أنت لا تريد التحميل الزائد قبل حان الوقت لتدفئة البيانات.

نصائح أخرى

vs سيئة سيئة السمعة في تحسين جوهري. تتحرك بشكل خاص البيانات من وإلى سجلات SSE. يتم استخدام الجوهر نفسها بشكل جيد ولكن ....

ما تراه هو أنه يحاول ملء سجل SSE مع هذا الوحش:

00AA100C  movzx       ecx,byte ptr [esp+0Fh]  
00AA1011  movzx       edx,byte ptr [esp+0Fh]  
00AA1016  movzx       eax,byte ptr [esp+0Fh]  
00AA101B  movd        xmm0,eax  
00AA101F  movzx       eax,byte ptr [esp+0Fh]  
00AA1024  movd        xmm2,edx  
00AA1028  movzx       edx,byte ptr [esp+0Fh]  
00AA102D  movd        xmm1,ecx  
00AA1031  movzx       ecx,byte ptr [esp+0Fh]  
00AA1036  movd        xmm4,ecx  
00AA103A  movzx       ecx,byte ptr [esp+0Fh]  
00AA103F  movd        xmm5,edx  
00AA1043  movzx       edx,byte ptr [esp+0Fh]  
00AA1048  movd        xmm3,eax  
00AA104C  movzx       eax,byte ptr [esp+0Fh]  
00AA1051  movdqa      xmmword ptr [esp+60h],xmm0  
00AA1057  movd        xmm0,edx  
00AA105B  movzx       edx,byte ptr [esp+0Fh]  
00AA1060  movd        xmm6,eax  
00AA1064  movzx       eax,byte ptr [esp+0Fh]  
00AA1069  movd        xmm7,ecx  
00AA106D  movzx       ecx,byte ptr [esp+0Fh]  
00AA1072  movdqa      xmmword ptr [esp+20h],xmm4  
00AA1078  movdqa      xmmword ptr [esp+80h],xmm0  
00AA1081  movd        xmm4,ecx  
00AA1085  movzx       ecx,byte ptr [esp+0Fh]  
00AA108A  movdqa      xmmword ptr [esp+70h],xmm2  
00AA1090  movd        xmm0,eax  
00AA1094  movzx       eax,byte ptr [esp+0Fh]  
00AA1099  movdqa      xmmword ptr [esp+10h],xmm4  
00AA109F  movdqa      xmmword ptr [esp+50h],xmm6  
00AA10A5  movd        xmm2,edx  
00AA10A9  movzx       edx,byte ptr [esp+0Fh]  
00AA10AE  movd        xmm4,eax  
00AA10B2  movzx       eax,byte ptr [esp+0Fh]  
00AA10B7  movd        xmm6,edx  
00AA10BB  punpcklbw   xmm0,xmm1  
00AA10BF  punpcklbw   xmm2,xmm3  
00AA10C3  movdqa      xmm3,xmmword ptr [esp+80h]  
00AA10CC  movdqa      xmmword ptr [esp+40h],xmm4  
00AA10D2  movd        xmm4,ecx  
00AA10D6  movdqa      xmmword ptr [esp+30h],xmm6  
00AA10DC  movdqa      xmm1,xmmword ptr [esp+30h]  
00AA10E2  movd        xmm6,eax  
00AA10E6  punpcklbw   xmm4,xmm5  
00AA10EA  punpcklbw   xmm4,xmm0  
00AA10EE  movdqa      xmm0,xmmword ptr [esp+50h]  
00AA10F4  punpcklbw   xmm1,xmm0  
00AA10F8  movdqa      xmm0,xmmword ptr [esp+70h]  
00AA10FE  punpcklbw   xmm6,xmm7  
00AA1102  punpcklbw   xmm6,xmm2  
00AA1106  movdqa      xmm2,xmmword ptr [esp+10h]  
00AA110C  punpcklbw   xmm2,xmm0  
00AA1110  movdqa      xmm0,xmmword ptr [esp+20h]  
00AA1116  punpcklbw   xmm1,xmm2  
00AA111A  movdqa      xmm2,xmmword ptr [esp+40h]  
00AA1120  punpcklbw   xmm2,xmm0  
00AA1124  movdqa      xmm0,xmmword ptr [esp+60h]  
00AA112A  punpcklbw   xmm3,xmm0  
00AA112E  punpcklbw   xmm2,xmm3  
00AA1132  punpcklbw   xmm6,xmm4  
00AA1136  punpcklbw   xmm1,xmm2  
00AA113A  punpcklbw   xmm6,xmm1  

هذا يعمل بشكل أفضل و (يجب) أن تكون أسرع بشكل أسرع

__declspec(align(16)) BYTE arr[16] = { sample15, sample14, sample13, sample12, sample11, sample10, sample9, sample8, sample7, sample6, sample5, sample4, sample3, sample2, sample1, sample0 };

__m128i packedSamples = _mm_load_si128( (__m128i*)arr );

بناء سرير الاختبار الخاص بي:

void    f()
{
    const int steps = 1000000;
    BYTE* pDest = new BYTE[steps*16+16];
    pDest += 16 - ((ULONG_PTR)pDest % 16);
    BYTE* pSrc = new BYTE[steps*16*16];

    const int channelStep0 = 0;
    const int channelStep1 = 1;
    const int channelStep2 = 2;
    const int channelStep3 = 3;
    const int channelStep4 = 16;

    __int64 freq;
    QueryPerformanceFrequency( (LARGE_INTEGER*)&freq );
    __int64 start = 0, end;
    QueryPerformanceCounter( (LARGE_INTEGER*)&start );

    for( int step = 0; step < steps; ++step )
    {
        __declspec(align(16)) BYTE arr[16];
        for( int j = 0; j < 4; ++j )
        {
            //for( int i = 0; i < 4; ++i )
            {
                arr[0+j*4] = *(pSrc + channelStep0);
                arr[1+j*4] = *(pSrc + channelStep1);
                arr[2+j*4] = *(pSrc + channelStep2);
                arr[3+j*4] = *(pSrc + channelStep3);
            }
            pSrc += channelStep4;
        }

#if test1
// test 1 with C
        for( int i = 0; i < 16; ++i )
        {
            *(pDest + step * 16 + i) = arr[i];
        }
#else
// test 2 with SSE load/store    
        __m128i packedSamples = _mm_load_si128( (__m128i*)arr );
        _mm_stream_si128( ((__m128i*)pDest) + step, packedSamples );
#endif
    }

    QueryPerformanceCounter( (LARGE_INTEGER*)&end );

    printf( "%I64d", (end - start) * 1000 / freq );

}

بالنسبة لي اختبار 2 أسرع ثم اختبار 1.

هل أفعل شيئا خطأ؟ هل هذا ليس الرمز الذي تستخدمه؟ ماذا افتقد؟ هل هذا فقط بالنسبة لي؟

باستخدام تحسينات جوهرية مفيد التحويل البرمجي!

النقطة برمتها من الوظائف الجوهرية هي إدراج Opcodes لا يعرف المحول البرمجي في دفق Opcodes، يعرف المحول البرمجي وتوليد. ما لم يتم إعطاء برنامج التحويل البرمجي بعض بيانات التعريف حول OpCode وكيف يؤثر على السجلات والذاكرة، لا يمكن للمترجم افتراض أن يتم الحفاظ على أي بيانات بعد تنفيذ الجوهر. هذا يؤلمني حقا الجزء الأمثل من المحول البرمجي - لا يمكن إعادة ترتيب التعليمات حول الجوهرية، ولا يمكن أن تفترض أن السجلات تتأثر وما إلى ذلك.

أعتقد أن أفضل طريقة لتحسين هذا هو إلقاء نظرة على الصورة الأكبر - تحتاج إلى النظر في العملية برمتها من قراءة البيانات المصدر لكتابة الإخراج النهائي. نادرا ما تعطي تحسينات Micro نتائج كبيرة، إلا إذا كنت تفعل شيئا سيئا للغاية للبدء.

ربما، إذا قمت بالتفصيل الإدخال المطلوب والإخراج، فقد يقترح شخص ما هنا طريقة مثالية للتعامل معها.

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