Мониторинг тактовых циклов для кода на Arduino / AVR?

11

Можно ли отслеживать блок кода и определять количество тактовых циклов процессора, которое код выполнял на процессоре Armelino и / или AVR Atmel? или лучше отслеживать микросекунды, прошедшие до и после выполнения кода? Примечание: меня не интересует реальное время (например, сколько реальных секунд прошло), а то, что я "сколько тактовых циклов требует этот код от процессора"

Текущее решение, которое я могу придумать - это time.c:

#define clockCyclesPerMicrosecond() ( F_CPU / 1000000L )
#define clockCyclesToMicroseconds(a) ( (a) / clockCyclesPerMicrosecond() )

wiring.c добавляет:

#define microsecondsToClockCycles(a) ( (a) * clockCyclesPerMicrosecond() )

По этой учетной записи я мог бы рассчитать тактовые циклы, пройденные путем мониторинга пройденных микросекунд, а затем передать их в microsecondsToClockCycles (). У меня вопрос, есть ли лучший способ?

sidenote: есть ли хорошие ресурсы для мониторинга производительности AVR. lmgtfy.com и различные поиски на форумах не дают никаких очевидных результатов, кроме исследования таймеров.

Спасибо

cyphunk
источник

Ответы:

6

Самый простой способ - заставить ваш код подтянуть некоторую булавку, прежде чем он выполнит код, который вы хотите синхронизировать, и понизить его после того, как он закончил делать что угодно. Затем сделайте кодовую петлю (или используйте цифровой осциллограф с памятью в режиме одиночной съемки) и просто прицельтесь, затем закрепите. Длина импульса говорит вам, сколько времени потребовалось для выполнения куска кода плюс один тактовый цикл от изменения состояния вывода (я думаю, что это занимает один цикл, не уверен на 100%).

даго
источник
Спасибо. Да, я вижу, что это, вероятно, самое точное решение. Я все еще отбрасываю код, который дал бы мне хотя бы общий анализ использования цикла внутри кода. Я собираюсь использовать это для создания некоторых инструментов тестирования, и было бы неплохо установить верхние границы для таких параметров, как максимально допустимое время выполнения, основываясь на том, насколько эффективно работает код + все, что с ним связано, на текущем процессоре Atmel в использование
cyphunk
4

Что вы подразумеваете под «монитором»?

Не должно быть сложно рассчитывать тактовые циклы для AVR для небольших фрагментов кода сборки.

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

И вы могли бы также прочитать время из быстродействующего таймера, как вы говорите.

starblue
источник
Под монитором я подразумеваю определение количества циклов, используемых кодом. что-то вроде (заметьте, форматирование кода, вероятно, будет сглажено механизмом комментариев): clocks = startCountingAtmegaClocks (); for ... {for ... {digitalRead ...}} Serial.print ("количество использованных циклов:"); Serial.print (currentCountingAtmegaClocks () - часы, DEC);
cyphunk
Но да, ваш ответ - это то, что я предположил, что у меня есть варианты. Я предполагаю, я предполагаю, что если я смогу вычислить тактовые циклы, которые ассемблер возьмет вручную, то, возможно, кто-то уже написал хороший код для этого программно
cyphunk
3

Это пример того, как Arduino использует функцию clockCyclesPerMicrosecond () для вычисления прошедших часов. Этот код будет ждать 4 секунды, а затем печатать время, прошедшее с момента запуска программы. Левые 3 значения - это общее время (микросекунды, миллисекунды, общее количество тактов), а самые правые 3 - истекшее время:

Выход:

clocks for 1us:16
runtime us, ms, ck :: elapsed tme us, ms ck
4003236 4002	64051776	::	4003236	4002	64051760
8006668 8006	128106688	::	4003432	4004	64054912
12010508    12010	192168128	::	4003840	4004	64061440
16014348    16014	256229568	::	4003840	4004	64061440
20018188    20018	320291008	::	4003840	4004	64061440
24022028    24022	384352448	::	4003840	4004	64061440
28026892    28026	448430272	::	4004864	4004	64077824
32030732    32030	512491712	::	4003840	4004	64061440
36034572    36034	576553152	::	4003840	4004	64061440
40038412    40038	640614592	::	4003840	4004	64061440
44042252    44042	704676032	::	4003840	4004	64061440
48046092    48046	768737472	::	4003840	4004	64061440
52050956    52050	832815296	::	4004864	4004	64077824

Я уверен, что есть разумное объяснение, почему первые циклы тоже имели более короткие истекшие тактовые циклы, чем большинство, и почему все остальные циклы переключаются между двумя длинами тактовых циклов.

