Как рассчитать размер процедур Arduino?
Я хочу разработать библиотеку для Arduino Uno/Nano (альтернативу float, которая потребляет меньше оперативной памяти), которая после компиляции занимает минимум места в коде. Есть ли (не слишком сложный) способ рассчитать или оценить размер скомпилированного кода, чтобы можно было проверить, как изменение кода влияет на размер каждого метода в библиотеке?
Например, я могу вычислить sin(x) и cos(x), используя отдельные многочлены или один и тот же многочлен от PI/2-x.
Существуют ли инструменты проверки кода, которые это делают?
@Christophe, 👍0
Обсуждение2 ответа
Библиотеки Arduino обычно поставляются с набором примеров скетчей. которые показывают различные способы использования библиотеки. Вы можете просто скомпилировать все эти скетчи и посмотрите, как меняются размеры, когда вы вносите изменения в библиотека.
Существуют ли инструменты проверки кода, которые это делают?
У команды Arduino есть действие на GitHub под названием report-size-deltas, которое делает именно это: когда вы открываете запрос на извлечение в одном из их библиотеки, он создает примеры, а затем публикует отчет о размере Изменения на странице запроса на извлечение. Вот пример одного из таких изменений. отчёт. А вот рабочий процесс, который запустил действие.
Возможно, вы сможете использовать это напрямую или в качестве вдохновения.
Спасибо, Эдгар. Я сделал несколько примеров и могу сделать больше. Путь действий GitHub действительно выглядит интересно. Есть ли где-нибудь руководство, объясняющее, как добавить действия и рабочие процессы GitHub в мой репозиторий библиотеки? Похоже, это довольно сложно (YML, немного Python, расписания и т. д.)?, @Christophe
@Christophe: На GitHub есть документация по этому вопросу. Я не знаю ничего лучше, но вы можете поискать руководства в интернете., @Edgar Bonet
Получить размеры функций
(Спасибо, Эдгар Бонет, за ваш ценный комментарий!) Вы можете использовать двоичную утилиту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
- Нужно ли уменьшать размер библиотек?
- Float печатается только 2 десятичных знака после запятой
- Отправка и получение различных типов данных через I2C в Arduino
- Как получить исходные файлы для библиотек Arduino?
- Ошибка: "недопустимое использование нестатической функции-члена" при вызове функции из моего собственного класса-метода
- Как подключить Wi-Fi Shield ESP-12E-ESP8266-UART-WIFI-Wireless-Shield к Arduino
- Существуют ли библиотеки сглаживания сигналов для Arduino?
- Wire.h не найден!
Это действительно про Arduino?, @Rohit Gupta
Конечно. Мне нужно знать, сколько байт кода требуется каждой процедуре, чтобы минимизировать её размер., @Christophe
Я не согласен с теми, кто закрыл этот вопрос. Автор вопроса спрашивает, как измерить размер кода на Arduino. В этом нет ничего плохого., @Nick Gammon
Какие значения вас на самом деле интересуют? Размер кода во флеш-памяти, размер статических данных в оперативной памяти, размер динамических данных в стеке и куче. Рассматривали ли вы возможность использования бинарных утилит используемого GCC?, @the busybee
@NickGammon, мне кажется, что там тоже задают много вопросов и просят высказать своё мнение. По-моему, стоило бы оставить тему закрытой., @Rohit Gupta
Как автор вопроса, я хотел бы знать, стоило ли мне задавать другие вопросы? Я прошу о «простых» инструментах, и это может быть субъективным, это противоречит правилам. Не думаю. Или вы имеете в виду только комментарий выше? Я удалил фрагменты и комментарий, о котором идёт речь. Так лучше?, @Christophe
@RohitGupta Вопрос, который я вынес из его поста, был: «Есть ли способ рассчитать или оценить размер скомпилированного кода?». Это разумный вопрос, и я не вижу в нём просьбы о чьём-либо мнении, кроме как «Что вы думаете?» (так как этот вопрос был отредактирован). Думаю, мы можем проигнорировать вопрос «Что вы думаете?» и просто ответить на вопрос, не затрагивая новых авторов с близким перевесом голосов. Два человека дали разумные ответы, так что вопрос не может быть слишком плохим. Предлагаю нам быть немного снисходительнее к новым участникам., @Nick Gammon