Почему эти эскизы не дают аналогичного результата ?

Я скомпилировал следующие два скетча с помощью компилятора, используемого в Arduino IDE, и запустил их на Arduino UNO (версия smd). Я использую:

  • Arduino IDE, версия 1.8.0
  • Платы Arduino AVR, версия 1.6.20

1.

void setup() {

  Serial.begin(115200);
  unsigned long a, b;
  //unsigned long c, d;


  a = micros();
  for (unsigned long i = 0; i != 1000000; i++) digitalRead(13);
  b = micros();
  Serial.println(b - a);


  //c = micros();
  //for (unsigned long i = 0; i != 1000000; i++) digitalRead(13);
  //d = micros();
  //Serial.println(d - c);

}

void loop () {}

2.

void setup() {

  Serial.begin(115200);
  unsigned long a, b;
  unsigned long c, d;


  a = micros();
  for (unsigned long i = 0; i != 1000000; i++) digitalRead(13);
  b = micros();
  Serial.println(b - a);


  c = micros();
  for (unsigned long i = 0; i != 1000000; i++) digitalRead(13);
  d = micros();
  Serial.println(d - c);

}

void loop () {}

Вот выходные данные на последовательном мониторе:

  1. 2452320

  2. 3332640 3332704

Почему выполнение каждого из двух циклов for во втором скетче заняло примерно на 33% больше времени, чем цикл в первом?

, 👍3

Обсуждение

Если вы повторите оба теста, вы получите те же цифры, что и выше?, @Michel Keijzers

@MichelKeijzers да, я всегда получаю одни и те же цифры., @noearchimede

Было бы полезно, если бы вы отредактировали свой вопрос, указав, какую версию Arduino IDE вы используете и какую версию плат Arduino AVR (показано в меню «Инструменты» > «Плата» > «Диспетчер плат»)., @per1234

@per1234 Arduino IDE 1.8.0, платы Arduino AVR 1.6.20. Я только что отредактировал вопрос., @noearchimede


1 ответ


1

Разное время подразумевает разное (количество) выполняемых инструкций. Вероятнее всего, это связано с ужасными оптимизациями компилятора GCC.

В первом наброске, вероятно, компилятору удается использовать только регистры, которые очень быстры. Во втором компилятор ДУМАЕТ, что ему нужно еще 8 байт, а регистров больше нет, поэтому он использует стек (внутреннюю оперативную память) для хранения некоторых переменных, что медленнее. (тем не менее, оптимизатор должен понимать, что на самом деле ему не нужно больше байт, потому что a и b не используются после первого println()). Для доступа к ячейке SRAM вам нужна операция ЗАГРУЗКИ/СОХРАНЕНИЯ, которая занимает больше времени по сравнению с операцией между регистрами (помимо необходимости загрузки адреса памяти в X, Y или Z). Вероятно, компилятор ещё хуже, потому что вместо того, чтобы поместить редко используемые переменные a, b, c, d в стек, он помещает i (которая всегда увеличивается) или другие переменные, используемые в digitalRead().

Тем не менее, чтобы объяснить, в чем проблема (т. е. где компилятор не может оптимизировать распределение/использование регистров), вам следует дизассемблировать объектный код. Используйте, например, Atmel Studio, которая все еще использует GCC, но имеет хороший отладчик, дизассемблер и cycle-exact анализ.

Все равно это очень странно, возможно, не были включены оптимизации platform.txt вашего arduino?

РЕДАКТИРОВАНИЕ: Используя Arduino 1.8.0, я протестировал два скетча и проанализировал lss (сгенерированный с помощью avr-objdump). Компилятор намного больше оптимизирует первый код. Вместо того, чтобы делать CALL для digitalRead() (как это происходит во втором скетче), он, вероятно, распознает, что такая функция вызывается только один раз в коде. Поэтому он делает rjmp для нее. В конце этой функции не будет RET, а будет переход к точке, где i вычисляется и проверяется (на самом деле i не увеличивается, а уменьшается и проверяется на 0).

Другими словами, даже если два кода равны (за исключением того, что второй набросок запускает один и тот же код дважды), они компилируются (оптимизируются) по-разному.

,

Вы уверены, что используете ту же настройку, что и OP?, @next-hack

Наконец я сделал тест. На самом деле я ошибался. Дело не в том, что GCC не оптимизирует второй скетч. Вместо этого он сильно оптимизирует первый (в моем случае я использую Arduino 1.8.0). Вместо того, чтобы сделать CALL к digitalRead, он, вероятно, распознает, что digitalRead вызывается только один раз в коде, поэтому он делает rjmp к digital read, затем из digitalRead возвращается в цикл for, где вычисляет и проверяет i. (Я отредактирую вопрос. Изменения будут видны в истории изменений, поэтому комментарии по-прежнему будут иметь смысл)., @next-hack

@LookAlterno и next-hack: прежде всего, спасибо за ваш интерес к моему вопросу. Но как вы смотрите на скомпилированную программу? Я бы хотел сам взглянуть на то, что вы называете "сгенерированным кодом" и - если это может быть полезно - предоставить тот, который сгенерировал мой компилятор, но я на самом деле не знаю, как его получить., @noearchimede

Arduino IDE по умолчанию не создает файлы .lss. Вы можете создать их, вызвав avr-objdump из командной строки: avr-objdump -S -h yourElfFile.elf >destination.txt. Вы найдете avr-objdump по следующему пути: Arduino\hardware\tools\avr\bin (где Arduino — это каталог, в который вы установили IDE). Кстати, я также получаю абсолютно одинаковые коды с Atmel Studio 7, но разные коды с Arduino 1.8.0 (см. ответ). Вероятно, это связано с разными настройками или разной версией GCC., @next-hack

Методология компиляции UECIDE несколько отличается от Arduino. Вероятно, она не оптимизирует так же, поскольку компилирует разные части отдельно и архивирует их. Это затрудняет встраивание или искажение функций с помощью rjmp вместо call. Также компилятор может быть другой версии (я давно его не обновлял, возможно, сегодня проверю наличие новой версии)., @Majenko