Можно ли найти время, затраченное миллисами?

Функция millis будет работать в диапазоне 100+ микросекунд или меньше. Есть ли надежный способ измерения времени, затраченного на один миллисекундный вызов?

Один подход, который приходит на ум, заключается в использовании micros, однако вызов micros будет включать время, посланное вызовом функции micros, так что в зависимости от того, сколько длится микросъемка, измерение для millis может быть отключено.

Мне нужно найти это как приложение, над которым я работаю, требует точных измерений времени для каждого шага, принятого в коде, включая millis.

12 голосов | спросил asheeshr 12 FebruaryEurope/MoscowbWed, 12 Feb 2014 20:04:13 +0400000000pmWed, 12 Feb 2014 20:04:13 +040014 2014, 20:04:13

3 ответа


21

Если вы хотите точно знать точно , как долго что-то будет сделано, есть только одно решение: посмотрите на разборку!

Начиная с минимального кода:

void setup(){};

volatile uint16_t x;
void loop()
{
  x = millis();

}

Этот код, скомпилированный, а затем загруженный в avr-objdump -S, создает документированную разборку. Вот интересные выдержки:

void loop() производит:

000000a8 <loop>:
  a8:   0e 94 a7 00     call    0x14e   ; 0x14e <millis>
  ac:   60 93 00 01     sts 0x0100, r22
  b0:   70 93 01 01     sts 0x0101, r23
  b4:   80 93 02 01     sts 0x0102, r24
  b8:   90 93 03 01     sts 0x0103, r25
  bc:   08 95           ret

Это вызов функции (call), четыре копии (которые копируют каждый из байтов в uint32_t значение возврата millis() (обратите внимание, что документы arduino называют это long, но они неправильны, чтобы не указывать явно размеры переменных)), и, наконец, функция возвращает.

call требуется 4 тактовых цикла, а для каждого sts требуется 2 тактовых цикла, поэтому у нас есть как минимум 12 тактовых циклов только для служебных вызовов функций.

Теперь давайте посмотрим на разборку функции <millis>, которая находится в 0x14e:

unsigned long millis()
{
    unsigned long m;
    uint8_t oldSREG = SREG;
 14e:   8f b7           in  r24, 0x3f   ; 63

    // disable interrupts while we read timer0_millis or we might get an
    // inconsistent value (e.g. in the middle of a write to timer0_millis)
    cli();
 150:   f8 94           cli
    m = timer0_millis;
 152:   20 91 08 01     lds r18, 0x0108
 156:   30 91 09 01     lds r19, 0x0109
 15a:   40 91 0a 01     lds r20, 0x010A
 15e:   50 91 0b 01     lds r21, 0x010B
    SREG = oldSREG;
 162:   8f bf           out 0x3f, r24   ; 63

    return m;
}
 164:   b9 01           movw    r22, r18
 166:   ca 01           movw    r24, r20
 168:   08 95           ret

Как вы можете видеть, функция millis() довольно проста:

  1. in сохраняет настройки регистра прерываний (1 цикл)
  2. cli отключает прерывания (1 цикл)
  3. lds копирует один из 4 байтов текущего значения миллиметра в временный регистр (2 такта)
  4. lds Байт 2 (2 такта)
  5. lds Байт 3 (2 такта)
  6. lds Байт 4 (2 такта)
  7. out восстановить настройки прерывания (1 такт)
  8. movw перетасовывает регистры вокруг (1 такт)
  9. movw и снова (1 такт)
  10. ret возврат из подпрограммы (4 цикла)

Итак, если мы добавим их все, у нас есть в общей сложности 17 тактов в самой функции millis(), плюс накладные расходы 12, в общей сложности 29 тактов.

Предполагая тактовую частоту 16 МГц (большинство ардуинов), каждый такт составляет 1 / 16e6 секунд или 0,0000000625 секунд, что составляет 62,5 наносекунды. 62,5 нс * 29 = 1,812 мкс.

Следовательно, общее время выполнения для одного вызова millis() на most Arduinos будет 1,812 микросекунд .


Ссылка на сборку AVR

В качестве побочной заметки здесь есть место для оптимизации! Если вы обновляете определение функции unsigned long millis(){} как inline unsigned long millis(){}, вы должны удалить служебные данные вызова (за счет чуть больше ). Кроме того, похоже, что компилятор делает два ненужных хоста (два вызова movw, но я не смотрел на них так близко).

Действительно, учитывая служебные данные вызова функции - это 5 инструкций, а фактическое содержимое функции millis() - всего 6 команд, я думаю, что millis () по умолчанию должен быть millis(), но кодовая база Arduino довольно плохо оптимизирована.


Вот полная разборка для всех, кого это интересует:

inline
ответил Connor Wolf 13 FebruaryEurope/MoscowbThu, 13 Feb 2014 16:26:29 +0400000000pmThu, 13 Feb 2014 16:26:29 +040014 2014, 16:26:29
8

Напишите эскиз, который миллионом 1000 раз, а не путем создания цикла, но путем копирования и вставки. Измерьте это и сравните его с фактическим ожидаемым временем. Имейте в виду, что результаты могут отличаться в разных версиях IDE (и, в частности, в компиляторе).

Другой вариант - переключить вывод ввода-вывода до и после вызова миллиса, затем измерить время для очень маленького значения и несколько большего значения. Сравните измеренные тайминги и вычислите накладные расходы.

Самый точный способ - взглянуть на список разборки, сгенерированный код. Но это не для слабонервных. Вы должны тщательно изучить таблицу, сколько времени занимает каждый цикл команд.

ответил jippie 12 FebruaryEurope/MoscowbWed, 12 Feb 2014 21:01:24 +0400000000pmWed, 12 Feb 2014 21:01:24 +040014 2014, 21:01:24
3

Я второй раз вызываю millis, а затем сравниваю фактическое и ожидаемое.

Там будут некоторые минимальные накладные расходы, но это будет уменьшаться по значению, чем больше раз вы называете millis ().

Если вы посмотрите

C:\Program Files (x86)\Arduino\Arduino ERW 1.0.5\hardware\arduino\cores\arduino\wiring.c

Вы можете видеть, что millis () очень крошечный только в 4 инструкциях (cli is simply # define cli() \__asm__ \__volatile__ ("cli" ::)) и return.

Я бы назвал его около 10 миллионов раз, используя цикл FOR, который имеет изменчивое состояние как условное. Ключевое слово volatile не позволит компилятору попытаться оптимизировать сам цикл.

Я не гарантирую синтаксически совершенное следующее:

int temp1,temp2;
temp1=millis();
for (volatile unsigned int j=0;j<1000000;++j){
temp2=millis();}
Serial.print("Execution time = ");
Serial.print((temp2-temp1,DEC);
Serial.print("ms");

Я предполагаю, что это занимает ~ 900 мс или около 56% за звонок до миллиса. (У меня нет удобного банкомата aruduino.

ответил 80HD 13 FebruaryEurope/MoscowbThu, 13 Feb 2014 14:41:34 +0400000000pmThu, 13 Feb 2014 14:41:34 +040014 2014, 14:41:34

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132