Реальная самая быстрая реализация для большого массива на современных процессорах x86 будет
- изменить режим округления MXCSR FP на округление в направлении -Infinity (он же
floor
) . В C это должно быть возможно с fenv
stuff или _mm_getcsr
/ _mm_setcsr
.
зацикливает массив, делая _mm_cvtps_epi32
для векторов SIMD, преобразуя 4 float
с в 32-разрядное целое число, используя текущий режим округления. (И сохраняя векторы результатов к месту назначения.)
cvtps2dq xmm0, [rdi]
- это одиночный микроплавкий переход на любом процессоре Intel или AMD начиная с K10 или Core 2. (https://agner.org/optimize/) То же самое для 256-битной версии AVX с векторами YMM .
- восстановить текущий режим округления до нормального режима IEEE по умолчанию, используя исходное значение MXCSR. (округление до ближайшего, даже с разрывом связи)
Это позволяет загружать + преобразовывать + сохранять 1 вектор SIMD результатов за такт, так же быстро, как и с усечением . (SSE2 имеет специальную инструкцию преобразования FP-> int для усечения, именно потому, что это очень часто требуется компиляторам C. В старые добрые времена с x87 даже (int)x
требовало изменить режим округления x87 на усечение, а затем обратно. cvttps2dq
для упакованного числа с плавающей запятой-> int с усечением (обратите внимание на дополнительные t
в мнемоническом выражении). Или для скалярного перехода от XMM к целочисленным регистрам, cvttss2si
или cvttsd2si
для скаляра double
до скалярного целого числа.
При некотором развертывании цикла и / или хорошей оптимизации это должно быть возможно без узких мест на внешнем интерфейсе, только пропускная способность хранилища 1 раз в час, при условии отсутствия узких мест в кэше. (И на Intel до Skylake, также узкое место с пропускной способностью пакетного преобразования 1 на такт.), То есть 16, 32 или 64 байта за цикл, используя SSE2, AVX или AVX512.
Без изменения текущего режима округления необходимо SSE4.1 roundps
, чтобы округлить float
до ближайшего целого числа float
, используя выбранные вами режимы округления. Или вы можете использовать один из трюков, показанных в других ответах, которые работают для чисел с плавающей запятой с достаточно малой величиной, чтобы поместиться в 32-разрядное целое число со знаком, так как это ваш конечный формат назначения в любом случае.
(С правильными опциями компилятора, такими как -fno-math-errno
и правильными опциями -march
или -msse4
, компиляторы могут встраивать floor
, используя roundps
, или скаляр и / или эквивалент двойной точности, например roundsd xmm1, xmm0, 1
, но это стоит 2 мопа и имеет пропускную способность 1 на 2 такта на Haswell для скаляров или векторов. На самом деле, gcc8.2 встроит roundsd
для floor
даже без каких-либо опций быстрой математики, , как вы можно увидеть в проводнике компилятора Godbolt . Но это с -march=haswell
. К сожалению, это не базовый уровень для x86-64, поэтому его нужно включить, если ваш компьютер его поддерживает.)