Frage

Was ist der schnellste Weg, Sie kennen eine Gleitkommazahl in einen int auf einer x86-CPU zu konvertieren. Vorzugsweise in C oder eine Baugruppe für eine beliebige Kombination der folgenden Schritte (das in ausgekleideten in C sein kann):

  • 32/64 / 80-Bit float -> 32/64-Bit-Ganzzahl

Ich interessiere mich für einige Technik, die schneller als nur lassen Sie den Compiler ist es.

War es hilfreich?

Lösung

Es hängt davon ab, ob Sie eine Abschneide Umwandlung oder eine Rundung ein und auf welcher Präzision wollen. Standardmäßig wird C eine Kürzen Konvertierung durchführen, wenn Sie von float zu int gehen. Es gibt FPU Anweisungen, die es tun, aber es ist nicht eine ANSI-C-Konvertierung und gibt es erhebliche Vorbehalte zu verwenden es (wie den FPU Rundungszustand zu kennen). Da die Antwort auf Ihr Problem ist sehr komplex und hängt von einigen Variablen, die Sie nicht zum Ausdruck gebracht haben, habe ich diesen Artikel zu diesem Thema empfehlen:

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

Andere Tipps

Verpackt Umwandlung SSE verwendet, ist bei weitem die schnellste Methode, da Sie mehrere Werte im gleichen Befehl umwandeln kann. ffmpeg viel Montage hierfür hat (vor allem für die decodierte Ausgabe von Audio auf ganzzahlige Abtastwerte Umwandlung); überprüfen Sie es für einige Beispiele.

Ein häufig verwendeter Trick für Normal x86 / x87-Code ist den Mantissenteil des Schwimmers zu zwingen, die int darzustellen. 32-Bit-Version folgt.

Die 64-Bit-Version ist analogical. Die Lua-Version oben geschrieben ist schneller, sondern stützt sich auf das Abschneiden von Doppel zu einem 32-Bit-Ergebnis, daher ist es die x87-Einheit benötigt, um mit doppelter Genauigkeit eingestellt werden, und nicht für doppelte bis 64-Bit-int Umwandlung angepasst werden kann.

Das Schöne an diesem Code ist es für alle Plattformen vollständig tragbar ist IEEE konformen 754, die einzige Annahme der Gleitkomma-Rundungsmodus zum nächsten eingestellt gemacht wird. Hinweis: Portable in dem Sinne, es kompiliert und funktioniert. Andere Plattformen als x86 Regel nicht von dieser Technik nicht viel profitieren, wenn überhaupt.

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

Wenn Sie die CPU garantieren kann Ihr Code ausgeführt wird, SSE3 kompatibel (auch 5 Pentium ist, JBB), können Sie die Compiler ermöglichen, seine FISTTP Anweisung zu verwenden (das heißt -msse3 für gcc). Es scheint die Sache zu tun, wie es immer getan haben sollte:

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

Beachten Sie, dass FISTTP von FISTP unterscheidet (das hat seine Probleme, was die Langsamkeit). Es kommt als Teil SSE3 ist aber eigentlich (einzige) X87-Seite Verfeinerung.

Andere dann X86 CPUs wahrscheinlich die Umwandlung nur in Ordnung, sowieso tun würde. :)

Prozessoren mit SSE3-Unterstützung

Es gibt eine Anweisung, ein Floating-Point in einen int in der Montage zu konvertieren: die FISTP Anweisung verwenden. Es erscheint, den Wert von dem Gleitkomma-Stapel, wandelt sie in eine Ganzzahl, und speichert dann bei unter der Adresse angegeben ist. Ich glaube nicht, es wäre ein schneller Weg sein (es sei denn, Sie erweiterte Befehlssätze wie MMX oder SSE verwenden, die ich bin nicht mit).

Eine weitere Anweisung, FIST, verläßt den Wert auf dem FP-Stack, aber ich bin nicht sicher, ob es funktioniert mit Quad-Wort großen Zielen.

Die Lua Code-Basis folgende Schnipsel hat diese (in src / luaconf.h von www.lua.org überprüfen) zu tun. Wenn Sie (SO findet) einen schnelleren Weg finden, ich bin sicher, sie werden begeistert sein würde.

Oh, bedeutet lua_Number verdoppeln. :)

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

Ich gehe davon aus Abschneiden erforderlich ist, gleich wie wenn man i = (int)f in "C" schreibt.

Wenn Sie SSE3 haben, können Sie:

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

Alternativ mit SSE2 (oder in x64 wo Inline-Assembly nicht verfügbar sein könnte), können Sie fast so schnell verwenden:

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

Bei älteren Computern gibt es eine Option, um den Rundungsmodus manuell einstellen und führen Umwandlung der gewöhnlichen fistp Anweisung verwendet wird. Das wird wohl nur für Arrays von Schwimmern arbeiten, sonst muss darauf geachtet werden, um keine Konstrukte zu verwenden, die den Compiler Änderung Rundungsmodus (wie Gießen) machen würden. Es wird so gemacht:

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

Beachten Sie, dass die Inline-Montage nur mit Microsoft Visual Studio Compiler funktioniert (und vielleicht Borland), wäre es müssen GNU Montage, um mit gcc zu kompilieren neu geschrieben werden. Die SSE2 Lösung mit intrinsics sollte recht tragbar sein, aber.

Andere Rundungsmodi möglich sind, die von verschiedenem SSE2 intrinsics oder durch manuell das FPU-Steuerwort an einen anderen Rundungsmodus zu setzen.

Wenn Sie wirklich kümmern uns um die Geschwindigkeit dieses sicherzustellen, dass Ihr Compiler die Anweisung FIST erzeugt. In MSVC können Sie tun dies mit / QIfist, diesem MSDN Überblick

Sie können auch unter Verwendung von SSE-Spezifika berücksichtigen, die Arbeit für Sie zu tun, diesen Artikel von Intel finden Sie unter: http://softwarecommunity.intel.com/articles/eng/2076.htm

Da MS uns aus Inline-Montage in X64 scews und zwingt uns intrinsics zu verwenden, schaute ich auf die verwenden. MSDN doc gibt _mm_cvtsd_si64x mit einem Beispiel .

Das Beispiel funktioniert, aber ist schrecklich ineffizient, eine nicht ausgerichtete Belastung von 2 Doppelzimmern mit, wo wir nur eine einzige Last benötigen, so der zusätzlichen Ausrichtung Anforderung loszuwerden. Dann viele unnötige Lasten und Reloads produziert werden, aber sie können beseitigt werden, wie folgt:

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

Ergebnis:

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

Der Rundungsmodus kann ohne Inline-Assembler eingestellt werden, z.

    _control87(_RC_NEAR,_MCW_RC);

, wo zum nächsten Runden ist default (sowieso).

Die Frage, ob den Rundungsmodus bei jedem Aufruf setzen oder sie zu übernehmen, werden gestellt (Dritter Libs) wird durch die Erfahrung zu beantworten hat, denke ich. Sie haben float.h für _control87() und zugehörige Konstanten sind.

Und, nein, das wird nicht in 32 Bit arbeiten, so hält die FISTP Anweisung verwendet:

_asm fld d
_asm fistp i

Im Allgemeinen können Sie den Compiler vertrauen effizient und korrekt zu sein. Es ist in der Regel nichts durch Walzen Ihre eigenen Funktionen für etwas gewonnen werden, die bereits im Compiler vorhanden ist.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top