Quelle est la manière la plus rapide de convertir float en int sur x86

Quelle est la façon la plus rapide de convertir un nombre à virgule flottante en int sur un CPU x86? Préférablement en C ou en assemblage (qui peut être intégré en C) pour toute combinaison des éléments suivants:

  • 32/64/80 bits float -> 32/64-bit integer

je cherche une technique qui est plus rapide que de simplement laisser le compilateur le faire.

20
demandé sur kristianp 2008-09-17 04:20:06

10 réponses

cela dépend si vous voulez une conversion tronquée ou un arrondi et à quelle précision. Par défaut, C effectuera une conversion tronquée lorsque vous passez de float à int. Il y a des instructions FPU qui le font, mais ce n'est pas une conversion ANSI C et il y a des mises en garde importantes à l'utiliser (comme connaître l'état d'arrondissement FPU). Puisque la réponse à votre problème est assez complexe et dépend de certaines variables, vous n'avez pas exprimé, je recommande cet article sur le question:

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

17
répondu Zach Burlingame 2008-09-17 00:34:40

Paniers de conversion à l'aide de l'ESS est de loin la méthode la plus rapide, puisque vous pouvez convertir plusieurs valeurs dans la même instruction. ffmpeg a beaucoup d'assemblage pour cela (principalement pour convertir la sortie décodée de l'audio en échantillons entiers); vérifiez-le pour quelques exemples.

13
répondu Dark Shikari 2014-07-23 17:45:43

une astuce couramment utilisée pour le code x86/x87 simple est de forcer la partie mantissa du flotteur à représenter l'int. Pour la version 32 bits de la façon suivante.

La version 64 bits est analogique. La version Lua affichée ci-dessus est plus rapide, mais s'appuie sur la troncature de double à un résultat de 32 bits, il nécessite donc l'unité x87 être réglé à double précision, et ne peut pas être adapté pour double à 64 bits conversion int.

ce qui est bien avec ce code, c'est qu'il est complètement portable pour toutes les plates-formes conformes à IEEE 754, la seule hypothèse faite est que le mode d'arrondi de la pointe flottante est réglé à la plus proche. Note: Portable dans le sens où il compile et fonctionne. Les plates-formes autres que x86 ne bénéficient généralement pas beaucoup de cette technique, voire pas du tout.

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;
}
9
répondu Suma 2013-04-30 16:10:51

si vous pouvez garantir que le CPU qui exécute votre code est compatible SSE3 (même Pentium 5 is, JBB), vous pouvez autoriser le compilateur à utiliser son instruction FISTTP (i.e.-msse3 pour gcc). Il semble faire la chose comme il se doit de toujours avoir été fait:

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

notez que FISTTP est différent de FISTP (qui a ses problèmes, provoquant la lenteur). Il vient dans le cadre de SSE3 mais est en fait (le seul) x87-side raffinement.

autre que x86 CPU ferait probablement la conversion très bien, de toute façon. :)

processeurs avec support SSE3

7
répondu akauppi 2009-03-15 11:34:23

il existe une instruction pour convertir un point flottant en int dans assembly: utilisez l'instruction FISTP. Il sort la valeur de la pile flottante, la convertit en un entier, puis la stocke à l'adresse spécifiée. Je ne pense pas qu'il y aurait un moyen plus rapide (à moins que vous n'utilisiez des ensembles d'instructions étendues comme MMX ou SSE, ce que je ne connais pas).

une autre instruction, FIST, laisse la valeur sur la pile FP mais je ne suis pas sûr que cela fonctionne avec quad-word sized destination.

6
répondu dreamlax 2008-09-17 00:27:00

la base de code Lua a l'extrait suivant pour faire ceci (cochez src/luaconf.h à partir de www.lua.org). Si vous trouvez (ainsi trouve) un moyen plus rapide, je suis sûr qu'ils seraient ravis.

Oh,lua_Number signifie double. :)

/*
@@ 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
6
répondu akauppi 2014-12-15 08:59:13

si vous vous souciez vraiment de la vitesse de ce programme, assurez-vous que votre compilateur génère l'instruction FIST. Dans MSVC vous pouvez le faire avec / QIfist, voir cet aperçu du MSDN

vous pouvez également envisager d'utiliser SSE intrinsics pour faire le travail pour vous, voir cet article de Intel:http://softwarecommunity.intel.com/articles/eng/2076.htm

3
répondu Don Neufeld 2008-09-17 00:47:32

comme Mme scews nous hors de l'assemblée en ligne dans X64 et nous oblige à utiliser intrinsics, j'ai regardé vers le haut à utiliser. MSDN doc donne _mm_cvtsd_si64x avec un exemple.

l'exemple fonctionne, mais est horriblement inefficace, en utilisant une charge non alignée de 2 doubles, où nous avons besoin d'une seule charge, donc se débarrasser de l'exigence d'alignement supplémentaire. Puis beaucoup de charges et de rechargements inutiles sont produits, mais ils peuvent être éliminés comme suit:

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

Résultat:

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

le mode d'arrondi peut être paramétré sans assemblage en ligne, par exemple

    _control87(_RC_NEAR,_MCW_RC);

où l'arrondissement à la plus proche est par défaut (de toute façon).

la question de savoir s'il faut régler le mode d'arrondi à chaque appel ou supposer qu'il sera rétabli (libs tiers) devra être répondue par l'expérience, je suppose. Vous aurez à inclure float.h_control87() et les constantes connexes.

Et, non, ce ne sera pas travaillez en 32 bits, alors continuez à utiliser L'instruction FISTP:

_asm fld d
_asm fistp i
3
répondu Jan 2013-07-20 07:25:09

je suppose que la troncature est nécessaire, comme si on écrit i = (int)f EN "C".

si vous avez SSE3, vous pouvez utiliser:

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

alternativement, avec SSE2 (ou dans x64 où l'assemblage en ligne pourrait ne pas être disponible), vous pouvez utiliser presque aussi vite:

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

sur les ordinateurs plus anciens il y a une option pour régler le mode d'arrondi manuellement et effectuer la conversion en utilisant l'ordinaire fistp instruction. Cela ne fonctionnera probablement que pour les ensembles de flotteurs, sinon les soins doivent être pris pour ne pas utiliser de constructions qui feraient changer le mode d'arrondi du compilateur (comme casting). Il est fait comme ceci:

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

notez que l'assemblage en ligne ne fonctionne qu'avec les compilateurs Visual Studio de Microsoft (et peut-être Borland), il devrait être réécrit à GNU assembly pour être compilé avec gcc. La solution SSE2 avec intrinsics devrait toutefois être assez portable.

les autres modes d'arrondi sont possibles par différents SSE2 intrinsèques ou manuellement paramétrage du mot de contrôle FPU sur un mode d'arrondi différent.

2
répondu the swine 2014-02-26 17:37:48

généralement, vous pouvez faire confiance au compilateur pour être efficace et correct. Il n'y a habituellement rien à gagner en roulant vos propres fonctions pour quelque chose qui existe déjà dans le compilateur.

-7
répondu user14504 2008-09-17 00:35:57