문제

x86 CPU에서 부동 소수점 숫자를 int로 변환하는 가장 빠른 방법은 무엇입니까?다음 조합의 경우 C 또는 어셈블리(C로 인라인될 수 있음)를 사용하는 것이 좋습니다.

  • 32/64/80비트 부동 소수점 -> 32/64비트 정수

나는 컴파일러가 하도록 하는 것보다 더 빠른 기술을 찾고 있습니다.

도움이 되었습니까?

해결책

잘라내기 변환을 원하는지 반올림 변환을 원하는지와 정밀도에 따라 다릅니다.기본적으로 C는 float에서 int로 이동할 때 잘림 변환을 수행합니다.이를 수행하는 FPU 명령이 있지만 이는 ANSI C 변환이 아니며 이를 사용하는 데 있어 중요한 주의 사항이 있습니다(예: FPU 반올림 상태를 아는 것).귀하의 문제에 대한 답변은 매우 복잡하고 귀하가 표현하지 않은 일부 변수에 따라 달라지므로 해당 문제에 대한 다음 기사를 권장합니다.

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

다른 팁

SSE를 사용한 팩형 변환은 동일한 명령어에서 여러 값을 변환할 수 있으므로 가장 빠른 방법입니다. ffmpeg 이를 위해 많은 어셈블리가 있습니다(주로 오디오의 디코딩된 출력을 정수 샘플로 변환하기 위해).몇 가지 예를 확인해보세요.

일반 x86/x87 코드에 일반적으로 사용되는 트릭은 float의 가수 부분이 int를 나타내도록 하는 것입니다.32비트 버전은 다음과 같습니다.

64비트 버전은 유사합니다.위에 게시된 Lua 버전은 더 빠르지만 double을 32비트 결과로 잘라내는 데 의존하므로 x87 단위를 배정밀도로 설정해야 하며 double을 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과 호환된다는 것을 보장할 수 있다면(펜티엄 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를 확인하세요).더 빠른 방법을 찾으면 그들이 기뻐할 것이라고 확신합니다.

오, 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 "C"에서.

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)에서만 작동하므로 gcc로 컴파일하려면 GNU 어셈블리로 다시 작성해야 합니다.그러나 내장 기능이 포함된 SSE2 솔루션은 이식성이 뛰어납니다.

다른 반올림 모드는 다른 SSE2 내장 함수를 사용하거나 FPU 제어 워드를 다른 반올림 모드로 수동 설정하여 가능합니다.

이 속도에 정말로 관심이 있다면 컴파일러가 FIST 명령을 생성하고 있는지 확인하십시오.MSVC에서는 /QIfist를 사용하여 이 작업을 수행할 수 있습니다. 이 MSDN 개요를 참조하세요.

SSE 내장 함수를 사용하여 작업을 수행하는 것도 고려할 수 있습니다. Intel의 다음 문서를 참조하세요. http://softwarecommunity.intel.com/articles/eng/2076.htm

MS는 X64의 인라인 어셈블리를 제거하고 내장 함수를 사용하도록 강요하기 때문에 어떤 것을 사용할지 찾아보았습니다. MSDN 문서 준다 _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);

어쨌든 가장 가까운 값으로 반올림하는 것이 기본값입니다.

각 호출에서 반올림 모드를 설정할지 또는 복원될 것이라고 가정할지(제3자 라이브러리)에 대한 질문은 경험을 통해 대답해야 할 것 같습니다.당신은 포함해야합니다 float.h ~을 위한 _control87() 및 관련 상수.

그리고 아니요. 32비트에서는 작동하지 않으므로 FISTP 명령어를 계속 사용하세요.

_asm fld d
_asm fistp i

일반적으로 컴파일러가 효율적이고 정확하다고 신뢰할 수 있습니다.일반적으로 컴파일러에 이미 존재하는 것에 대해 자신의 함수를 롤링하여 얻을 수 있는 것은 없습니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top