Главная > AVR > Почему float в контроллерах — плохо.

Почему float в контроллерах — плохо.

Иногда мне приходится видеть, как некоторые товарищи, особенно ардуинофилы, бодро используют тип float при вычислениях вроде «y=1+1» на контроллерах типа младших AVR, PIC, MSP430 или других, на которых FPU отродясь не было. Такой код вызывает сочувствие к человеку, его написавшему, ибо написать его может либо совсем зеленый неофит, либо закоренелый дилетант.

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

Вообще говоря, поводом к написанию этого поста послужило то, что сегодня я дописал свою реализацию алгоритма Гёрцеля в двух вариантах — с плавающей точкой и с фиксированной. Если у меня выдастся время, про сам алгоритм и про процесс написания еще будут статьи, ибо приключений в процессе было достаточно. Пока же те, кто не знают, что это за алгоритм, могут почитать про него в Википедии. Вкратце — это такой радикально упрощенный вид ДПФ, позволяющий вычислить значение всего одного выбранного частотного компонента. В силу своей простоты он пригоден для выполнения на мелких микроконтроллерах. Однако он, конечно, требует работы с вещественными числами.

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

1. Учитывалось только время работы основного алгоритма (путем замера в симуляторе времени выполнения функции в тактах), без вычисления используемого коэффициента.
2. Размер буфера данных для обоих версий составлял 80 байт.
3. Программы идентичны с точностью до названия функции и типов переменных. Интересующиеся могут поглядеть на листинги внизу статьи.

Итак, для версии с фиксированной точкой:

Размер скомпилированной программы: 628 байт.
Время работы алгоритма: 13516 тактов, 1,69 мс при тактовой частоте 8 МГц.

Для версии с плавающей точкой:

Размер скомпилированной программы: 3740 байт.
Время работы алгоритма: 166001 такт, 20,75 мс при тактовой частоте 8 МГц.

По размеру разница в шесть (!) раз, по скорости — в двенадцать раз! Именно столько теряют любители float в контроллерах. Практически это означает, что реализацию с фиксированной точкой можно использовать, например, в ATmega48, и еще останется больше трех килобайт для остального кода, а вот версия с плавающей точкой туда просто не уместится, для нее потребуется как минимум ATmega88, и то будет съедена почти половина памяти.

Листинги тестовых программ.

С фиксированной точкой:

#include <avr/io.h>
#include <stdint.h>

#include "goertzel.h"

#define FREQ_CONST        fix_fill(1,414)

volatile uint8_t buffer[BLOCK_SIZE];
volatile fixed result;

void main(void)
{
  result=0;

  result=GzFix_GetFreqMagnitude(buffer,FREQ_CONST);

  result=0;

  while (1);
}

fixed — мой самописный тип данных с фиксированной точкой. Из тридцати двух бит под дробную часть отдается десять.

fix_fill() — самописный макрос, формирующий число с фиксированной точкой из целой и дробной части.

С плавающей точкой:

#include <avr/io.h>
#include <stdint.h>

#include "goertzel.h"

#define FREQ_CONST        1.414

volatile uint8_t buffer[BLOCK_SIZE];
volatile float result;

