Как измерить время выполнения программы в процессоре ARM Cortex-A8? - PullRequest
29 голосов
/ 14 июля 2010

Я использую процессор на базе 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, сгенерированный исполняемый файл, который при попытке запустить на плате я получил - Недопустимое сообщение с инструкцией: (* ​​1023 *

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 ]

47 голосов
/ 15 июля 2010

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

В двух словах вы должны выполнить следующие две строки внутри ядра.Либо как загружаемый модуль, либо просто добавление двух строк где-то в 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.Накладные расходы довольно высоки и могут исказить ваши измерения.К счастью, накладные расходы также фиксированы, так что вы можете измерить их и вычесть из своих временных интервалов.

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

1 голос
/ 07 апреля 2014

Более подробно об ответе Нильса сейчас, когда прошло несколько лет! - простой способ получить доступ к этим счетчикам - собрать ядро ​​с помощью gator . Затем он сообщает значения счетчиков для использования с Streamline , который является инструментом анализа производительности ARM.

Он будет отображать каждую функцию на временной шкале (предоставляя вам высокоуровневый обзор того, как работает ваша система), показывая вам, сколько именно времени потребовалось для выполнения, наряду с% загрузки ЦП, который он занял. Это можно сравнить с диаграммами каждого счетчика, который вы настроили для сбора и выполнения задач, интенсивно работающих с ЦП, вплоть до уровня исходного кода.

Streamline работает со всеми процессорами серии Cortex-A.

1 голос
/ 14 июля 2010

Вам необходимо профилировать свой код с помощью инструментов анализа производительности до и после оптимизации.

Acct - это командная строка и функция, которую вы можете использовать для мониторинга своих ресурсов. Вы можете узнать больше об использовании и просмотре файла данных, сгенерированного acct.

Я обновлю этот пост другими инструментами анализа производительности с открытым исходным кодом.

Gprof - еще один такой инструмент. Пожалуйста, проверьте документацию для того же.

0 голосов
/ 14 июля 2010

Я работал в цепочке инструментов для ARM7, в которой был симулятор уровня команд.Запуск приложений в этом может дать время для отдельных строк и / или инструкции asm.Это было здорово для микрооптимизации данной рутины.Этот подход, вероятно, не подходит для оптимизации всего приложения / всей системы.

...