Предотвращение Вызовов на пол ()

Я работаю над частью кода, где я должен иметь дело с uvs (2D координаты текстуры), которые находятся не обязательно в от 0 до 1 диапазона. Как пример, иногда я буду получать UV с u компонентом, который является 1.2. Для обработки этого, я реализую обертывание, которое вызывает мозаичное размещение путем выполнения следующего:

u -= floor(u)
v -= floor(v)

Выполнение этого заставляет 1.2 становиться 0.2, который является желаемым результатом. Это также обрабатывает отрицательные случаи, такие как-0.4 становления 0.6.

Однако эти вызовы на пол являются довольно медленными. Я представил свою Intel использования приложения VTune, и я трачу огромное количество циклов, просто делающих эту операцию пола.

Сделав некоторое дополнительное чтение по проблеме, я придумал следующую функцию, которая немного быстрее, но все еще оставляет желать лучшего (я все еще подвергаюсь штрафам преобразования типов, и т.д.).

int inline fasterfloor( const float x ) { return x > 0 ? (int) x : (int) x - 1; }

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

Кто-либо знает какие-либо приемы для обработки этого вида сценария?

11
задан 28 February 2010 в 19:24
поделиться

6 ответов

Итак, вы хотите действительно быстрое преобразование типа float-> int? AFAIK преобразование int-> float выполняется быстро, но, по крайней мере, на MSVC ++ преобразование float-> int вызывает небольшую вспомогательную функцию ftol (), которая выполняет некоторые сложные вещи, чтобы гарантировать выполнение преобразования, соответствующего стандартам. Если вам не нужно такое строгое преобразование, вы можете взломать сборку, предполагая, что вы используете x86-совместимый процессор.

Вот функция для быстрого преобразования float в int, которая округляет в меньшую сторону, используя встроенный синтаксис сборки MSVC ++ (в любом случае это должно дать вам правильное представление):

inline int ftoi_fast(float f)
{
    int i;

    __asm
    {
        fld f
        fistp i
    }

    return i;
}

В 64-разрядной версии MSVC ++ вам понадобится внешний .asm файл, поскольку 64-разрядный компилятор отклоняет встроенную сборку. Эта функция в основном использует необработанные инструкции FPU x87 для загрузки с плавающей запятой (fld), а затем сохраняет float как целое число (fistp). (Примечание к предупреждению: вы можете изменить используемый здесь режим округления, напрямую настроив регистры на ЦП, но не делайте этого, вы сломаете много вещей, включая реализацию sin и cos в MSVC!)

Если вы можете предположить поддержку SSE на ЦП (или есть простой способ создать кодовый путь, поддерживающий SSE), вы также можете попробовать:

#include <emmintrin.h>

inline int ftoi_sse1(float f)
{
    return _mm_cvtt_ss2si(_mm_load_ss(&f));     // SSE1 instructions for float->int
}

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

Один из них должен охватывать дорогостоящий случай преобразования типа float в int, и любые преобразования int в float по-прежнему должны быть дешевыми. Извините за специфику Microsoft, но именно здесь я проделал аналогичную работу с производительностью и таким образом добился больших успехов.Если переносимость / другие компиляторы являются проблемой, вам придется поискать что-нибудь еще, но эти функции компилируются, возможно, в две инструкции, занимающие <5 тактов, в отличие от вспомогательной функции, которая требует 100+ тактов.

11
ответ дан 3 December 2019 в 03:04
поделиться

Если вы используете Visual C ++, проверьте параметр компилятора «Включить встроенные функции». Если он включен, это должно ускорить большинство математических функций (включая пол). Обратной стороной является то, что обработка крайних случаев (например, NaN) может быть неправильной, но для игры вас это может не волновать.

2
ответ дан 3 December 2019 в 03:04
поделиться

Если диапазон значений, которые могут возникнуть, достаточно мал, возможно, вы можете выполнить двоичный поиск минимального значения. Например, если могут иметь место значения -2 <= x <2 ...

if (u < 0.0)
{
  if (u < 1.0)
  {
    //  floor is 0
  }
  else
  {
    //  floor is 1
  }
}
else
{
  if (u < -1.0)
  {
    //  floor is -2
  }
  else
  {
    //  floor is -1
  }
}

Я не даю никаких гарантий по этому поводу - я не знаю, как эффективность сравнений сравнивается с полом - но, возможно, стоит попробовать.

1
ответ дан 3 December 2019 в 03:04
поделиться

Требуемая операция может быть выражена с помощью функции fmod (fmodf для чисел с плавающей запятой, а не для удвоений):

#include <math.h>
u = fmodf(u, 1.0f);

Достаточно велики шансы, что ваш компилятор сделает это наиболее эффективным способом.

С другой стороны, насколько вас беспокоит точность последнего бита? Можете ли вы установить нижнюю границу своих отрицательных значений, например, зная, что они никогда не ниже -16.0? Если это так, что-то вроде этого спасет вас от условного оператора, который, скорее всего, будет полезен, если его нельзя надежно спрогнозировать ветвление с вашими данными:

u = (u + 16.0);  // Does not affect fractional part aside from roundoff errors.
u -= (int)u;     // Recovers fractional part if positive.

(В этом отношении, в зависимости от того, как выглядят ваши данные и процессор, который вы используете, если большая часть из них имеет отрицательное значение, но очень небольшая часть меньше 16,0, вы можете обнаружить, что добавление 16,0f перед выполнением условного преобразования int дает вам ускорение, потому что оно делает ваше условное предсказание. ваш компилятор может делать это с чем-то другим, кроме условной ветки, и в этом случае это бесполезно; трудно сказать, не проверяя и не глядя на сгенерированную сборку.)

3
ответ дан 3 December 2019 в 03:04
поделиться

Еще одна глупая идея, которая может сработать, если диапазон небольшой ...

Извлеките экспоненту из числа с плавающей запятой с помощью побитовых операций, затем используйте поиск таблица, чтобы найти маску, которая стирает нежелательные частицы с мантиссы. Используйте это, чтобы найти пол (протрите биты ниже точки), чтобы избежать проблем с перенормировкой.

РЕДАКТИРОВАТЬ Я удалил это как «слишком глупо, плюс с проблемой + ve против -ve». Поскольку за него все равно проголосовали, он восстановлен, и я оставлю его другим решать, насколько это глупо.

2
ответ дан 3 December 2019 в 03:04
поделиться

Каков максимальный диапазон ввода ваших значений u, v? Если это довольно небольшой диапазон, например От -5,0 до +5,0, тогда будет быстрее многократно добавлять / вычитать 1,0 до тех пор, пока вы не попадете в диапазон, а не вызывать дорогостоящие функции, такие как floor.

0
ответ дан 3 December 2019 в 03:04
поделиться
Другие вопросы по тегам:

Похожие вопросы: