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

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

  • 32/64/80-битное число с плавающей запятой -> 32/64-битное целое число

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

Переключитесь с Pentium 5 на чип, который правильно выполняет вычисления ... (Человек, который заставляет меня чувствовать себя старым ...)

JBB 17.09.2008 04:21

Я катаюсь по земле. Черт возьми, как же плохо, что тебя понизили для этого!

Kevin 17.09.2008 21:29

:) А есть ли вообще Pentium 5? И если есть, извините, у него есть SSE3, и поэтому все в порядке. При разумном использовании (см. Комментарии к SSE3 и FISTTP).

akauppi 15.03.2009 14:36
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
25
3
24 891
10
Перейти к ответу Данный вопрос помечен как решенный

Ответы 10

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

Это хорошее предложение, однако я оговорюсь, что оно предполагает две вещи: - что у вас есть процессор x86 с SSE (> PII) или SSE2 (> PIII) - что вам действительно нужно усечение, а не округление, преобразование

Zach Burlingame 17.09.2008 04:40

Также обратите внимание на ограничение, что это, конечно, не будет вариантом для 80-битного значения с плавающей запятой.

PhiS 20.07.2013 21:53

Есть одна инструкция для преобразования числа с плавающей запятой в 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). Поскольку ответ на вашу проблему довольно сложен и зависит от некоторых переменных, которые вы не указали, я рекомендую эту статью по этой проблеме:

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

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

Вы просто неправы. В этом случае сворачивание собственного - это очень наглядное 10-кратное улучшение скорости по сравнению со встроенными функциями, потому что, когда вы делаете это самостоятельно, вы можете доверять состоянию флагов FPU, чего не делает встроенный _ftol, или вы можете сделать это распараллеливанием, используя SSE.

Don Neufeld 17.09.2008 04:49

Или вы можете указать «-msse3» (gcc), и «фиксированный» FTSTTP сделает это правильно и без проблем.

akauppi 15.03.2009 14:28

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

Nick Dowell 07.04.2011 18:37

В базе кода 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; }

chmike 01.05.2009 11:42

static float const snapper; делает это медленнее, чем необходимо. Просто напишите fval += 1<<23;

R.. GitHub STOP HELPING ICE 25.11.2010 06:05

На x86 он не медленнее, так как сгенерированный код такой же. На x87 нет инструкций FPU, принимающих немедленные аргументы.

Suma 25.11.2010 16:56

Если вы можете гарантировать, что ЦП, на котором запущен ваш код, совместим с 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

Поскольку 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.

Cody Gray 20.07.2013 11:26

Я предполагаю, что требуется усечение, как если бы вы записали 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 поддерживают)

PhiS 26.02.2014 21:56

Другие вопросы по теме