Pergunta

Qual é a maneira mais rápida de você saber para converter um número de ponto flutuante para um inteiro em uma CPU x86.De preferência em C ou assembly (que pode ser forrada em C) para qualquer combinação das seguintes opções:

  • 32/64/80-bit float -> 32/64-bit inteiro

Eu estou procurando alguma técnica que é mais rápido do que deixar que o compilador faça isso.

Foi útil?

Solução

Depende se você quer um truncamento de conversão ou de um arredondamento de um e do que precisão.Por padrão, C vai realizar uma truncar a conversão, quando você ir de float para int.Há FPU instruções que fazer isso, mas não é um ANSI C de conversão e são importantes as advertências quanto ao uso de ti (tais como saber o FPU de arredondamento estado).Pois a resposta para o seu problema é bastante complexo e depende de algumas variáveis ainda não expressa, recomendo este artigo sobre o assunto:

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

Outras dicas

Repleto de conversão usando o SSE é de longe o método mais rápido, desde que você pode converter vários valores na mesma instrução. ffmpeg tem um monte de montagem para isso (principalmente para a conversão de decodificada de saída de áudio para o número inteiro amostras);verifique se alguns exemplos.

Um comumente usado truque simples para x 86/87 x código é forçar a mantissa parte do carro alegórico para representar o int.Versão de 32 bits do seguinte modo.

A versão de 64 bits é analógica.A versão Lua postou acima é mais rápido, mas depende do truncamento da dupla para um resultado de 32 bits, portanto, requer o x 87 unidade a ser definida para precisão dupla, e não pode ser adaptado para a dupla de 64 bits, int conversão.

A coisa agradável sobre este código é completamente portátil para todas as plataformas conformidade com a norma IEEE 754, o único pressuposto é o de ponto flutuante modo de arredondamento é definido mais próximo.Nota:Portátil, no sentido de que compila e funciona.Outras plataformas x86, geralmente, não se beneficiam muito com esta técnica, se em tudo.

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;
}

Se você pode garantir a CPU a executar o seu código é SSE3 compatível (mesmo Pentium 5 é, JBB), você pode permitir que o compilador a usar sua FISTTP instrução (i.e.-msse3 para o gcc).Ele parece fazer a coisa como sempre deveria ter sido feito:

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

Note que FISTTP é diferente de FISTP (que tem seus problemas, causando a lentidão).Ele vem como parte de SSE3, mas, na verdade, é (o único) 87 X do lado do refinamento.

Outros X86 CPU provavelmente iria fazer a conversão bem, de qualquer maneira.:)

Processadores com suporte a SSE3

Há uma instrução para converter um ponto flutuante para um inteiro na montagem:use a instrução FISTP.Ele aparece o valor de ponto flutuante de pilha, o converte para um número inteiro e, em seguida, lojas no endereço especificado.Eu acho que não seria uma forma mais rápida (a menos que você use estendido conjuntos de instruções de como MMX ou SSE, que eu não estou familiarizado com).

Outra instrução, PUNHO, deixa o valor do FP pilha, mas eu não tenho certeza se ele funciona com processador quad-palavra do tamanho de destinos.

A Lua base de código tem o seguinte fragmento de código para fazer isso (check-in src/luaconf.h www.lua.org).Se você encontrar (ENTÃO encontra) um modo mais rápido, eu tenho certeza de que deseja ser feliz.

Oh, lua_Number significa o dobro.:)

/*
@@ 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

Eu suponho que o truncamento é necessário, mesmo que se escreve i = (int)f em "C".

Se você tem SSE3, você pode usar:

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

Alternadamente, com SSE2 (ou em x64 onde inline assembly podem não estar disponíveis), você pode usar quase tão rápido:

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

Em computadores mais antigos, existe uma opção para definir o modo de arredondamento manualmente e executar a conversão utilizando o processo fistp instrução.Que provavelmente só irá funcionar para matrizes de carros alegóricos, caso contrário, deve ser tomado cuidado para não usar quaisquer construções que faria o compilador alterar o modo de arredondamento (tais como fundição).Ele é feito assim:

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:
    }
}

Observe que a linha de montagem só funciona com Microsoft Visual Studio compiladores (e talvez Borland), teria que ser reescrito para GNU assembleia para compilar com o gcc.O SSE2 solução com intrínsecos, deve ser bastante portátil, no entanto.

Outros modos de arredondamento são possíveis por diferentes SSE2 intrínsecos ou definindo manualmente a FPU palavra de controle para um diferente modo de arredondamento.

Se você realmente se preocupam com a velocidade do certifique-se de que o seu compilador é gerar o PUNHO de instrução.No MSVC você pode fazer isso com /QIfist, veja este MSDN visão geral

Você também pode considerar o uso SSE intrínsecos para fazer o trabalho para você, consulte este artigo da Intel: http://softwarecommunity.intel.com/articles/eng/2076.htm

Desde que o MS scews-nos de assembly embutido em X64 e obriga-nos a usar intrínsecos, eu olhei para cima para usar. MSDN doc_mm_cvtsd_si64x com um exemplo.

O exemplo que funciona, mas é terrivelmente ineficiente, usando um unaligned de carga de 2 de casal, onde precisamos apenas de uma única carga, de modo a livrar-se dos adicionais exigência de alinhamento.Em seguida, um monte inúteis as cargas e recargas são produzidos, mas eles podem ser eliminados da seguinte forma:

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

Resultado:

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

O modo de arredondamento pode ser definido sem inline assembly, por exemplo,

    _control87(_RC_NEAR,_MCW_RC);

onde arredondamento para o mais próximo é o padrão (de qualquer maneira).

A questão de se definir o modo de arredondamento em cada chamada ou assumir ele será restaurado (terceiro libs) terão que ser respondidas por experiência, eu acho.Você terá que incluir o float.h para _control87() e constantes relacionadas.

E, não, isso não vai funcionar em 32 bits, de modo a manter a utilizar a instrução FISTP:

_asm fld d
_asm fistp i

Geralmente, você pode confiar que o compilador para ser eficiente e correta.Geralmente há nada a ser ganho pelo material de suas próprias funções para algo que já existe no compilador.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top