c++ - لماذا يغير 0.1f إلى 0 بطء الأداء بنسبة 10x؟




performance visual-studio-2010 (3)

في دول مجلس التعاون الخليجي ، يمكنك تمكين FTZ و DAZ باستخدام هذا:

#include <xmmintrin.h>

#define FTZ 1
#define DAZ 1   

void enableFtzDaz()
{
    int mxcsr = _mm_getcsr ();

    if (FTZ) {
            mxcsr |= (1<<15) | (1<<11);
    }

    if (DAZ) {
            mxcsr |= (1<<6);
    }

    _mm_setcsr (mxcsr);
}

استخدم أيضًا مفاتيح gcc: -msse -mfpmath = sse

(الاعتمادات المقابلة لكارل Hetherington [1])

[1] 1

لماذا هذا الجزء من التعليمات البرمجية ،

const float x[16] = {  1.1,   1.2,   1.3,     1.4,   1.5,   1.6,   1.7,   1.8,
                       1.9,   2.0,   2.1,     2.2,   2.3,   2.4,   2.5,   2.6};
const float z[16] = {1.123, 1.234, 1.345, 156.467, 1.578, 1.689, 1.790, 1.812,
                     1.923, 2.034, 2.145,   2.256, 2.367, 2.478, 2.589, 2.690};
float y[16];
for (int i = 0; i < 16; i++)
{
    y[i] = x[i];
}

for (int j = 0; j < 9000000; j++)
{
    for (int i = 0; i < 16; i++)
    {
        y[i] *= x[i];
        y[i] /= z[i];
        y[i] = y[i] + 0.1f; // <--
        y[i] = y[i] - 0.1f; // <--
    }
}

تشغيل أكثر من 10 مرات أسرع من البتة التالية (متطابقة باستثناء ما لوحظ)؟

const float x[16] = {  1.1,   1.2,   1.3,     1.4,   1.5,   1.6,   1.7,   1.8,
                       1.9,   2.0,   2.1,     2.2,   2.3,   2.4,   2.5,   2.6};
const float z[16] = {1.123, 1.234, 1.345, 156.467, 1.578, 1.689, 1.790, 1.812,
                     1.923, 2.034, 2.145,   2.256, 2.367, 2.478, 2.589, 2.690};
float y[16];
for (int i = 0; i < 16; i++)
{
    y[i] = x[i];
}

for (int j = 0; j < 9000000; j++)
{
    for (int i = 0; i < 16; i++)
    {
        y[i] *= x[i];
        y[i] /= z[i];
        y[i] = y[i] + 0; // <--
        y[i] = y[i] - 0; // <--
    }
}

عند التحويل البرمجي باستخدام Visual Studio 2010 SP1. (أنا لم تختبر مع compilers أخرى.)


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

  • قد لا تعمل في بعض بيئات دول مجلس التعاون الخليجي:

    // Requires #include <fenv.h>
    fesetenv(FE_DFL_DISABLE_SSE_DENORMS_ENV);
    
  • قد لا تعمل في بعض بيئات Visual Studio: 1

    // Requires #include <xmmintrin.h>
    _mm_setcsr( _mm_getcsr() | (1<<15) | (1<<6) );
    // Does both FTZ and DAZ bits. You can also use just hex value 0x8040 to do both.
    // You might also want to use the underflow mask (1<<11)
    
  • يظهر للعمل في كل من GCC و Visual Studio:

    // Requires #include <xmmintrin.h>
    // Requires #include <pmmintrin.h>
    _MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON);
    _MM_SET_DENORMALS_ZERO_MODE(_MM_DENORMALS_ZERO_ON);
    
  • يحتوي برنامج إنتل على خيارات لتعطيل عدم التشويه بشكل افتراضي على معالجات إنتل الحديثة. مزيد من التفاصيل هنا

  • مفاتيح المترجم. -ffast-math أو -msse أو -mfpmath=sse سيؤدي إلى تعطيل -msse -mfpmath=sse وإجراء بعض الأشياء الأخرى بشكل أسرع ، ولكن مع الأسف أيضًا الكثير من عمليات التقريب الأخرى التي قد تؤدي إلى كسر الشفرة. اختبار بعناية! ما يعادل الرياضيات السريعة لمترجم Visual Studio هو /fp:fast ولكني لم أتمكن من تأكيد ما إذا كان هذا يعطل كذلك عدم التشويه. 1


