web-dev-qa-db-ja.com

ARM Cortex-A8プロセッサでプログラムの実行時間を測定する方法は?

ARM i.MX515と呼ばれるCortex-A8ベースのプロセッサを使用しています。LinuxUbuntu 9.10ディストリビューションがあります。Cで書かれた非常に大きなアプリケーションを実行しています。 gettimeofday();関数を使用して、アプリケーションにかかる時間を測定します。

main()

{

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

}

この方法は、アプリケーションのどのブロックがどのくらいの時間を費やしているかを調べるのに十分でした。しかし、今、私はコードを非常に徹底的に最適化しようとしています、時間を計算するgettimeofday()メソッドを使用して、連続する実行(最適化の前後に実行)の間に多くの変動が見られるため、実際の実行時間を決定するため、私の改善の影響。

誰かが私に何をすべきかを提案できますか?

サイクルカウンターにアクセスすることによって(ARM Webサイトfor Cortex-Mで提案されたアイデア)誰かが私にいくつかのコードを指摘して、私が従わなければならない手順を教えてくれるならタイマーにアクセスCortex-A8に登録

この方法があまり正確でない場合は、いくつかの代替案を提案してください。

ありがとう


フォローアップ

フォローアップ1:次のプログラムをコードソーサリーに書き込んだ場合、ボードで実行しようとしたときに実行可能ファイルが生成されました-不正な指示メッセージ:(

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を使用してストリームを送信するコードの例を添付して送っています、あなたはinitではないようです正しく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) ;
}
30

パフォーマンスカウンターへのアクセスは難しくありませんが、カーネルモードから有効にする必要があります。デフォルトでは、カウンターは無効になっています。

簡単に言えば、カーネル内で次の2行を実行する必要があります。ロード可能なモジュールとして、またはboard-initのどこかに2行を追加するだけで、次のようになります。

_  /* 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 CPUで動作するはずです。

ああ-そしていくつかのメモ:

これらのカウンターを使用して、他のプロセスまたはカーネルで費やされたすべてを含む、get_cyclecount()への2つの呼び出しの間の正確な時間を測定します。測定をプロセスまたは単一スレッドに制限する方法はありません。

また、get_cyclecount()の呼び出しは無料ではありません。これは単一のasm命令にコンパイルされますが、コプロセッサーから移動すると、ARMパイプライン全体が停止します。オーバーヘッドは非常に高く、測定値を歪める可能性があります。幸い、オーバーヘッドも修正されていますなので、測定してタイミングから差し引くことができます。

私の例では、すべての測定に対してそれを行いました。実際にはこれを行わないでください。遅かれ早かれ2つの呼び出しの間に割り込みが発生し、測定値をさらに歪めます。アイドルシステムでオーバーヘッドを数回測定し、すべての部外者を無視して、代わりに固定定数を使用することをお勧めします。

47

数年が経過した今、ニルスの答えをさらに詳しく説明します。 -これらのカウンターにアクセスする簡単な方法は gatorでカーネルを構築する です。次に、ARMのパフォーマンス分析ツールである Streamline で使用するためのカウンター値を報告します。

タイムラインに各機能が表示され(システムのパフォーマンスの概要がわかります)、実行にかかった時間とCPU使用率(%)が正確に表示されます。これを、CPUを集中的に使用するタスクを収集してソースコードレベルまで追跡するように設定した各カウンターのグラフと比較できます。

Streamlineは、すべてのCortex-Aシリーズプロセッサで動作します。

1
Badmanton Casio

最適化の前後に、パフォーマンス分析ツールでコードをプロファイルする必要があります。

Acct は、リソースの監視に使用できるコマンドラインと関数です。したがって、acctによって生成されたdatファイルの使用と表示について、さらにグーグルできます。

この投稿を他のオープンソースのパフォーマンス分析ツールで更新します。

Gprof もそのようなツールです。ドキュメントについても同様に確認してください。

1
Praveen S

私は、命令レベルシミュレータを備えたARM7のツールチェーンで作業しました。その中でアプリを実行すると、個々のラインやasm命令にタイミングが与えられる可能性があります。これは、特定のルーチンのマイクロ最適化に最適でした。ただし、このアプローチは、アプリ全体/システム全体の最適化にはおそらく適切ではありません。

0
Digikata