سؤال

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

  • تعويم 32/64/80 بت -> عدد صحيح 32/64 بت

أنا أبحث عن تقنية أسرع من مجرد السماح للمترجم بالقيام بذلك.

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

المحلول

يعتمد ذلك على ما إذا كنت تريد تحويلاً مقتطعًا أو تحويلاً تقريبيًا وبأي دقة.بشكل افتراضي، سيقوم C بإجراء تحويل مقتطع عندما تنتقل من float إلى int.توجد تعليمات FPU تقوم بذلك ولكنها ليست تحويل ANSI C وهناك محاذير كبيرة لاستخدامها (مثل معرفة حالة تقريب FPU).نظرًا لأن الإجابة على مشكلتك معقدة جدًا وتعتمد على بعض المتغيرات التي لم يتم التعبير عنها، فإنني أوصي بهذا المقال حول هذه المشكلة:

http://www.stereopsis.com/FPU.html

نصائح أخرى

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

إحدى الخدع الشائعة الاستخدام لرمز x86/x87 العادي هي إجبار الجزء العشري من العائمة على تمثيل int.يتبع الإصدار 32 بت.

الإصدار 64 بت تمثيلي.إصدار Lua المنشور أعلاه أسرع، ولكنه يعتمد على اقتطاع نتيجة مضاعفة إلى 32 بت، وبالتالي يتطلب ضبط وحدة x87 على دقة مضاعفة، ولا يمكن تكييفه لتحويل مزدوج إلى 64 بت.

والشيء الجميل في هذا الرمز هو أنه قابل للنقل تمامًا لجميع الأنظمة الأساسية المطابقة لـ IEEE 754، والافتراض الوحيد الذي تم وضعه هو أن وضع تقريب النقطة العائمة تم ضبطه على الأقرب.ملحوظة:محمول بمعنى أنه يجمع ويعمل.الأنظمة الأساسية بخلاف x86 عادةً لا تستفيد كثيرًا من هذه التقنية، هذا إن استفادت على الإطلاق.

static const float Snapper=3<<22;

union UFloatInt {
 int i;
 float f;
};

/** by Vlad Kaipetsky
portable assuming FP24 set to nearest rounding mode
efficient on x86 platform
*/
inline int toInt( float fval )
{
  Assert( fabs(fval)<=0x003fffff ); // only 23 bit values handled
  UFloatInt &fi = *(UFloatInt *)&fval;
  fi.f += Snapper;
  return ( (fi.i)&0x007fffff ) - 0x00400000;
}

إذا كان بإمكانك ضمان أن وحدة المعالجة المركزية التي تقوم بتشغيل التعليمات البرمجية الخاصة بك متوافقة مع SSE3 (حتى Pentium 5 هو JBB)، فيمكنك السماح للمترجم باستخدام تعليمات FISTTP الخاصة به (على سبيل المثال.-mse3 لدول مجلس التعاون الخليجي).يبدو أنه يفعل الشيء كما كان ينبغي القيام به دائمًا:

http://software.intel.com/en-us/articles/how-to-implement-the-fisttp-streaming-simd-extensions-3-instruction/

لاحظ أن FISTTP يختلف عن FISTP (الذي له مشاكله، مما يسبب البطء).إنه يأتي كجزء من SSE3 ولكنه في الواقع (الوحيد) تحسين جانب X87.

بخلاف ذلك، من المحتمل أن تقوم وحدات المعالجة المركزية X86 بإجراء التحويل بشكل جيد، على أي حال.:)

المعالجات التي تدعم SSE3

هناك تعليمات واحدة لتحويل النقطة العائمة إلى int في التجميع:استخدم تعليمات FISTP.يقوم بإخراج القيمة من مكدس الفاصلة العائمة، ويحولها إلى عدد صحيح، ثم يخزنها في العنوان المحدد.لا أعتقد أنه ستكون هناك طريقة أسرع (إلا إذا كنت تستخدم مجموعات تعليمات موسعة مثل MMX أو SSE، والتي لست على دراية بها).

تعليمات أخرى، FIST، تترك القيمة في مكدس FP ولكني لست متأكدًا من أنها تعمل مع الوجهات ذات الحجم الرباعي.

تحتوي قاعدة كود Lua على المقتطف التالي للقيام بذلك (راجع src/luaconf.h من www.lua.org).إذا وجدت (SO وجدت) طريقة أسرع، فأنا متأكد من أنهم سيشعرون بسعادة غامرة.

أوه، lua_Number يعني مزدوج.:)

/*
@@ lua_number2int is a macro to convert lua_Number to int.
@@ lua_number2integer is a macro to convert lua_Number to lua_Integer.
** CHANGE them if you know a faster way to convert a lua_Number to
** int (with any rounding method and without throwing errors) in your
** system. In Pentium machines, a naive typecast from double to int
** in C is extremely slow, so any alternative is worth trying.
*/