مرحبًا بك في عالم العائمة المزيلة ! يمكن أن تعيث فسادا في الأداء!

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

إذا قمت بطباعة الأرقام بعد 10،000 تكرار ، سترى أنها تقاربت إلى قيم مختلفة اعتمادًا على ما إذا تم استخدام 0 أو 0.1 .

هنا رمز الاختبار المترجمة على x64:

int main() {

    double start = omp_get_wtime();

    const float x[16]={1.1,1.2,1.3,1.4,1.5,1.6,1.7,1.8,1.9,2.0,2.1,2.2,2.3,2.4,2.5,2.6};
    const float z[16]={1.123,1.234,1.345,156.467,1.578,1.689,1.790,1.812,1.923,2.034,2.145,2.256,2.367,2.478,2.589,2.690};
    float y[16];
    for(int i=0;i<16;i++)
    {
        y[i]=x[i];
    }
    for(int j=0;j<9000000;j++)
    {
        for(int i=0;i<16;i++)
        {
            y[i]*=x[i];
            y[i]/=z[i];
#ifdef FLOATING
            y[i]=y[i]+0.1f;
            y[i]=y[i]-0.1f;
#else
            y[i]=y[i]+0;
            y[i]=y[i]-0;
#endif

            if (j > 10000)
                cout << y[i] << "  ";
        }
        if (j > 10000)
            cout << endl;
    }

    double end = omp_get_wtime();
    cout << end - start << endl;

    system("pause");
    return 0;
}

انتاج:

#define FLOATING
1.78814e-007  1.3411e-007  1.04308e-007  0  7.45058e-008  6.70552e-008  6.70552e-008  5.58794e-007  3.05474e-007  2.16067e-007  1.71363e-007  1.49012e-007  1.2666e-007  1.11759e-007  1.04308e-007  1.04308e-007
1.78814e-007  1.3411e-007  1.04308e-007  0  7.45058e-008  6.70552e-008  6.70552e-008  5.58794e-007  3.05474e-007  2.16067e-007  1.71363e-007  1.49012e-007  1.2666e-007  1.11759e-007  1.04308e-007  1.04308e-007

//#define FLOATING
6.30584e-044  3.92364e-044  3.08286e-044  0  1.82169e-044  1.54143e-044  2.10195e-044  2.46842e-029  7.56701e-044  4.06377e-044  3.92364e-044  3.22299e-044  3.08286e-044  2.66247e-044  2.66247e-044  2.24208e-044
6.30584e-044  3.92364e-044  3.08286e-044  0  1.82169e-044  1.54143e-044  2.10195e-044  2.45208e-029  7.56701e-044  4.06377e-044  3.92364e-044  3.22299e-044  3.08286e-044  2.66247e-044  2.66247e-044  2.24208e-044

لاحظ كيف أن الأرقام في المدى الثاني قريبة جدًا من الصفر.

عادةً ما تكون الأرقام التي تم تسويتها نادرة ، وبالتالي لا تحاول معظم المعالجات التعامل معها بكفاءة.

لإثبات أن هذا له علاقة بالأرقام التي لم يتم تسويتها ، إذا قمنا بإسقاط حالات عدم التسليم إلى الصفر بإضافة هذا إلى بداية الكود:

_MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON);

عندئذٍ ، لم يعد الإصدار الذي يحتوي على 0 أبطأ عشرة أضعاف وأصبح أسرع في الواقع. (يتطلب ذلك تجميع التعليمات البرمجية مع تمكين SSE.)

وهذا يعني أنه بدلاً من استخدام هذه القيم الغريبة ذات الدقة المنخفضة تقريبًا ، فإننا بدلاً من استخدام الصفر تقريبًا بدلاً من ذلك.

التوقيت: Core i7 920 @ 3.5 غيغاهرتز:

//  Don't flush denormals to zero.
0.1f: 0.564067
0   : 26.7669

//  Flush denormals to zero.
0.1f: 0.587117
0   : 0.341406

في النهاية ، هذا ليس له علاقة فعلاً بما إذا كان عددًا صحيحًا أو عائمًا. يتم تحويل 0 أو 0.1f / تخزينها إلى سجل خارج كلا الحلقتين. لذلك ليس له أي تأثير على الأداء.







floating-point