Каков самый быстрый способ конвертировать float в int на x86

Каков самый быстрый способ, с помощью которого вы конвертируете число с плавающей запятой в int на процессоре x86. Предпочтительно в C или сборке (которая может быть облицована в C) для любой комбинации следующего:

  • 32/64/80-бит float -> 32/64-битное целое число

Я ищу технику, которая быстрее, чем просто позволить компилятору это сделать.

Это зависит от того, хотите ли вы усекающее преобразование или округление, и с какой точностью. По умолчанию C будет выполнять усекающее преобразование, когда вы переходите от float к int. Есть инструкции FPU, которые это делают, но это не преобразование ANSI C, и есть существенные оговорки к его использованию (например, знание состояния округления FPU). Поскольку ответ на вашу проблему довольно сложный и зависит от некоторых переменных, которые вы не выражали, я рекомендую эту статью по этой проблеме:

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

Упакованное преобразование с использованием SSE является самым быстрым методом, поскольку вы можете преобразовать несколько значений в одну и ту же инструкцию. ffmpeg имеет много сборок для этого (в основном для преобразования декодированного вывода аудио в целые образцы); проверьте его для некоторых примеров.

Обычно используемый трюк для простого кода x86 / x87 состоит в том, чтобы заставить часть мантиссы поплавка представлять int. Далее следует 32-битная версия.

64-разрядная версия аналогична. Версия Lua, вышеперечисленная выше, выполняется быстрее, но полагается на усечение двойного на 32-битный результат, поэтому требуется, чтобы блок x87 был настроен на двойную точность и не может быть адаптирован для преобразования с двойным и 64-битным int.

Самое приятное в этом коде – он полностью переносится для всех платформ, соответствующих 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; } 

Если вы можете гарантировать, что процессор, работающий на вашем коде, совместим с 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

Существует одна команда для преобразования плавающей точки в int в сборке: используйте инструкцию FISTP. Он выдает значение из стека с плавающей запятой, преобразует его в целое, а затем сохраняет по указанному адресу. Я не думаю, что был бы более быстрый способ (если вы не используете расширенные наборы команд, такие как MMX или SSE, с которыми я не знаком).

Другая инструкция FIST оставляет значение в стеке FP, но я не уверен, что он работает с четырьмя словами.

База кода Lua имеет следующий fragment, чтобы сделать это (проверьте src / luaconf.h с сайта http://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 

Если вам действительно нужна скорость, убедитесь, что ваш компилятор генерирует инструкцию FIST. В MSVC вы можете сделать это с помощью / QIfist, см. Этот обзор MSDN

Вы также можете рассмотреть возможность использования SSE для выполнения этой работы, см. Эту статью от Intel: http://softwarecommunity.intel.com/articles/eng/2076.htm

Поскольку MS выводит нас из встроенной сборки в X64 и заставляет нас использовать встроенные функции, я посмотрел, что использовать. В документе MSDN приведен _mm_cvtsd_si64x .

Этот пример работает, но ужасно неэффективен, используя невыложенную нагрузку в 2 удвоения, где нам нужна только одна загрузка, поэтому избавляемся от дополнительного требования к выравниванию. Затем производится много ненужных нагрузок и перезарядов, но их можно устранить следующим образом:

  #include  #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 

Я предполагаю, что требуется усечение, как если бы i = (int)f записывал 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, где встроенная assembly может быть недоступна), вы можете использовать почти так же быстро:

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

Обратите внимание, что встроенная assembly работает только с компиляторами Microsoft Visual Studio (и, возможно, с Borland), ее нужно будет переписать на сборку GNU для компиляции с помощью gcc. Однако решение SSE2 с внутренним интерфейсом должно быть довольно портативным.

Другие режимы округления возможны с помощью различных свойств SSE2 или путем ручной установки управляющего слова FPU в другом режиме округления.

Как правило, вы можете доверять компилятору, чтобы быть эффективным и правильным. Как правило, ничего не получается, перекладывая свои собственные функции на то, что уже существует в компиляторе.

  • Получение фактической ширины элемента с плавающей запятой
  • Эмуляция «double» с использованием 2 "float" s
  • Почему GCC не оптимизирует a * a * a * a * a * a to (a * a * a) * (a * a * a)?
  • сравнение значений float / double с использованием оператора ==
  • Как отобразить двоичное представление float или double?
  • Float / double precision в режимах отладки / выпуска
  • Java двойное сравнение epsilon
  • Java: зачем вам нужно указывать 'f' в streamовом литерале?
  • Как использовать деление с плавающей запятой в bash?
  • Сделать поплавок показывать только два десятичных знака
  • Спецификатор ширины печати для поддержания точности значения с плавающей запятой
  • Interesting Posts

    Bash: ограничить количество одновременных заданий?

    Как предотвратить дублирование пользовательских свойств в Firebase?

    Обработка ошибок в математических функциях

    Преобразование магнитного поля X, Y, Z значений из устройства в глобальную систему отсчета

    NoClassDefFoundError после обновления IntelliJ IDEA

    Как включить PAE в Windows 7 (32-разрядная версия) для поддержки более 3,5 ГБ оперативной памяти?

    Повышение точности решения трансцендентного уравнения

    Включает ли Twitter-бутстрап jQuery?

    Как я сериализую объект и сохраняю его в файл на Android?

    Реферирование ссылочного типа – новая ссылочная переменная, созданная каждым циклом в цикле, если объявлена ​​в нем?

    Проблемы с локальной областью переменных. Как его решить?

    Полноэкранный режим в Android?

    Как сделать брекеты с клавиатурой Apple в Windows?

    Как переименовать некоторые файлы в соответствии с их меткой времени

    Самый быстрый способ удалить строки, которые нельзя захватить с помощью SpecialCells

    Давайте будем гением компьютера.