Как рассчитать размер процедур Arduino?

Я хочу разработать библиотеку для Arduino Uno/Nano (альтернативу float, которая потребляет меньше оперативной памяти), которая после компиляции занимает минимум места в коде. Есть ли (не слишком сложный) способ рассчитать или оценить размер скомпилированного кода, чтобы можно было проверить, как изменение кода влияет на размер каждого метода в библиотеке?

Например, я могу вычислить sin(x) и cos(x), используя отдельные многочлены или один и тот же многочлен от PI/2-x.

Существуют ли инструменты проверки кода, которые это делают?

, 👍0

Обсуждение

Это действительно про Arduino?, @Rohit Gupta

Конечно. Мне нужно знать, сколько байт кода требуется каждой процедуре, чтобы минимизировать её размер., @Christophe

Я не согласен с теми, кто закрыл этот вопрос. Автор вопроса спрашивает, как измерить размер кода на Arduino. В этом нет ничего плохого., @Nick Gammon

Какие значения вас на самом деле интересуют? Размер кода во флеш-памяти, размер статических данных в оперативной памяти, размер динамических данных в стеке и куче. Рассматривали ли вы возможность использования бинарных утилит используемого GCC?, @the busybee

@NickGammon, мне кажется, что там тоже задают много вопросов и просят высказать своё мнение. По-моему, стоило бы оставить тему закрытой., @Rohit Gupta

Как автор вопроса, я хотел бы знать, стоило ли мне задавать другие вопросы? Я прошу о «простых» инструментах, и это может быть субъективным, это противоречит правилам. Не думаю. Или вы имеете в виду только комментарий выше? Я удалил фрагменты и комментарий, о котором идёт речь. Так лучше?, @Christophe

@RohitGupta Вопрос, который я вынес из его поста, был: «Есть ли способ рассчитать или оценить размер скомпилированного кода?». Это разумный вопрос, и я не вижу в нём просьбы о чьём-либо мнении, кроме как «Что вы думаете?» (так как этот вопрос был отредактирован). Думаю, мы можем проигнорировать вопрос «Что вы думаете?» и просто ответить на вопрос, не затрагивая новых авторов с близким перевесом голосов. Два человека дали разумные ответы, так что вопрос не может быть слишком плохим. Предлагаю нам быть немного снисходительнее к новым участникам., @Nick Gammon


2 ответа


2

Библиотеки Arduino обычно поставляются с набором примеров скетчей. которые показывают различные способы использования библиотеки. Вы можете просто скомпилировать все эти скетчи и посмотрите, как меняются размеры, когда вы вносите изменения в библиотека.

Существуют ли инструменты проверки кода, которые это делают?

У команды Arduino есть действие на GitHub под названием report-size-deltas, которое делает именно это: когда вы открываете запрос на извлечение в одном из их библиотеки, он создает примеры, а затем публикует отчет о размере Изменения на странице запроса на извлечение. Вот пример одного из таких изменений. отчёт. А вот рабочий процесс, который запустил действие.

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

,

Спасибо, Эдгар. Я сделал несколько примеров и могу сделать больше. Путь действий GitHub действительно выглядит интересно. Есть ли где-нибудь руководство, объясняющее, как добавить действия и рабочие процессы GitHub в мой репозиторий библиотеки? Похоже, это довольно сложно (YML, немного Python, расписания и т. д.)?, @Christophe

@Christophe: На GitHub есть документация по этому вопросу. Я не знаю ничего лучше, но вы можете поискать руководства в интернете., @Edgar Bonet


2

Получить размеры функций

(Спасибо, Эдгар Бонет, за ваш ценный комментарий!) Вы можете использовать двоичную утилиту1 «nm», входящую в комплект поставки Arduino. Путь к ней зависит от вашей операционной системы. Например, в моей системе (производной от Arch Linux) их две. Это довольно распространённая практика для реализации различных сценариев использования.

~/.arduino15/packages/arduino/tools/avr-gcc/7.3.0-atmel3.6.1-arduino7/bin/avr-nm

и

~/.arduino15/packages/arduino/tools/avr-gcc/7.3.0-atmel3.6.1-arduino7/avr/bin/nm

Он печатает имена (отсюда и название) в таблице символов и их атрибуты.

В качестве альтернативы вы можете использовать бинарную утилиту1 «objdump», которую вы найдёте в тех же местах. Они есть в моей системе.

~/.arduino15/packages/arduino/tools/avr-gcc/7.3.0-atmel3.6.1-arduino7/bin/avr-objdump

и

~/.arduino15/packages/arduino/tools/avr-gcc/7.3.0-atmel3.6.1-arduino7/avr/bin/objdump

В Windows ознакомьтесь с документацией, чтобы найти инструменты.

Экспортируйте пример скетча, использующего вашу библиотеку, и найдите ELF-файл. Обычно он находится в каталоге рядом с исходным файлом скетча. Затем вызовите один из инструментов с ELF-файлом, чтобы распечатать все символы и при необходимости отфильтровать их.

1 Пакет GNU называется «binutils».

Пример

Я составил простой пример наброска для Uno. Функция delay() должна вызываться более одного раза, иначе она будет вставлена в место вызова. «Встроенная» означает, что код функции вставляется вместо вызова. В этом случае отдельной функции нет.