void main(void)
{
  result=0;

  result=Gz_GetFreqMagnitude(buffer,FREQ_CONST);

  result=0;

  while (1);
}
Рубрики:AVR
  1. ReAl
    18/12/2012 в 12:44

    Нельзя ли подробнее? Параметры компиляции и линковки.
    А то мой опыт с float на AVR подсказывает, что на такой короткой программе, демонстрирующей только вычисление, разница в размере должна быть в диапазоне 2..3 раза, а не шесть. Ну а в реальных программах разница ещё меньше. В сравнении — float-арифметика (без коренй/синусов) добавляет к программе меньше, чем printf без float-форматов. В каком-то из технологических пультов, где во float статистика считалась, текстовые строки больше занимали :-), а время по сравнению с выводом на ЖКИ тоже не катастрофа.
    Да и время-то тоже не на порядок. У AVR и 32-битной арифметики родной нет.

    • YS
      19/12/2012 в 11:03

      Да, действительно, статья короче, чем ей стоило бы быть. Просто сейчас на меня навалилась куча всякой работы (да еще и сессия), и потому растекаться мысию по древу особо некогда — подготовка качественного материала требует усилий…

      В скором времени я надеюсь написать пространную статью про свою реализацию алгоритма, где и привести все исходники и тесты. Пока же могу выслать Вам свои исходники полностью, чтобы Вы смогли проверить все сами. Ну или указать мне, где я не прав.

      • 19/12/2012 в 17:32

        Мельком глянул. Что видно сразу по полученных исходниках:
        — Во float-варианте из энергии извлекается корень, в целочисленном этого нет, вот уже кусочек разницы.
        — В fixed макрос надо так
        #define fix_fill(wpart,fpart) (((int32_t)(wpart) << FIX_BITS) | (((int32_t)(fpart) << FIX_BITS) / DEC_DENOM))
        без приведения fix_fill(512,0) даст int константу 0, которая приведется к int32 (и местами пойдёт деление на 0). При правильном макросе несколько увеличится fixed-вариант.
        — Во float из исходников не выбрасывается GzFix_GetFreqMagnitude (тут пока неуверенно, ключи компиляции/линковки не смотрел).

        Но это всё брызги, ну станет float-вариант не в 6 раз длиннее, а в 5, всё равно не то. Хотя второй пункт с макросами может что-то сыграть с выбрасыванием напрочь из кода подпрограмм 32-битного целочисленного деления, это уже весомее.

        И у меня в исходниках нет
        #define FREQ_CONST fix_fill(1,414)
        так что может я немного не то смотрю.

        Кстати, какая версия avr-gcc использовалась? Вечером вернусь, запущу в виртуалке студию, пролинкую нужный каталог на C:\WinAVR и тогда уже и покомпилирую, и на все ключи гляну.

        • YS
          19/12/2012 в 19:49

          — поскольку энергия меряется в попугаях, достаточно подогнать выходные значения так, чтобы они влезали в диапазон, корень извлекать необязательно.

          — с макросом понял. Спасибо.

          — GzFix_GetFreqMagnitude просто закомментирована.

          — одна версия превращалась в другую редактированием непосредственно перед компиляцией.

          — версию gcc так не помню, надо смотреть.

          • ReAl
            20/12/2012 в 02:36

            Прошу прощения, в письме было указано WinAVR-20100110, просто я не обратил внимания. Им же и работал. Исходники модифицировал, теперь всё переключается при помощи USE_FLOATING_POINT, которая задаётся в проекте AVRStudio в конфигурации float. Сверяю размер
            fixed, not patched macro 628
            float, sqrt 3740

            Теперь убираем из float корень, всё равно для сравнивания амплитуд на больше-меньше он не нужен:
            fixed, patched macro 682
            float, no sqrt 3490

            И в этой точке не заглядывая в ключи уже вспоминаю, в чём дело.
            Дело в том, что в libgcc, идущей в комплекте с avr-gcc, поддержка плавающей точки выписана… короче, никак не выписана. Там стоят универсальные заглушки soft-float, написанные на С и единые для всех систем (для которых это не заменено в порте / поддержке). Поэтому при работе с float нужно подключать библиотеку libm из avr-libc. Без неё время даже не смотрел, нет смысла. Размеры с libm:

            float, sqrt 1510
            float, no sqrt 1334
            А теперь, внимание, заменяем в плавающих вычислениях деление на 255.0 умножением на (1.0/255.0)
            float, *(1.0/256.0) 1126

            Времена для fixed с некорректированным макросом и для float на пустом (нулевом) массиве сверять почти не имеет смысла.
            Оптимизированная float-библиотека проверяет многое на 0 в самом начале. Время в циклах:
            fixed, not patched macro 13516
            float, no sqrt 23380

            А вот по честному, с пилой в буфере данных и с исправленным макро вышло нос в нос:
            fixed, patched macro 74822
            float, no sqrt 70279
            По сравнению с обычной целочисленной арифметикой в fixed добавляются сдвиги, а float-библиотека в avr-libc вылизана на асме.
            Ну и герой дня — вариант float без деления. На треть быстрее fixed-варианта с делением (таки деление, что float, xnj int32 — больная тема для AVR).
            float, *(1.0/256.0) 44053

            Но уже fixed-вариант взывает к справедливой оценке. Вместо fix_div на 512 делаем fix_mul на 1/512, которая влазит при FIX_BITS==10 просто чудесно. Уходим от деления.
            Размер / время в циклах
            fixed, (1<<FIX_BITS)/512 542 / 14212
            float, *(1.0/256.0) 1126 / 44053
            Теперь моя душенька довольна, float проигрывает приблизительно в два раза по размеру и в три по скорости 🙂

            Конечно, это предварительные результаты, окончательно можно сказать только после прогона на данных, для которых известен (посчитан на персоналке) результат.
            Надеюсь, я не привнёс своих ошибок.

            Кстати, в avr-gcc толи 4.7, толи 4.8 появилась поддержка int24_t, для fixed-point при аккуратной работе с диапазонами может быть интересно. А также где-то там появилась поддержка _Fixed, но там нельзя выбирать положение точки, может оказаться ненеинтересно.

            • YS
              20/12/2012 в 16:30

              Круто, спасибо, я так далеко не залезал. 🙂 Тем не менее, мое утверждение не теряет смысла — если я не дошел до подключения нестандартной библиотеки с плавающей точкой, то ардуинофанат тем более вряд ли до этого дойдет. 🙂 Так что общий совет начинающим, тем не менее, — «float — плохо».

              Получил Ваше письмо, на досуге поразбираю подробно.

              • ReAl
                20/12/2012 в 17:56

                Так я и не спорю с тем, что «с PC-шного дуру пихать везде float это плохо».
                Но совет я бы переформулировал в «не знаешь точно зачем оно тебе нужно — не лезь».

                А ардуины не зря быстро от mega8 до армов дошли, с их подходами всегда будет мало места. Но мне кажется, если так подходить к задаче, на армах уже .net micro framework будет и не хуже по поедаемым ресурсам, и удобнее для работы.

  1. No trackbacks yet.

Ответить на YS Отменить ответ