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

programming avr-gcc performance

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

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

void loop()
{
    while (true)
    {
        // делаем что-то...
    }
}

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

, 👍21


2 ответа


Лучший ответ:

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

И проверьте вывод в текстовом редакторе.

,

Хорошо, но разве нет причины вызывать функцию 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


6

Ответ 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