Будет ли бесконечный цикл внутри loop () работать быстрее?

19

Когда вы пишете типичный скетч, вы обычно полагаетесь на loop()то , что вам звонят многократно, пока работает Arduino. При въезде и выходе из loop()функции должны быть небольшие издержки.

Чтобы избежать этого, вы можете создать свой собственный бесконечный цикл, например так:

void loop()
{
    while (true)
    {
        // do stuff...
    }
}

Это жизнеспособный способ улучшить производительность? Это вызовет другие проблемы, если loop()никогда не вернется?

Питер Блумфилд
источник

Ответы:

18

Часть кода на ядре 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
источник
Хорошо, но нет ли причины для вызова функции serialEventRun ()? Для чего это?
jfpoilpret
1
Это часть функциональности, используемой HardwareSerial, не знаю, почему его не убирают, когда Serial не нужен.
Cybergibbons
2
Было бы полезно кратко объяснить, как вы сгенерировали выходные данные ASM, чтобы люди могли проверить себя.
Джиппи
@Cybergibbons никогда не вывозится, потому что он является частью стандарта, main.cиспользуемого в Arduino IDE. Однако это не означает, что библиотека HardwareSerial включена в ваш эскиз; на самом деле он не включается, если вы его не используете (вот почему он есть if (serialEventRun)в main()функции. Если вы не используете библиотеку HardwareSerial, то serialEventRunон будет нулевым, а значит, и без вызова.
jfpoilpret
1
Да, это часть main.c, как указано, но я ожидаю, что он будет оптимизирован, если не требуется, поэтому я думаю, что аспекты Serial всегда включены. Я часто пишу код, который никогда не вернется из loop () и не замечает проблем с Serial.
Cybergibbons
6

Ответ Cybergibbons довольно хорошо описывает генерацию кода сборки и различия между этими двумя методами. Предполагается, что это будет дополнительным ответом, рассматривающим проблему с точки зрения практических различий, т. Е. Насколько сильно изменится любой подход с точки зрения времени выполнения .


Варианты кода

Я сделал анализ, включающий следующие варианты:

  • Basic 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.
  • Разница во времени между неоптимизированным кодом и по умолчанию оптимизированным Arduino кодом практически невелика . Вам лучше будет компилировать вручную, используя avr-gccи используя свои собственные флаги оптимизации, а не полагаться на Arduino IDE, которая поможет вам в этом (если вам нужна оптимизация в микросекундах).

ПРИМЕЧАНИЕ: фактические значения времени здесь не имеют значения, разница между ними есть. В ~ 90 микросекунд времени выполнения включает в себя вызов Serial.println, microsи delay.

ПРИМЕЧАНИЕ 2. Это было сделано с использованием IDE Arduino и флагов компилятора по умолчанию, которые он предоставляет.

ПРИМЕЧАНИЕ 3. Анализ (график и расчеты) был выполнен с использованием R.

asheeshr
источник
1
Хорошая работа. График имеет миллисекунды, а не микросекунды, но не является большой проблемой.
Cybergibbons
@Cybergibbons Это маловероятно, так как все измерения в микросекундах, и я нигде не менял шкалы :)
asheeshr