/* On a Pentium, resort to a trick */
#if defined(LUA_NUMBER_DOUBLE) && !defined(LUA_ANSI) && !defined(__SSE2__) && \
    (defined(__i386) || defined (_M_IX86) || defined(__i386__))

/* On a Microsoft compiler, use assembler */
#if defined(_MSC_VER)

#define lua_number2int(i,d)   __asm fld d   __asm fistp i
#define lua_number2integer(i,n)     lua_number2int(i, n)

/* the next trick should work on any Pentium, but sometimes clashes
   with a DirectX idiosyncrasy */
#else

union luai_Cast { double l_d; long l_l; };
#define lua_number2int(i,d) \
  { volatile union luai_Cast u; u.l_d = (d) + 6755399441055744.0; (i) = u.l_l; }
#define lua_number2integer(i,n)     lua_number2int(i, n)

#endif

/* this option always works, but may be slow */
#else
#define lua_number2int(i,d) ((i)=(int)(d))
#define lua_number2integer(i,d) ((i)=(lua_Integer)(d))

#endif

أفترض أن الاقتطاع مطلوب، كما لو كان المرء يكتب i = (int)f شركة".

إذا كان لديك SSE3، يمكنك استخدام:

int convert(float x)
{
    int n;
    __asm {
        fld x
        fisttp n // the extra 't' means truncate
    }
    return n;
}

بدلاً من ذلك، مع SSE2 (أو في x64 حيث قد لا يكون التجميع المضمن متاحًا)، يمكنك الاستخدام بنفس السرعة تقريبًا:

#include <xmmintrin.h>
int convert(float x)
{
    return _mm_cvtt_ss2si(_mm_load_ss(&x)); // extra 't' means truncate
}

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

void Set_Trunc()
{
    // cw is a 16-bit register [_ _ _ ic rc1 rc0 pc1 pc0 iem _ pm um om zm dm im]
    __asm {
        push ax // use stack to store the control word
        fnstcw word ptr [esp]
        fwait // needed to make sure the control word is there
        mov ax, word ptr [esp] // or pop ax ...
        or ax, 0xc00 // set both rc bits (alternately "or ah, 0xc")
        mov word ptr [esp], ax // ... and push ax
        fldcw word ptr [esp]
        pop ax
    }
}

void convertArray(int *dest, const float *src, int n)
{
    Set_Trunc();
    __asm {
        mov eax, src
        mov edx, dest
        mov ecx, n // load loop variables

        cmp ecx, 0
        je bottom // handle zero-length arrays

    top:
        fld dword ptr [eax]
        fistp dword ptr [edx]
        loop top // decrement ecx, jump to top
    bottom:
    }
}

لاحظ أن التجميع المضمّن يعمل فقط مع مترجمي Microsoft Visual Studio (وربما بورلاند)، ويجب إعادة كتابته إلى تجميع GNU من أجل التجميع مع gcc.ومع ذلك، يجب أن يكون حل SSE2 ذو العناصر الجوهرية محمولاً تمامًا.

أوضاع التقريب الأخرى ممكنة من خلال جوهريات SSE2 مختلفة أو عن طريق ضبط كلمة التحكم FPU يدويًا على وضع تقريب مختلف.

إذا كنت تهتم حقًا بسرعة هذا الأمر، فتأكد من أن المترجم الخاص بك يقوم بإنشاء تعليمات FIST.في MSVC يمكنك القيام بذلك باستخدام /QIfist، راجع نظرة عامة على MSDN

يمكنك أيضًا التفكير في استخدام جوهريات SSE للقيام بالعمل نيابةً عنك، راجع هذه المقالة من Intel: http://softwarecommunity.intel.com/articles/eng/2076.htm

نظرًا لأن MS يخرجنا من التجميع المضمّن في X64 ويجبرنا على استخدام العناصر الجوهرية، فقد بحثت عن أي منها يجب استخدامه. وثيقة MSDN يعطي _mm_cvtsd_si64x مع مثال.

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

 #include <intrin.h>
 #pragma intrinsic(_mm_cvtsd_si64x)
 long long _inline double2int(const double &d)
 {
     return _mm_cvtsd_si64x(*(__m128d*)&d);
 }

نتيجة:

        i=double2int(d);
000000013F651085  cvtsd2si    rax,mmword ptr [rsp+38h]  
000000013F65108C  mov         qword ptr [rsp+28h],rax  

يمكن ضبط وضع التقريب بدون التجميع المضمن، على سبيل المثال.

    _control87(_RC_NEAR,_MCW_RC);

حيث التقريب إلى الأقرب هو الافتراضي (على أي حال).

أعتقد أن السؤال حول ما إذا كان سيتم تعيين وضع التقريب في كل مكالمة أو افتراض أنه سيتم استعادته (مكتبات الطرف الثالث) يجب الإجابة عليه من خلال الخبرة.سيكون عليك تضمينها float.h ل _control87() والثوابت ذات الصلة.

ولا، لن يعمل هذا في 32 بت، لذا استمر في استخدام تعليمات FISTP:

_asm fld d
_asm fistp i

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

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