Часть кода на ядре ATmega, которая выполняет setup () и loop (), выглядит следующим образом:
#include <Arduino.h>
int main(void)
{
init();
#if defined(USBCON)
USBDevice.attach();
#endif
setup();
for (;;) {
loop();
if (serialEventRun) serialEventRun();
}
return 0;
}
Довольно просто, но есть издержки для serialEventRun (); там.
Давайте сравним два простых наброска:
void setup()
{
}
volatile uint8_t x;
void loop()
{
x = 1;
}
и
void setup()
{
}
volatile uint8_t x;
void loop()
{
while(true)
{
x = 1;
}
}
Значение x и volatile просто для гарантии того, что оно не оптимизировано.
В произведенном ASM вы получите разные результаты:
Вы можете видеть, что while (true) просто выполняет rjmp (относительный переход) назад для нескольких инструкций, тогда как loop () выполняет вычитание, сравнение и вызов. Это 4 инструкции против 1 инструкции.
Для генерации ASM, как описано выше, вам нужно использовать инструмент под названием avr-objdump. Это включено в avr-gcc. Местоположение меняется в зависимости от ОС, поэтому его проще всего искать по имени.
avr-objdump может работать с файлами .hex, но в них отсутствуют первоначальный источник и комментарии. Если вы только что создали код, у вас будет файл .elf, который содержит эти данные. Опять же, расположение этих файлов зависит от ОС - самый простой способ найти их - включить подробную компиляцию в настройках и посмотреть, где хранятся выходные файлы.
Запустите команду следующим образом:
avr-objdump -S output.elf> asm.txt
И проверить вывод в текстовом редакторе.
main.c
используемого в Arduino IDE. Однако это не означает, что библиотека HardwareSerial включена в ваш эскиз; на самом деле он не включается, если вы его не используете (вот почему он естьif (serialEventRun)
вmain()
функции. Если вы не используете библиотеку HardwareSerial, тоserialEventRun
он будет нулевым, а значит, и без вызова.Ответ Cybergibbons довольно хорошо описывает генерацию кода сборки и различия между этими двумя методами. Предполагается, что это будет дополнительным ответом, рассматривающим проблему с точки зрения практических различий, т. Е. Насколько сильно изменится любой подход с точки зрения времени выполнения .
Варианты кода
Я сделал анализ, включающий следующие варианты:
void loop()
(который указывается при компиляции)void loop()
(использование__attribute__ ((noinline))
)while(1)
(который оптимизируется)while(1)
(путем добавления__asm__ __volatile__("");
. Этоnop
инструкция, которая предотвращает оптимизацию цикла без дополнительных издержек дляvolatile
переменной)void loop()
с оптимизированнымwhile(1)
void loop()
с неоптимизированнымwhile(1)
Эскизы можно найти здесь .
эксперимент
Я запускал каждый из этих эскизов в течение 30 секунд, таким образом, накапливая 300 точек данных каждый . В
delay
каждом цикле было 100-миллисекундный вызов (без которого плохие вещи не происходили ).Результаты
Затем я вычислил среднее время выполнения каждого цикла, вычел 100 миллисекунд из каждого и затем нанес на график результаты.
http://raw2.github.com/AsheeshR/Arduino-Loop-Analysis/master/Figures/timeplot.png
Вывод
while(1)
цикл внутриvoid loop
быстрее, чем оптимизированный компиляторvoid loop
.avr-gcc
и используя свои собственные флаги оптимизации, а не полагаться на Arduino IDE, которая поможет вам в этом (если вам нужна оптимизация в микросекундах).ПРИМЕЧАНИЕ: фактические значения времени здесь не имеют значения, разница между ними есть. В ~ 90 микросекунд времени выполнения включает в себя вызов
Serial.println
,micros
иdelay
.ПРИМЕЧАНИЕ 2. Это было сделано с использованием IDE Arduino и флагов компилятора по умолчанию, которые он предоставляет.
ПРИМЕЧАНИЕ 3. Анализ (график и расчеты) был выполнен с использованием R.
источник