Как измерить время выполнения программы в процессоре ARM Cortex-A8?
Я использую процессор на базе ARM Cortex-A8, который называется i.MX515. Есть дистрибутив linux Ubuntu 9.10. Я запускаю очень большое приложение, написанное на C, и я использую gettimeofday();
функции для измерения времени, которое занимает мое приложение.
main()
{
gettimeofday(start);
....
....
....
gettimeofday(end);
}
Этого метода было достаточно, чтобы посмотреть, какие блоки моего приложения занимают какое количество времени. Но теперь, когда я пытаюсь оптимизировать свой код очень тщательно, с помощью метода вычисления времени gettimeofday(), я вижу много колебаний между последовательными запусками (запуск до и после моих оптимизаций), поэтому я не могу определить фактическое время выполнения, отсюда и влияние моих улучшений.
Кто-нибудь может подсказать, что мне делать?
Если при доступе к счетчику циклов (идея предложена на веб-сайте ARM для Cortex-M3) кто-нибудь может указать мне какой-то код, который дает мне шаги, которые я должен выполнить для доступа к регистрам таймера на Cortex-A8?
Если этот метод не очень точный, пожалуйста, предложите несколько альтернатив.
Спасибо
Следить за
Продолжение 1: Написал следующую программу на Code Sorcery, сгенерированный исполняемый файл, который, когда я попытался запустить на плате, я получил - Сообщение о недопустимой инструкции:(
static inline unsigned int get_cyclecount (void)
{
unsigned int value;
// Read CCNT Register
asm volatile ("MRC p15, 0, %0, c9, c13, 0\t\n": "=r"(value));
return value;
}
static inline void init_perfcounters (int32_t do_reset, int32_t enable_divider)
{
// in general enable all counters (including cycle counter)
int32_t value = 1;
// peform reset:
if (do_reset)
{
value |= 2; // reset all counters to zero.
value |= 4; // reset cycle counter to zero.
}
if (enable_divider)
value |= 8; // enable "by 64" divider for CCNT.
value |= 16;
// program the performance-counter control-register:
asm volatile ("MCR p15, 0, %0, c9, c12, 0\t\n" :: "r"(value));
// enable all counters:
asm volatile ("MCR p15, 0, %0, c9, c12, 1\t\n" :: "r"(0x8000000f));
// clear overflows:
asm volatile ("MCR p15, 0, %0, c9, c12, 3\t\n" :: "r"(0x8000000f));
}
int main()
{
/* enable user-mode access to the performance counter*/
asm ("MCR p15, 0, %0, C9, C14, 0\n\t" :: "r"(1));
/* disable counter overflow interrupts (just in case)*/
asm ("MCR p15, 0, %0, C9, C14, 2\n\t" :: "r"(0x8000000f));
init_perfcounters (1, 0);
// measure the counting overhead:
unsigned int overhead = get_cyclecount();
overhead = get_cyclecount() - overhead;
unsigned int t = get_cyclecount();
// do some stuff here..
printf("\nHello World!!");
t = get_cyclecount() - t;
printf ("function took exactly %d cycles (including function call) ", t - overhead);
get_cyclecount();
return 0;
}
Продолжение 2: Я написал в Freescale для поддержки, и они отправили мне ответ и программу (я не совсем понял из этого)
Вот что мы можем вам помочь прямо сейчас: я отправляю вам прикрепленный пример кода, который отправляет поток с использованием UART, из того, что ваш код, кажется, что вы не правильно инициализируете MPU.
(hash)include <stdio.h>
(hash)include <stdlib.h>
(hash)define BIT13 0x02000
(hash)define R32 volatile unsigned long *
(hash)define R16 volatile unsigned short *
(hash)define R8 volatile unsigned char *
(hash)define reg32_UART1_USR1 (*(R32)(0x73FBC094))
(hash)define reg32_UART1_UTXD (*(R32)(0x73FBC040))
(hash)define reg16_WMCR (*(R16)(0x73F98008))
(hash)define reg16_WSR (*(R16)(0x73F98002))
(hash)define AIPS_TZ1_BASE_ADDR 0x70000000
(hash)define IOMUXC_BASE_ADDR AIPS_TZ1_BASE_ADDR+0x03FA8000
typedef unsigned long U32;
typedef unsigned short U16;
typedef unsigned char U8;
void serv_WDOG()
{
reg16_WSR = 0x5555;
reg16_WSR = 0xAAAA;
}
void outbyte(char ch)
{
while( !(reg32_UART1_USR1 & BIT13) );
reg32_UART1_UTXD = ch ;
}
void _init()
{
}
void pause(int time)
{
int i;
for ( i=0 ; i < time ; i++);
}
void led()
{
//Write to Data register [DR]
*(R32)(0x73F88000) = 0x00000040; // 1 --> GPIO 2_6
pause(500000);
*(R32)(0x73F88000) = 0x00000000; // 0 --> GPIO 2_6
pause(500000);
}
void init_port_for_led()
{
//GPIO 2_6 [73F8_8000] EIM_D22 (AC11) DIAG_LED_GPIO
//ALT1 mode
//IOMUXC_SW_MUX_CTL_PAD_EIM_D22 [+0x0074]
//MUX_MODE [2:0] = 001: Select mux mode: ALT1 mux port: GPIO[6] of instance: gpio2.
// IOMUXC control for GPIO2_6
*(R32)(IOMUXC_BASE_ADDR + 0x74) = 0x00000001;
//Write to DIR register [DIR]
*(R32)(0x73F88004) = 0x00000040; // 1 : GPIO 2_6 - output
*(R32)(0x83FDA090) = 0x00003001;
*(R32)(0x83FDA090) = 0x00000007;
}
int main ()
{
int k = 0x12345678 ;
reg16_WMCR = 0 ; // disable watchdog
init_port_for_led() ;
while(1)
{
printf("Hello word %x\n\r", k ) ;
serv_WDOG() ;
led() ;
}
return(1) ;
}
4 ответа
Доступ к счетчикам производительности не сложен, но вы должны включить их в режиме ядра. По умолчанию счетчики отключены.
Короче говоря, вы должны выполнить следующие две строки внутри ядра. Либо как загружаемый модуль, либо просто добавление двух строк где-то в board-init сделает:
/* enable user-mode access to the performance counter*/
asm ("MCR p15, 0, %0, C9, C14, 0\n\t" :: "r"(1));
/* disable counter overflow interrupts (just in case)*/
asm ("MCR p15, 0, %0, C9, C14, 2\n\t" :: "r"(0x8000000f));
Как только вы это сделаете, счетчик циклов начнет увеличиваться для каждого цикла. Переполнения регистра останутся незамеченными и не вызовут никаких проблем (за исключением того, что они могут испортить ваши измерения).
Теперь вы хотите получить доступ к счетчику циклов из пользовательского режима:
Начнем с функции, которая читает регистр:
static inline unsigned int get_cyclecount (void)
{
unsigned int value;
// Read CCNT Register
asm volatile ("MRC p15, 0, %0, c9, c13, 0\t\n": "=r"(value));
return value;
}
И вы, скорее всего, хотите сбросить и установить разделитель:
static inline void init_perfcounters (int32_t do_reset, int32_t enable_divider)
{
// in general enable all counters (including cycle counter)
int32_t value = 1;
// peform reset:
if (do_reset)
{
value |= 2; // reset all counters to zero.
value |= 4; // reset cycle counter to zero.
}
if (enable_divider)
value |= 8; // enable "by 64" divider for CCNT.
value |= 16;
// program the performance-counter control-register:
asm volatile ("MCR p15, 0, %0, c9, c12, 0\t\n" :: "r"(value));
// enable all counters:
asm volatile ("MCR p15, 0, %0, c9, c12, 1\t\n" :: "r"(0x8000000f));
// clear overflows:
asm volatile ("MCR p15, 0, %0, c9, c12, 3\t\n" :: "r"(0x8000000f));
}
do_reset
установит счетчик циклов на ноль. Легко как то.
enable_diver
включит делитель цикла 1/64. Без этого флага вы будете измерять каждый цикл. При включении счетчик увеличивается на каждые 64 цикла. Это полезно, если вы хотите измерить длительное время, которое могло бы вызвать переполнение счетчика.
Как это использовать:
// init counters:
init_perfcounters (1, 0);
// measure the counting overhead:
unsigned int overhead = get_cyclecount();
overhead = get_cyclecount() - overhead;
unsigned int t = get_cyclecount();
// do some stuff here..
call_my_function();
t = get_cyclecount() - t;
printf ("function took exactly %d cycles (including function call) ", t - overhead);
Должен работать на всех процессорах Cortex-A8.
Ох - и некоторые заметки:
Используя эти счетчики, вы будете измерять точное время между двумя вызовами get_cyclecount()
включая все, что потрачено на другие процессы или ядро. Нет способа ограничить измерение вашим процессом или отдельной нитью.
Также звоню get_cyclecount()
не бесплатно Он будет скомпилирован в одну asm-инструкцию, но перемещение из сопроцессора остановит весь конвейер ARM. Накладные расходы довольно высоки и могут исказить ваши измерения. К счастью, накладные расходы также фиксированы, так что вы можете измерить их и вычесть из своих временных интервалов.
В моем примере я делал это для каждого измерения. Не делай этого на практике. Между двумя вызовами рано или поздно произойдет прерывание, что приведет к еще большему искажению результатов измерений. Я предлагаю вам пару раз измерить накладные расходы в режиме ожидания, игнорировать всех посторонних и вместо этого использовать фиксированную константу.
Чтобы расширить ответ Нильса сейчас, когда прошло несколько лет! - простой способ получить доступ к этим счетчикам - собрать ядро с помощью gator. Затем он сообщает значения счетчиков для использования с Streamline, который является инструментом анализа производительности ARM.
Он будет отображать каждую функцию на временной шкале (предоставляя вам общий обзор того, как работает ваша система), показывая вам, сколько именно времени потребовалось для выполнения, вместе с% CPU, который он занял. Вы можете сравнить это с диаграммами каждого счетчика, который вы настроили для сбора и выполнения задач, интенсивно работающих с ЦП, вплоть до уровня исходного кода.
Streamline работает со всеми процессорами серии Cortex-A.
Вам нужно профилировать свой код с помощью инструментов анализа производительности до и после оптимизации.
Acct - это командная строка и функция, которую вы можете использовать для мониторинга своих ресурсов. Вы можете узнать больше об использовании и просмотре файла dat, следовательно, сгенерированного acct.
Я дополню этот пост другими инструментами анализа производительности с открытым исходным кодом.
Gprof - еще один такой инструмент. Пожалуйста, проверьте документацию на то же самое.
Я работал над набором инструментов для ARM7, в котором был симулятор уровня команд. Запуск приложений в этом может дать время для отдельных строк и / или инструкции asm. Это было здорово для микрооптимизации данной рутины. Этот подход, вероятно, не подходит для оптимизации всего приложения / всей системы.