Comments 22
Читал в руководстве от Intel по оптимизации с применением MMX, SSE и т.д. Там предлагается в первую очередь отказаться от чисел с плавающей точкой, если это возможно.
И сейчас предлагается. Только причина не в том, что floating point медленный, а в том, что расширения MMX и SSE используют регистры математического сопроцессора. То есть если программа использует и то, и другое, придется постоянно сохранять и восстанавливать регистры сопроцессора, а это много (больше 100 байт, если я правильно помню) и долго.
Странно что fixed point не поддерживается на уровне языка c/c++. Я когда-то писал программу для микроконтроллера в которого не было FPU. Конечно написать можно и так, но для каждой переменной и для каждой операции приходилось писать комментарий — сколько тут знаков до запятой и после:)
Бонусом то что FPU всегда требует дополнительной конвертации int в флоат и обратно и это не параллелится. Но для fixed SIMD обычная загрузка позволяет загружать до 4 значений X или W сразу и она паралеллится с самой вычисляющей SIMD FMA инструкций — итого по две инструкции за ОДИН такт, и обе из них векторные которые обрабатывают несколько пар.
Итог:
на FPU нейросеть на 1000 классов — не более одной FPS ровно,
на фиксед с SIMD — несколько FPS.
И это притом что помимо нейросетки необходимо захватывать сырой raw с камеры, дебаеринг, денойз делать, а потом результаты выводить в графический интерфейс с тачами и жестами, окнами и оверлеями в реалтайме. И это всё на одном ядре контроллера. Хотя скоро двуядерные кортекс М7 появятся.
для каждой переменной и для каждой операции приходилось писать комментарий — сколько тут знаков до запятой и после:)
В с++ же есть шаблоны и их параметризация целыми числами. Почему бы информацию о положении точки не сделать частью типа? Тогда после компиляции эта информация пропадёт и на производительность не повлияет, но зато во время компиляции будет возможность поймать часть ошибок.
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
typedef signed int __int32_t;
en.cppreference.com/w/cpp/header/cstdintsigned int
и DIGITS
стоило бы сделать параметрами шаблона.
2^8 = 1024???
fmul никто уже давно не использует, как и весь x87.
SSE и mulss.
При выполнении операций умножения и деления возможен случай переполнения…
Все гараздо страшнее — переполнение может возникнуть и при сложении/вычитании.
А вот умножение и деление кстати можно организовать так что переполнения не будет — через отбрасывание мл. части для умножения и через сдвиг делимого в старшую часть. Для деления правда будут ограничения — делимое должно быть меньше делителя.
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;
}
Арифметика fixed-point на C++