Будет ли бесконечный цикл внутри loop() работать быстрее?
Когда вы пишете типичный скетч, вы обычно полагаетесь на повторный вызов loop()
, пока работает Arduino. Однако вход и выход из функции loop()
должен привести к небольшим накладным расходам.
Чтобы избежать этого, вы могли бы создать свой собственный бесконечный цикл, например:
void loop()
{
while (true)
{
// делаем что-то...
}
}
Это действенный способ повысить производительность? Не вызовет ли это других проблем, если loop()
никогда не вернется?
@Peter Bloomfield, 👍21
2 ответа
Лучший ответ:
Часть кода ядра 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
И проверьте вывод в текстовом редакторе.
Ответ 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/Рисунки/timeplot.png
Заключение
- Неоптимизированный цикл
while(1)
внутриvoid loop
работает быстрее, чем оптимизированный компиляторомvoid loop
. - Разница во времени между неоптимизированным кодом и кодом, оптимизированным для Arduino по умолчанию, незначительна практически. Вам будет лучше компилировать вручную с помощью
avr-gcc
и использовать свои собственные флаги оптимизации, а не полагаться на помощь Arduino IDE (если вам нужна микросекундная оптимизация).
ПРИМЕЧАНИЕ: Фактические значения времени здесь не имеют значения, важна разница между ними. Время выполнения ~90 микросекунд включает вызов Serial.println
, micros
и delay
.
ПРИМЕЧАНИЕ 2: это было сделано с помощью среды разработки Arduino IDE и флагов компилятора по умолчанию, которые она предоставляет.
ПРИМЕЧАНИЕ 3: анализ (график и расчеты) был выполнен с использованием R.
Хорошая работа. График имеет миллисекунды, а не микросекунды, но это не большая проблема., @Cybergibbons
@Cybergibbons Это маловероятно, так как все измерения в микросекундах, и я нигде не менял масштабы :), @asheeshr
- Будет ли .ino-скетч ардуино компилироваться непосредственно на GCC-AVR?
- Поскольку double и float представляют один и тот же тип данных (обычно), что предпочтительнее?
- Как писать скетчи, совместимые с makefile?
- Преимущества глобальных переменных перед статическими членами класса?
- Передача аргументов в LCD.print через другую функцию
- Новый язык — взаимодействие с AVR
- Асимметричное шифрование на Teensy?
- Как объявить массив переменного размера (глобально)
Хорошо, но разве нет причины вызывать функцию serialEventRun()? Для чего это?, @jfpoilpret
Это часть функциональности, используемой HardwareSerial, не знаю, почему ее не удаляют, когда Serial не нужен., @Cybergibbons
Было бы полезно кратко объяснить, как вы сгенерировали вывод ASM, чтобы люди могли проверить себя., @jippie
@Cybergibbons никогда не удаляется, потому что он является частью стандартного
main.c
, используемого Arduino IDE. Однако это не означает, что в ваш скетч включена библиотека HardwareSerial; на самом деле он не включается, если вы его не используете (поэтому естьif (serialEventRun)
в функцииmain()
. Если вы не используете библиотеку HardwareSerial, тоserialEventRun
будет нулевым, поэтому вызов не ., @jfpoilpretОднако библиотека HardwareSerial, вероятно, будет включена (может ли кто-нибудь подтвердить это?), если вы используете некоторые функции Arduino, такие как
Serial.print()
. Что может случиться в этой ситуации, если вашloop()
зациклится навсегда и, таким образом, никогда не вызоветserialEventRun()
? Боюсь, Serial может не сработать в этом случае., @jfpoilpretДа, это часть main.c, как указано, но я ожидаю, что он будет оптимизирован, если не требуется, поэтому я думаю, что аспекты Serial всегда включены. Я часто пишу код, который никогда не вернется из цикла(), и не замечаю проблем с Serial., @Cybergibbons
@Cybergibbons,
serialEventRun()
вызываетserialEvent()
для различных последовательных портов. По умолчанию все это [пустые процедуры](https://github.com/arduino/Arduino/blob/master/hardware/arduino/cores/arduino/HardwareSerial.cpp), но они могут быть [переопределены пользователем ](http://arduino.cc/en/Tutorial/SerialEvent), чтобы при желании сделать что-нибудь полезное. Если вы не переопределяете его, нет необходимости его вызывать., @microtherion