Код:

unsigned long us, ms, ck;
unsigned long _us, _ms, _ck;
unsigned long __us, __ms, __ck;
void setup() {
        Serial.begin(9600);
}
boolean firstloop=1;
void loop() { 
        delay(4000);

        if (firstloop) {
                Serial.print("clocks for 1us:");
                ck=microsecondsToClockCycles(1);
                Serial.println(ck,DEC);
                firstloop--;
                Serial.println("runtime us, ms, ck :: elapsed tme us, ms ck");
        }

        _us=us;
        _ms=ms;
        _ck=ck;

        us=micros(); // us since program start
        ms=millis();
        //ms=us/1000;
        ck=microsecondsToClockCycles(us);
        Serial.print(us,DEC);
        Serial.print("\t");
        Serial.print(ms,DEC);
        Serial.print("\t");
        Serial.print(ck,DEC);     
        Serial.print("\t::\t");

        __us = us - _us;
        __ms = ms - _ms;
        __ck = ck - _ck;
        Serial.print(__us,DEC);
        Serial.print("\t");
        Serial.print(__ms,DEC);
        Serial.print("\t");
        Serial.println(__ck,DEC);     

}

Sidenote: если вы уберете 4-секундную задержку, вы начнете видеть эффекты Serial.print () гораздо более четко. Обратите внимание, здесь сравниваются 2 прогона. Я только включил 4 образца рядом друг с другом из их соответствующих журналов.

Прогон 1:

5000604 5000	80009664	::	2516	2	40256
6001424 6001	96022784	::	2520	3	40320
7002184 7002	112034944	::	2600	3	41600
8001292 8001	128020672	::	2600	3	41600

Прогон 2:

5002460 5002	80039360	::	2524	3	40384
6000728 6000	96011648	::	2520	2	40320
7001452 7001	112023232	::	2600	3	41600
8000552 8000	128008832	::	2604	3	41664

Истекшее время увеличивается в течение всего времени выполнения. Через секунду часы увеличиваются в среднем с 40 до 44 тысяч. Это происходит последовательно через несколько миллисекунд после 1 секунды, и истекшие часы остаются около 44k в течение по крайней мере следующих 10 секунд (я не проверял это далее). Вот почему мониторинг полезен или необходим. Возможно, снижение эффективности связано с конфигурацией или ошибками в последовательном соединении? Или, возможно, код неправильно использует память и имеет утечку, которая влияет на производительность и т. Д.

cyphunk
источник
спустя много лет мне все еще хотелось бы что-то, что более точно показывает часы с помощью кода (в отличие от осциллографа). Я пытаюсь определить количество тактов, необходимых для digitalWrite () в 16 МГц и 8 МГц. В 16 МГц я получаю 8us / 64clk. Но в 8MHZ я получаю 0us / 0clk.
cyphunk
1

Поскольку каждая строка кода, добавленная в ваш источник, будет влиять на производительность и может изменить применяемые оптимизации. Изменения должны быть минимальными, необходимыми для выполнения задачи.

Я только что нашел плагин Atmel Studio под названием «Отладчик аннотированных сборочных файлов». http://www.atmel.com/webdoc/aafdebugger/pr01.html Похоже, что вы перебираете фактический сгенерированный язык ассемблера, хотя, вероятно, утомительно покажет вам, что именно происходит. Возможно, вам все равно придется декодировать, сколько циклов требуется для каждой инструкции, но это будет намного ближе, чем некоторые другие опубликованные параметры.

Для тех, кто не знает, в папке Output вашего проекта есть файл с расширением LSS. Этот файл содержит весь ваш исходный код в виде комментариев, а под каждой строкой находится язык ассемблера, который был сгенерирован на основе этой строки кода. Генерацию файла LSS можно отключить, поэтому проверьте следующие настройки.

Свойства проекта | Toolchain | AVR / GNU Common | OutputFiles

Флажок ".lss (Создать файл lss)

Джеймс
источник
1

Вы можете использовать один из встроенных таймеров. Получите все настройки prescaller = 1 и TCNT = 0 перед кадром. Затем включите таймер на линии перед блоком и отключите его на линии после блока. TCNT теперь будет хранить количество циклов, которое принял блок, за вычетом фиксированных циклов для кода включения и отключения.

Обратите внимание, что TNCT переполнится после 65535 тактов на 16-битном таймере. Вы можете использовать флаг переполнения, чтобы удвоить время выполнения. Если вам все еще нужно больше, вы можете использовать прескалер, но получите меньшее разрешение.

bigjosh
источник