Почему эти эскизы не дают аналогичного результата ?
Я скомпилировал следующие два скетча с помощью компилятора, используемого в 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 () {}
Вот выходные данные на последовательном мониторе:
2452320
3332640 3332704
Почему выполнение каждого из двух циклов for
во втором скетче заняло примерно на 33% больше времени, чем цикл в первом?
@noearchimede, 👍3
Обсуждение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
- Как справиться с rollover millis()?
- Почему мои часы реального времени показывают неверное время с моего ПК?
- Будет ли .ino-скетч ардуино компилироваться непосредственно на GCC-AVR?
- Несколько неблокирующих таймеров обратного отсчета?
- Есть ли лучший выбор, кроме использования delay() для 6-часовой задержки?
- Использование Arduino в качестве автономного компилятора
- Получение кода FFT arduino для работы более 9 часов с использованием micros()
- Как повторить код
Если вы повторите оба теста, вы получите те же цифры, что и выше?, @Michel Keijzers
@MichelKeijzers да, я всегда получаю одни и те же цифры., @noearchimede
Было бы полезно, если бы вы отредактировали свой вопрос, указав, какую версию Arduino IDE вы используете и какую версию плат Arduino AVR (показано в меню «Инструменты» > «Плата» > «Диспетчер плат»)., @per1234
@per1234 Arduino IDE 1.8.0, платы Arduino AVR 1.6.20. Я только что отредактировал вопрос., @noearchimede