В ответ на ваш комментарий я также использовал операторы <= и += для типов float. Обратите внимание, что функции для операторов и подобных им функций можно найти только в том случае, если компилятор их использует. Как мы видим, функции используются для операторов с аргументами float, но не для short. Кстати, вам нужно знать названия таких функций, которые вы найдете в документации или листингах.

float f1;
float f2;

void setup() {
  delay(100);
}

void loop() {
  delay(1000);
  if (f1 <= f2) {
    f1 += f2;
  }
}

Экспорт скомпилированного двоичного файла обязателен для получения ELF-файла исполняемого файла.

В листинге указаны имена функций. Кстати, функции Arduino setup() и loop(), по-видимому, встроены, поскольку вызываются только один раз. Для генерации листинга используйте objdump:

<путь-к>objdump -d <sketch>.ino.elf > <имя-файла-списка>

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

000001d2 <main>:
  :
  :
 260:   0e 94 6d 00     call    0xda    ; 0xda <delay>
  :
  :
 270:   0e 94 6d 00     call    0xda    ; 0xda <delay>
  :
  :
 28e:   0e 94 cc 01     call    0x398   ; 0x398 <__cmpsf2>
  :
  :
 2a0:   0e 94 60 01     call    0x2c0   ; 0x2c0 <__addsf3>
  :
  :
 2bc:   d5 cf           rjmp    .-86        ; 0x268 <main+0x96>

Вызов nm с некоторыми параметрами выводит список символов, отсортированных по размеру. Я отфильтровал вывод по интересующим меня именам.

<путь-к>nm -CSrtd --size-sort <sketch>.ino.elf | grep -E '(delay|__cmpsf2|__addsf3)'

Выход:

00000750 00000204 T __addsf3x
00000218 00000100 t delay
00000920 00000010 T __cmpsf2

Поля во втором столбце показывают размеры функций. (Интересно, что он на 2 байта короче, чем в первой версии моего ответа. Тогда я использовал случайный старый скетч, скомпилированный до обновления Arduino IDE, которое, по-видимому, принесло более новую версию компилятора или библиотеки.)

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


Я также попробовал objdump для просмотра символов и отфильтровал его вывод. Вы также можете просто перенаправить вывод в файл и использовать свой любимый текстовый редактор для его изучения.

<путь-к>objdump -t <sketch>.ino.elf | grep -E '(delay|__cmpsf2|__addsf3)'

Выход:

000000da l     F .text  00000064 delay
00000398 g     F .text  0000000a __cmpsf2
000002ee g     F .text  000000cc __addsf3x
000002c0 g       .text  00000000 __addsf3

И снова, поля во втором столбце показывают размеры функций (0x64 = 100 байт для delay()). Согласно тексту справки, objdump не может выводить числа в десятичном формате.

Это еще не все

Однако здесь есть подводный камень. Функции и методы часто вызывают другие функции или методы. Кроме того, компоненты библиотек часто оптимизированы таким образом, что создают несколько путей входа в общее тело функции. Вы не сможете получить их правильно, не изучив сгенерированный код.

Самый правильный способ — сформировать листинг примера скетча.

Теперь вы можете просмотреть листинг и суммировать весь код, необходимый для интересующей вас части. Например, delay() вызывает micros() (фрагмент листинга):

00000090 <micros>:
  :
  :
  d6:   d1 f7           brne    .-12        ; 0xcc <micros+0x3c>
  d8:   08 95           ret

000000da <delay>:
  :
  :
  ee:   0e 94 48 00     call    0x90    ; 0x90 <micros>
  :
  :
  f6:   0e 94 48 00     call    0x90    ; 0x90 <micros>
  :
  :
 10a:   a8 f3           brcs    .-22        ; 0xf6 <delay+0x1c>
  :
  :
 12a:   29 f7           brne    .-54        ; 0xf6 <delay+0x1c>
  :
  :
 13c:   08 95           ret

Размер можно рассчитать, вычитая начальные адреса из конечных. Не забудьте последнюю инструкцию, здесь 2 байта! В этом примере:

  • micros() размер = 0xd8 - 0x90 + 2 = 0x4a = 74 десятичное.
  • delay() размер = 0x13c - 0xda + 2 = 0x64 = 100 в десятичной системе счисления.

Таким образом, использование delay() добавит 174 байта к вашему скетчу.

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

,

Значит, функция задержки из библиотеки Arduino занимает 102 байта? Отлично! Работает ли это также с операторами типа <= или +=? И с перегруженными функциями? Знаете ли вы, как это работает в Windows?, @Christophe

Отличный ответ! Два небольших замечания: 1. Чтобы предотвратить встраивание функции, можно квалифицировать её с помощью __attribute__((noinline)). 2. Чтобы получить размеры всех функций, используйте avr-nm -CSrtd --size-sort: это вернёт список удалённых символов с размерами в десятичном формате, отсортированными от большего к меньшему., @Edgar Bonet

@EdgarBonet Кому 1: Да, конечно. Однако delay() объявлен в системных заголовках, которые лучше оставить в покое. Обычно мы _предпочитаем_ встраивание для повышения производительности. :-D Кому 2: Хорошее замечание, спасибо! Добавлю, когда будет больше времени., @the busybee