Как измерить время выполнения программы в процессоре 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) ;
}

person HaggarTheHorrible    schedule 14.07.2010    source источник
comment
Вы всегда можете взять среднее время выполнения для большого количества прогонов.   -  person caf    schedule 15.07.2010
comment
Пример кода модуля Performance Monitor для ARM11 и Cortex-A/R: infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.faqs/   -  person 0x90    schedule 05.09.2013


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

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

person Nils Pipenbrinck    schedule 14.07.2010
comment
Уважаемый Нильс, еще раз спасибо за такой быстрый и подробный ответ. Я хочу идти шаг за шагом в этом подходе, потому что я хочу узнать, как все это работает, поэтому я начал с самого базового уровня. Я никогда раньше не программировал на ассемблере и не знаю всех предпосылок, так что прошу отнестись с пониманием к моему невежеству. Я написал новый основной файл и включил первые две строки в main(){} и скомпилировал его с помощью gcc. У меня не было ошибок компиляции, был сгенерирован окончательный исполняемый файл, и при его выполнении я получаю недопустимую инструкцию. Пропустили что-нибудь здесь? - person HaggarTheHorrible; 15.07.2010
comment
Нильс, у меня есть CodeSorcery g++ IDE (30-дневная пробная версия), установленная на моей настольной системе Linux. Я думал создать там свой проект (используя инструменты кросс-компилятора), а затем использовать исполняемый файл на моей плате i.MX515. Я написал программу, как вы упомянули, исполняемый файл был сгенерирован. Пробовал отлаживать (на эмуляторе), но колдовство Кода выдавало ошибку как Illegal Instruction и она останавливалась, ну мне там все равно было не так важно. Я скопировал исполняемый файл на свою плату i.MX515 и попытался запустить его, но снова получил сообщение о недопустимой инструкции :( (я отредактировал вопрос) - person HaggarTheHorrible; 15.07.2010
comment
Я не знал о двух состояниях, в которых функционирует ОС: режим ядра и режим пользователя. Я только что узнал об этом от своего коллеги. Может быть, я сейчас работаю в пользовательском режиме, поэтому, хотя моя программа компилируется без ошибок, я получаю сообщение о недопустимой инструкции. - person HaggarTheHorrible; 15.07.2010
comment
@vikramtheone, первые две строки должны выполняться из режима ядра. Они обеспечивают доступ пользовательского режима к регистрам CCNT (и связанным с ними). Обойти это невозможно. На мой взгляд, самый простой способ — написать очень короткий модуль ядра, который делает это. Для компиляции этих модулей нужны заголовки ядра ядра, которое вы используете на своей плате, но, поскольку вы используете Ubuntu, это не должно быть большой проблемой. Вот минимальный исходный код модуля ядра: torus.untergrund.net/code/perfcnt_enable.c - person Nils Pipenbrinck; 16.07.2010
comment
вы компилируете его (на цели!) с помощью модулей make -C ‹path-to-kernel-source› SUBDIRS=$(PWD) . Это должно сгенерировать файл с именем perfcnt_enable.ko, который вы можете загрузить (на цель) с помощью insmod ./perfcnt_enable.ko. dmesg скажет вам, сработало это или нет. - person Nils Pipenbrinck; 16.07.2010
comment
@NilsPipenbrinck Отличный ответ, вы знакомы с библиотекой / исходным кодом, который обрабатывает все счетчики производительности? - person 0x90; 05.09.2013
comment
Если вы следуете этому руководству и продолжаете получать ошибку illegal instruction, см. здесь . По сути, это означает, что вы, вероятно, работаете на многоядерном процессоре и должны использовать taskset -c <core number> <your program>, так как PMU не включены на всех ядрах, а только на одном. - person tonysdg; 15.03.2017
comment
Кроме того, если вы хотите включить PMU на всех ядрах, используйте функцию on_each_cpu в заголовке linux/smp.h для запуска кода инициализации ядра на каждом ядре в системе! - person tonysdg; 17.03.2017
comment
Похоже, это не работает для aarch64. Показания счетчика можно изменить на: asm volatile(msr cntv_ctl_el0, %0 : : r (val)); См. stackoverflow.com /questions/32374599/ подробнее - person Sil; 19.03.2019
comment
Я совсем новичок в ARM и у меня есть вопрос. Выполняется ли ассемблерная инструкция в сериализации get_cyclecount() или нам нужно следить за выполнением не по порядку? Если это так, возможно, это можно упомянуть в ответе. - person ilstam; 09.02.2020

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

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

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

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

person Praveen S    schedule 14.07.2010
comment
Правин, проблема, с которой я сталкивался ранее с инструментами анализа производительности (например, gprof), заключается в том, что когда я включаю флаги оптимизации (-O3), статистика, которую я получаю, не имеет никакого смысла. По этой причине я давно использовал gprof, сейчас попробую, дайте посмотреть. - person HaggarTheHorrible; 14.07.2010
comment
@vikramtheone - Предположим, вы создаете файл acct для каждого вызова функции, вы можете получить подробную информацию об используемых ресурсах с точки зрения времени и других параметров. Я использовал это для профилирования и сравнения оптимизаций кода на функциональном уровне. В качестве альтернативы вы можете получить доступ к структуре time_t с помощью gettimeofday и получить время выполнения функции на уровне микросекунд. Так что это зависит от того, чего вы хотите добиться с его помощью. - person Praveen S; 14.07.2010
comment
Правин, я посмотрю на счет. Что касается gettimeofday, я использую его на данный момент, но проблема, с которой я сталкиваюсь, заключается в большом количестве колебаний во времени, которое он измеряет каждый раз, поэтому я думаю, что прямое измерение времени скорее не очень уместно, вместо этого использовать некоторые другие объект, который будет оставаться постоянным независимо от того, сколько процессов запущено, более полезен, и такой объект является счетчиком циклов. По крайней мере, на данный момент я думаю, что это останется постоянным, не знаю, что ждет правда. - person HaggarTheHorrible; 15.07.2010
comment
@vikramtheone - В таком случае вы можете профилировать код. Вы можете проверить getruusage(). А инструменты профилирования, такие как Acct и grof, дадут вам представление о сценарии времени выполнения. Но если вы можете четко объяснить, с каким типом несоответствия вы сталкиваетесь, вы можете получить более точные ответы, поскольку профилирование является основным действием перед выпуском. - person Praveen S; 15.07.2010

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

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

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

person Badmanton Casio    schedule 07.04.2014

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

person Digikata    schedule 14.07.2010