簡體   English   中英

在x86上將float轉換為int的最快方法是什么

[英]What is the fastest way to convert float to int on x86

在x86 CPU上將浮點數轉換為int的最快方法是什么。 優選在C或組件中(可以在C中內嵌)以獲得以下任何組合:

  • 32/64/80位浮點數 - > 32/64位整數

我正在尋找一些比讓編譯器更快的技術。

這取決於您是否需要截斷轉換或舍入轉換以及精確度。 默認情況下,當您從float轉到int時,C將執行截斷轉換。 有FPU指令可以做到這一點,但它不是ANSI C轉換,並且使用它有很多警告(例如了解FPU舍入狀態)。 由於你的問題的答案非常復雜,並且取決於你沒有表達的一些變量,我推薦這篇文章:

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

使用SSE的打包轉換是迄今為止最快的方法,因為您可以在同一指令中轉換多個值。 ffmpeg有很多組裝(主要用於將音頻的解碼輸出轉換為整數樣本); 檢查它的一些例子。

普通x86 / x87代碼的常用技巧是強制浮點的尾數部分表示int。 隨后是32位版本。

64位版本是類比的。 上面發布的Lua版本更快,但依賴於截斷double到32位結果,因此它需要將x87單位設置為雙精度,並且不能適用於雙到64位int轉換。

這個代碼的好處是它對於符合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;
}

如果可以保證運行代碼的CPU與SSE3兼容(即使是Pentium 5,JBB),也可以允許編譯器使用其FISTTP指令(即-msse3用於gcc)。 它似乎做了應該總是這樣做的事情:

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

請注意,FISTTP與FISTP不同(它有問題,導致速度緩慢)。 它是SSE3的一部分,但實際上是(唯一的)X87端的改進。

除此之外,X86 CPU可能會很好地進行轉換。 :)

支持SSE3的處理器

在匯編中有一條指令將浮點轉換為int:使用FISTP指令。 它將浮點堆棧中的值彈出,將其轉換為整數,然后將其存儲在指定的地址處。 我認為不會有更快的方式(除非你使用像我不熟悉的MMX或SSE這樣的擴展指令集)。

另一條指令FIST將值保留在FP堆棧上,但我不確定它是否適用於四字大小的目的地。

Lua代碼庫有以下代碼片段(請訪問www.lua.org查看src / luaconf.h)。 如果你發現(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

我假設需要截斷,就像在“C”中寫入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編譯器(也許是Borland),它必須重寫為GNU程序集才能使用gcc進行編譯。 然而,具有內在函數的SSE2解決方案應該是非常便攜的。

其他舍入模式可以通過不同的SSE2內在函數或通過手動將FPU控制字設置為不同的舍入模式來實現。

由於MS在X64中使我們脫離內聯匯編並迫使我們使用內在函數,因此我查找了要使用的內容。 MSDN doc給出了_mm_cvtsd_si64x的一個例子。

這個例子有效,但效率非常低,使用2個雙倍的未對齊加載,我們只需要一個加載,因此擺脫了額外的對齊要求。 然后產生了許多不必要的負載和重新加載,但它們可以如下消除:

 #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);

舍入到最近的是默認值(無論如何)。

我想,是否要在每次通話中設置舍入模式或假設它將被恢復(第三方庫)的問題必須通過經驗來回答。 您必須為_control87()和相關常量包含float.h

並且,不,這不會在32位中工作,因此請繼續使用FISTP指令:

_asm fld d
_asm fistp i

如果你真的關心它的速度,請確保你的編譯器正在生成FIST指令。 在MSVC中,您可以使用/ QIfist執行此操作, 請參閱此MSDN概述

您還可以考慮使用SSE內在函數為您完成工作,請參閱英特爾的這篇文章: http//softwarecommunity.intel.com/articles/eng/2076.htm

通常,您可以信任編譯器高效且正確。 通常可以通過為編譯器中已存在的東西滾動自己的函數來獲得任何東西。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM