Как стать автором
Обновить

Комментарии 22

Проблема производительности FP уже не актульна лет 20, сейчас FP-вычисления могут быть даже быстрее целочисленных (это на x86, не знаю как в других). А вот в плане проблем с унификацией результата вычислений это может помочь (если обычный FP может давать разные результаты на разных платформах, то проэмулированый даст один и тот же).
Проблема мега актуальна для микроконтроллеров, DSP и FPGA, где FPU нет, либо оно медленное, либо затратное по аппаратным ресурсам. Кроме того, еще лет 10 назад операции с плавающей точкой даже на x86 были медленнее, чем целочисленные. Читал в руководстве от Intel по оптимизации с применением MMX, SSE и т.д. Там предлагается в первую очередь отказаться от чисел с плавающей точкой, если это возможно.
Медленнее максимум в несколько раз, и по-этому бороться с ними эмулятором не получится — разве что полным отказом от FP, как Вы и сказали.
Читал в руководстве от Intel по оптимизации с применением MMX, SSE и т.д. Там предлагается в первую очередь отказаться от чисел с плавающей точкой, если это возможно.

И сейчас предлагается. Только причина не в том, что floating point медленный, а в том, что расширения MMX и SSE используют регистры математического сопроцессора. То есть если программа использует и то, и другое, придется постоянно сохранять и восстанавливать регистры сопроцессора, а это много (больше 100 байт, если я правильно помню) и долго.

Странно что fixed point не поддерживается на уровне языка c/c++. Я когда-то писал программу для микроконтроллера в которого не было FPU. Конечно написать можно и так, но для каждой переменной и для каждой операции приходилось писать комментарий — сколько тут знаков до запятой и после:)

Да даже если есть в контроллере FPU, то часто бывает супер скалярность и SIMD инструкции когда сразу вычисляется умножение нескольких 16 битных пар X и W с накоплением в аккумулятор 32 битный. Я поступал так: просто завёл два типа 16 и 32 бита int внутри и сделал так чтоб они между собой были не совместимы с ошибками компиляции. И набор функций конвертации — просто сдвиги с явным привидением типа.
Бонусом то что FPU всегда требует дополнительной конвертации int в флоат и обратно и это не параллелится. Но для fixed SIMD обычная загрузка позволяет загружать до 4 значений X или W сразу и она паралеллится с самой вычисляющей SIMD FMA инструкций — итого по две инструкции за ОДИН такт, и обе из них векторные которые обрабатывают несколько пар.

Итог:
на FPU нейросеть на 1000 классов — не более одной FPS ровно,
на фиксед с SIMD — несколько FPS.
И это притом что помимо нейросетки необходимо захватывать сырой raw с камеры, дебаеринг, денойз делать, а потом результаты выводить в графический интерфейс с тачами и жестами, окнами и оверлеями в реалтайме. И это всё на одном ядре контроллера. Хотя скоро двуядерные кортекс М7 появятся.
Работают, из недавних есть P0037R6

уже два года прошло, есть подвижки?


yruslan

для каждой переменной и для каждой операции приходилось писать комментарий — сколько тут знаков до запятой и после:)

В с++ же есть шаблоны и их параметризация целыми числами. Почему бы информацию о положении точки не сделать частью типа? Тогда после компиляции эта информация пропадёт и на производительность не повлияет, но зато во время компиляции будет возможность поймать часть ошибок.

Я пришёл к такой системе:

struct 
{
        uint16_t Value;
        uint8_t FixedPoint;
 }fPointVal;

fPointVal Temperature;

Temperature.Value=366;
Temperature.Point=1;

LCD_print_fixed(0, 0, Temperature.Value, Temperature.Point); //36.6



Добавьте бит знака, сделайте Value 24-битным, FixedPoint — 7-битным и показывающим положение двоичной точки и вы получите IEEE-754 Single-Precision Floating Point

Спасибо, автор. Тема актуальная.

signed int и DIGITS стоило бы сделать параметрами шаблона.

Интуитивно кажется, что лучше операцию умножения всегда выполнять по ветке с защитой от переполнения, чем при каждом умножении делать дополнительное деление с возможным пересчётом результата.
Ещё дополню: операцию возведения в квадрат можно сделать быстрее, чем умножение числа на само себя (три умножения вместо четырёх), а операцию деления можно сделать точнее за счёт более точного вычисления выражения (i*DIGITS)/b.x (по указанной в статье формуле 1 / 3 будет вычислено равным нулю).

fmul никто уже давно не использует, как и весь x87.
SSE и mulss.

При выполнении операций умножения и деления возможен случай переполнения…

Все гараздо страшнее — переполнение может возникнуть и при сложении/вычитании.
А вот умножение и деление кстати можно организовать так что переполнения не будет — через отбрасывание мл. части для умножения и через сдвиг делимого в старшую часть. Для деления правда будут ограничения — делимое должно быть меньше делителя.

12 лет назад писал библиотеку FixedPoint для embedded-камня без FPU. Надо поковыряться, поискать, где она у меня. Была поддержка прямой и обратной тригонометрии и несколько других приятных плюшек.
typedef signed int __int32_t;

Зачем изобретать велосипед? Есть же int32_t.

Востребовано это в лучшем случае для ARM, а то и вообще контроллеров каких нибудь. А тут нас поджидают проблемы. Компилятор не так чтобы круто оптимизирует. Когда я 15 лет назад писал софтверную имплементацию OpenGL ES для ARM телефонов то вообще отказался от С++ и писал на голом C и макросах — это оберегало от проблем компилятора. Например тогда он не умел работать с такими классами как у вас и не сводил это к регистру а реально создавал обьект в памяти каждый раз. Кроме того у вас всякие деления на 2, умножения и деления на степень двойки — это не факт что будет оптимизировано, поэтому все это надо делать через сдвиг конечно. Я думаю что компиляторы под всякие контроллеры тоже не так чтобы будут все это оптимизировать а поэтому лучше сразу все делать как надо. И кстати у меня были отдельно умножение с проверкой на переполнение, без и без перевода в 64 бита — короче несколько вариантов которые применялись в зависимости от места — во многих местах мы точно знаем диапазон чисел и можем ускорить операции.


В завершение приведу функцию вычисления корня — тогда это был один из самых быстрых алгоритмов. Можете сравнить с вашей реализацией.


/ signed int — 32 bit 16.16 fixed point
signed int LGE_Sqrt( signed int a)
{
register signed int root = 0;
register signed int bitSqrt;
register signed int Val = a;
register signed int s;


if (Val < 0x10000) {
    bitSqrt = 0x4000;
}
else {
    if (Val < 0x1000000) {
        bitSqrt = 0x400000;
    }
    else {
        bitSqrt = 0x40000000;
    }
}
while (bitSqrt) {
    s = bitSqrt + root;
    if (Val > s) {
        Val -= s;
        root = (root >> 1) | bitSqrt;
    }
    else {
        root = (root >> 1);
    }
    bitSqrt = bitSqrt >> 2;
}
return (root << 8) | 0xff;

}

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Изменить настройки темы

Истории