Функция millis
будет работать в диапазоне от 100+ микросекунд или меньше. Есть ли надежный способ измерения времени, затрачиваемого на один миллисекунд?
Один подход, который приходит на ум, заключается в использовании micros
, однако, вызова, который micros
будет включать в себя также время, затрачиваемое самим вызовом функции micros
, поэтому в зависимости от того, сколько времени занимает микро, измерение millis
может быть выключено.
Мне нужно найти это, поскольку приложение, над которым я работаю, требует точных измерений времени для каждого шага в коде, в том числе millis
.
miilis
.Ответы:
Если вы хотите точно знать , сколько времени займет что-то, есть только одно решение: посмотрите на разборку!
Начиная с минимального кода:
Этот код, скомпилированный и затем переданный,
avr-objdump -S
производит документированную разборку. Вот интересные выдержки:void loop()
производит:Это вызов функции (
call
), четыре копии (которые копируют каждый из байтов вuint32_t
возвращаемом значенииmillis()
(обратите внимание, что документы arduino называют это along
, но они некорректны, чтобы не указывать явно переменные размеры)), и, наконец, функция возврата.call
требуется 4 такта, и каждыйsts
требует 2 такта, поэтому у нас есть минимум 12 тактов только для служебных вызовов вызовов.Теперь давайте посмотрим на разбор
<millis>
функции, которая находится по адресу0x14e
:Как видите,
millis()
функция довольно проста:in
сохраняет настройки регистра прерываний (1 цикл)cli
отключает прерывания (1 цикл)lds
скопировать один из 4 байтов текущего значения счетчика милли во временный регистр (2 такта)lds
Байт 2 (2 такта)lds
Байт 3 (2 такта)lds
Байт 4 (2 такта)out
восстановить настройки прерывания (1 такт)movw
регистры случайного воспроизведения (1 такт)movw
и снова (1 такт)ret
возврат из подпрограммы (4 цикла)Таким образом, если мы сложим их все вместе, у нас будет в общей сложности 17 тактов в самой
millis()
функции, плюс накладные расходы на вызов 12, что в общей сложности составит 29 тактов.Предполагая тактовую частоту 16 МГц (большинство arduinos), каждый тактовый цикл составляет
1 / 16e6
секунды, или 0,0000000625 секунд, что составляет 62,5 наносекунды. 62,5 нс * 29 = 1,812 мкс.Таким образом, общее время выполнения одного
millis()
вызова на большинстве Arduinos составит 1,812 микросекунд .AVR Сборка ссылки
Как примечание, здесь есть место для оптимизации! Если вы обновите
unsigned long millis(){}
определение функции, чтобы бытьinline unsigned long millis(){}
, вы удалили бы издержки вызова (за счет немного большего размера кода). Кроме того, похоже, что компилятор делает два ненужных шага (дваmovw
вызова, но я не слишком внимательно на это смотрю).Действительно, с учетом накладных расходов вызова функции 5 инструкций, а фактические содержанием этого
millis()
функции только 6 инструкции, я думаю , чтоmillis()
функция должна быть действительноinline
по умолчанию, но Arduino кодового довольно плохо оптимизирован.Вот полная разборка для всех, кто интересуется:
источник
sts
Четверка не должна учитываться как накладные расходы на вызовы: это стоимость сохранения результата в переменную, которую вы обычно не делаете. 2) В моей системе (Arduino 1.0.5, gcc 4.8.2) у меня нетmovw
s. Тогда стоимость вызоваmillis()
составляет: 4 цикла служебных вызовов + 15 цикловmillis()
сами по себе = всего 19 циклов (≈ 1,188 мкс при 16 МГц).x
этоuint16_t
. Это должно быть максимум 2 копии, если это является причиной. В любом случае, вопрос в том , как долгоmillis()
принимать при использовании , а не при вызове, игнорируя при этом результат. Поскольку любое практическое использование будет связано с выполнением каких-либо действий с результатом, я принудительно сохраню результатvolatile
. Как правило, тот же эффект будет достигнут при более позднем использовании переменной, для которой установлено возвращаемое значение вызова, но я не хотел, чтобы этот дополнительный вызов занимал место в ответе.uint16_t
в источнике не соответствует сборке (4 байта хранятся в оперативной памяти). Вы, наверное, разместили исходники и разборки двух разных версий.Напишите эскиз, который будет повторяться 1000 раз, не путем создания петли, а путем копирования и вставки. Измерьте это и сравните с фактическим ожидаемым временем. Помните, что результаты могут отличаться в зависимости от версии IDE (и, в частности, ее компилятора).
Другой вариант - переключить вывод ввода-вывода до и после вызова миллисекунды, а затем измерить время для очень маленького значения и несколько большего значения. Сравните измеренные тайминги и рассчитайте накладные расходы.
Самый точный способ - взглянуть на список разборок, сгенерированный код. Но это не для слабонервных. Вам придется внимательно изучить таблицу, сколько времени занимает каждый цикл инструкций.
источник
millis()
звонков?delay
, ты прав. Но идея остается прежней, вы можете рассчитывать большое количество звонков и усреднять их. Отключение прерываний по всему миру может быть не очень хорошей идеей; о)Я второй раз вызываю миллис, а затем сравниваю фактическое с ожидаемым.
Издержки будут минимальными, но они будут уменьшаться по мере того, как вы будете вызывать миллис ().
Если вы посмотрите на
Вы можете видеть, что millis () очень крошечный, всего 4 инструкции
(cli is simply # define cli() \__asm__ \__volatile__ ("cli" ::))
и возврат.Я бы назвал это около 10 миллионов раз, используя цикл FOR, в котором условным является значение volatile. Ключевое слово volatile будет препятствовать тому, чтобы компилятор пытался оптимизировать сам цикл.
Я не гарантирую, что следующее будет синтаксически совершенным.
я предполагаю, что это занимает ~ 900 мс или около 56 мкс за звонок в миллис. (У меня нет удобного банкомата aruduino.
источник
int temp1,temp2;
это,volatile int temp1,temp2;
чтобы компилятор не мог их оптимизировать.