Каков самый быстрый способ конвертировать float в int на x86
Каков самый быстрый способ преобразования числа с плавающей запятой в int на процессоре x86. Предпочтительно в С или сборке (которая может быть облицована в С) для любой комбинации из следующего:
- 32/64/80-bit float → 32/64-разрядное целое
Я ищу технику, которая быстрее, чем просто позволить компилятору сделать это.
Ответы
Ответ 1
Это зависит от того, хотите ли вы усекать конверсию или округлять, и с какой точностью. По умолчанию C будет выполнять усекающее преобразование, когда вы переходите от float к int. Есть инструкции FPU, которые это делают, но это не преобразование ANSI C, и есть существенные оговорки относительно его использования (например, знание состояния округления FPU). Поскольку ответ на вашу проблему довольно сложный и зависит от некоторых переменных, которые вы не выражали, я рекомендую эту статью по этой проблеме:
http://www.stereopsis.com/FPU.html
Ответ 2
Упакованное преобразование с использованием SSE является самым быстрым методом, поскольку вы можете преобразовать несколько значений в одну и ту же инструкцию. ffmpeg имеет много сборок для этого (в основном для преобразования декодированного вывода аудио в целые образцы); проверьте его для некоторых примеров.
Ответ 3
Обычно используемый трюк для простого кода x86/x87 заключается в том, чтобы заставить часть мантиссы поплавка представлять int. Далее следует 32-разрядная версия.
64-разрядная версия аналогична. Версия Lua, опубликованная выше, выполняется быстрее, но полагается на усечение двойного на 32-битный результат, поэтому требуется, чтобы блок x87 был настроен на двойную точность и не может быть адаптирован для преобразования с двойной или 64-разрядной версией.
Приятная вещь в этом коде - это полностью переносимая для всех платформ, соответствующих 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;
}
Ответ 4
Существует одна команда для преобразования числа с плавающей точкой в int в сборке: используйте инструкцию FISTP. Он выдает значение из стека с плавающей запятой, преобразует его в целое, а затем сохраняет по указанному адресу. Я не думаю, что был бы более быстрый способ (если вы не используете расширенные наборы команд, такие как MMX или SSE, с которыми я не знаком).
Другая инструкция, FIST, оставляет значение в стеке FP, но я не уверен, что он работает с четырьмя местами для определения размера слова.
Ответ 5
База кода Lua имеет следующий фрагмент для этого (проверьте src/luaconf.h с сайта www.lua.org).
Если вы обнаружите (SO находит) более быстрый способ, я уверен, что они будут в восторге.
О, lua_Number
означает 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
Если вы можете гарантировать, что процессор, работающий на вашем коде, совместим с 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, вероятно, сделает конверсию просто прекрасной.:)
Процессоры с поддержкой SSE3
Ответ 7
Если вам действительно нужна скорость, убедитесь, что ваш компилятор генерирует инструкцию FIST. В MSVC вы можете сделать это с помощью /QIfist, посмотреть этот обзор MSDN
Вы также можете рассмотреть возможность использования SSE для выполнения этой работы, см. эту статью от Intel: http://softwarecommunity.intel.com/articles/eng/2076.htm
Ответ 8
Поскольку 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);
где округление до ближайшего по умолчанию (в любом случае).
Вопрос о том, устанавливать ли режим округления при каждом вызове или принять его, будет восстановлен (сторонние библиотеки), на мой взгляд, должен быть отвечен опытом.
Вы должны включить float.h
для _control87()
и связанных констант.
И нет, это не будет работать в 32 битах, поэтому продолжайте использовать инструкцию FISTP:
_asm fld d
_asm fistp i
Ответ 9
Я предполагаю, что требуется усечение, так же, как если бы он записывал 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), ее нужно будет переписать на сборку GNU для компиляции с помощью gcc.
Однако решение SSE2 с встроенными функциями должно быть довольно портативным.
Другие режимы округления возможны с помощью различных встроенных функций SSE2 или путем ручной установки управляющего слова FPU в другой режим округления.
Ответ 10
Как правило, вы можете доверять компилятору, чтобы он был эффективным и правильным. Как правило, ничего не получается, перевернув собственные функции для чего-то, что уже существует в компиляторе.