Каков самый быстрый способ преобразования числа с плавающей запятой в int на процессоре x86? Предпочтительно в C или сборке (которая может быть встроена в C) для любой комбинации следующего:
Я ищу метод, который будет быстрее, чем просто позволить компилятору сделать это.
Я катаюсь по земле. Черт возьми, как же плохо, что тебя понизили для этого!
:) А есть ли вообще Pentium 5? И если есть, извините, у него есть SSE3, и поэтому все в порядке. При разумном использовании (см. Комментарии к SSE3 и FISTTP).





Пакетное преобразование с использованием SSE - безусловно, самый быстрый метод, поскольку вы можете преобразовать несколько значений в одной инструкции. ffmpeg имеет много ассемблера для этого (в основном для преобразования декодированного вывода аудио в целочисленные сэмплы); проверьте это на некоторых примерах.
Это хорошее предложение, однако я оговорюсь, что оно предполагает две вещи: - что у вас есть процессор x86 с SSE (> PII) или SSE2 (> PIII) - что вам действительно нужно усечение, а не округление, преобразование
Также обратите внимание на ограничение, что это, конечно, не будет вариантом для 80-битного значения с плавающей запятой.
Есть одна инструкция для преобразования числа с плавающей запятой в int в сборке: используйте инструкцию FISTP. Он извлекает значение из стека с плавающей запятой, преобразует его в целое число и затем сохраняет по указанному адресу. Я не думаю, что будет более быстрый способ (если вы не используете расширенные наборы инструкций, такие как MMX или SSE, с которыми я не знаком).
Другая инструкция, FIST, оставляет значение в стеке FP, но я не уверен, что она работает с адресатами размером в четыре слова.
Если вы действительно заботитесь о скорости этого, убедитесь, что ваш компилятор генерирует инструкцию FIST. В MSVC это можно сделать с помощью / QIfist, см. этот обзор MSDN
Вы также можете рассмотреть возможность использования встроенных функций SSE, чтобы сделать эту работу за вас, см. Эту статью от Intel: http://softwarecommunity.intel.com/articles/eng/2076.htm
Это зависит от того, хотите ли вы преобразование с усечением или с округлением и с какой точностью. По умолчанию C выполняет преобразование с усечением, когда вы переходите от float к int. Существуют инструкции FPU, которые делают это, но это не преобразование ANSI C, и есть серьезные предостережения при его использовании (например, знание состояния округления FPU). Поскольку ответ на вашу проблему довольно сложен и зависит от некоторых переменных, которые вы не указали, я рекомендую эту статью по этой проблеме:
Как правило, вы можете быть уверены, что компилятор будет эффективным и правильным. Обычно нет никакой пользы от сворачивания ваших собственных функций для того, что уже существует в компиляторе.
Вы просто неправы. В этом случае сворачивание собственного - это очень наглядное 10-кратное улучшение скорости по сравнению со встроенными функциями, потому что, когда вы делаете это самостоятельно, вы можете доверять состоянию флагов FPU, чего не делает встроенный _ftol, или вы можете сделать это распараллеливанием, используя SSE.
Или вы можете указать «-msse3» (gcc), и «фиксированный» FTSTTP сделает это правильно и без проблем.
Подпрограммы, предоставляемые компилятором, не очень подходят для мультимедийных приложений, где производительность имеет решающее значение.
В базе кода Lua для этого есть следующий фрагмент (проверьте src / luaconf.h с www.lua.org). Если вы найдете (SO найдет) более быстрый способ, я уверен, они будут в восторге.
О, 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
Обычно используемый трюк для простого кода x86 / x87 - заставить мантиссу части float представлять int. Далее следует 32-битная версия.
Аналогична 64-битная версия. Опубликованная выше версия Lua работает быстрее, но полагается на усечение двойного до 32-битного результата, поэтому требует, чтобы для единицы x87 была установлена двойная точность, и она не может быть адаптирована для двойного преобразования в 64-битное int.
Хорошая особенность этого кода заключается в том, что он полностью переносится для всех платформ, соответствующих IEEE 754, единственное допущение заключается в том, что для режима округления с плавающей запятой установлено значение ближайшего. Примечание: Portable в том смысле, что он компилируется и работает. Платформы, отличные от 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;
}
Для целых чисел без знака это может быть проще: inline uint32_t toInt (float fval) {static float const snapper = 1 << 23; fval + = окунь; возврат ((uint32_t) fval) & 0x007FFFFF; }
static float const snapper; делает это медленнее, чем необходимо. Просто напишите fval += 1<<23;
На x86 он не медленнее, так как сгенерированный код такой же. На x87 нет инструкций FPU, принимающих немедленные аргументы.
Если вы можете гарантировать, что ЦП, на котором запущен ваш код, совместим с SSE3 (даже Pentium 5, JBB), вы можете разрешить компилятору использовать его инструкцию FISTTP (например, -msse3 для gcc). Кажется, что так должно быть всегда:
Обратите внимание, что FISTTP отличается от FISTP (у которого есть свои проблемы, вызывающие медлительность). Он входит в состав SSE3, но на самом деле (единственное) усовершенствование на стороне X87.
В любом случае, другие процессоры, кроме X86, вероятно, отлично справились бы с преобразованием. :)
Поскольку 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
Это интересно и кажется правильным, но в моих тестах компилятор x64 фактически генерирует точно такой же код (проверенный с помощью дизассемблера) для вашего кода здесь и для примера MSDN.
Я предполагаю, что требуется усечение, как если бы вы записали 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 на другой режим округления.
re встроенная сборка: да Embarcadero (ранее Borland) поддерживает его (как компиляторы C++, так и Delphi поддерживают)
Переключитесь с Pentium 5 на чип, который правильно выполняет вычисления ... (Человек, который заставляет меня чувствовать себя старым ...)