Подтвердить что ты не робот

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

Я использую процессор ARM Cortex-A8, называемый i.MX515. Существует дистрибутив Linux Ubuntu 9.10. Я запускаю очень большое приложение, написанное на C, и я использую функции gettimeofday(); для измерения времени, которое занимает мое приложение.

main()

{

gettimeofday(start);
....
....
....
gettimeofday(end);

}

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

Кто-нибудь может предложить мне, что я должен делать?

Если при доступе к счетчику циклов (Идея, предложенная на веб-сайте ARM для Cortex-M3), кто-нибудь может указать мне на какой-то код, который дает мне шаги, которые я должен выполнить для доступа к таймеру регистры на Cortex-A8?

Если этот метод не очень точен, пожалуйста, предложите несколько альтернатив.

Спасибо


Последующие действия

Follow up 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) ;
}
4b9b3361

Ответ 1

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

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

И вы, скорее всего, захотите reset и установите разделитель:

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

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

Ответ 2

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

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

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

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

Ответ 3

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

Ответ 4

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

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

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