dsPIC33AKシリーズ③CPU性能について

CPUについて

概要

過去2回、dsPIC33AKシリーズについて紹介しました。
今回は大幅に強化されたCPUの性能についてdsPIC33CHと比較・検証します。

記事リンク
第1回dsPIC33AKシリーズについてdsPIC33Aシリーズに関して – ぴくおの電子工作的な何かWP (electricpico.com)
第2回開発ボード、コンフィグレーション設定、クロック設定についてdsPIC33AKシリーズ②開発ボード,コンフィグレーション設定,クロック設定について – ぴくおの電子工作的な何かWP (electricpico.com)
第3回(本記事)CPU性能についてdsPIC33AKシリーズ③CPU性能について – ぴくおの電子工作的な何かWP (electricpico.com)
第4回(予定)FPU性能について
第5回(予定)DSP性能について
(fig.1-1)各記事リンク

開発環境

開発環境を以下に示します。

項目リンク
ベースボードdsPIC33A CURIOSITY PLATFORM
DEVELOPMENT BOARD
dsPIC33A Curiosity Platform Development Board User’s Guide (microchip.com)
CPUボードEV68M17A – dsPIC33AK128MC106 Motor Control DIMdsPIC33AK128MC106 Motor Control Dual In-Line Module (DIM) Information Sheet (microchip.com)
統合開発環境MPLAB X IDE v6.20MPLAB® X IDE | Microchip Technology
コンパイラMPLAB XC DSC v3.10MPLAB® XC DSC Compiler | Microchip Technology
(fig.2-1)本記事の動作確認環境
(fig.2-2)ベースボード+CPUボード(EV68M17A)

項目リンク
ベースボードdsPIC33CH CURIOSITY
DEVELOPMENT BOARD
DSPIC33CH CURIOSITY DEVELOPMENT BOARD | Microchip Technology
統合開発環境MPLAB X IDE v6.20MPLAB® X IDE | Microchip Technology
コンパイラMPLAB XC DSC v3.10MPLAB® XC DSC Compiler | Microchip Technology
(fig.2-3)dsPIC33CHの動作確認環境

今回の実験のコンフィクレーションおよびクロックの設定は第2回の記事の通りとします。

CPU概要

dspPIC33AKシリーズは、システムクロックスピードの向上(100MHz→200MHz)、バス幅拡大(16bit→32bit)、5段インターロック命令パイプライン(*1)、投機実行(*2)、条件分岐レイテンシを提言するプリフェッチ分岐予測を特徴とするCPUアーキテクチャを備えています。また2kBの命令キャッシュによるアクセス時間短縮。発振周波数=命令サイクル数(※注:dsPIC33CKでは発振周波数/2=命令サイクル)、アキュムレータ幅拡大(40bit→72bit)と演算処理性能がdsPIC33Cシリーズと比較し格段に向上しております。

(*1)パイプライン構造とは

パイプライン構造は、コンピュータのCPUにおいて命令の処理を効率化するための技術です。パイプラインは、命令の実行を複数の段階に分け、各段階を並列に処理することで、CPUの処理能力を向上させます。これにより、1つの命令が完了するまで待たずに次の命令の処理を開始することができます。

パイプライン構造の基本的な段階は以下のようになります:

  1. フェッチ(Fetch):メモリから命令を読み込む。
  2. デコード(Decode):読み込んだ命令を解釈し、実行するための準備をする。
  3. 実行(Execute):命令を実行する。
  4. メモリアクセス(Memory Access):必要な場合はメモリにアクセスする。
  5. 書き戻し(Write Back):演算結果をレジスタやメモリに書き戻す。

例えば、以下のように複数の命令を並列で処理することができます:

  • 命令1がフェッチ段階にあるとき、
  • 命令2はデコード段階に、
  • 命令3は実行段階に、
  • 命令4はメモリアクセス段階に、
  • 命令5は書き戻し段階にある。

このように、パイプラインは各命令が異なる段階にある状態を維持し、常にCPUがフル稼働するように設計されています。

しかし、パイプラインにはいくつかの課題もあります。例えば、条件分岐が発生した場合、どの命令が次に実行されるかが分からなくなり、パイプラインが停止することがあります(パイプラインハザード)。これを解決するために、投機実行や分岐予測などの技術が使用されます。

また、パイプラインの段階が増えると、1つの命令が完了するまでの時間は長くなりますが、並列処理によって全体の処理速度は向上します。このバランスを取ることが、パイプライン設計の重要なポイントです。

(*2)投機実行とは

CPUの投機実行(Speculative Execution)とは、プロセッサが命令を事前に実行し、実行結果を待たずに次の命令の実行を開始する技術です。この方法は、プログラムの実行を高速化するために使われます。

具体的には、CPUがプログラムの命令を逐次実行する際に、分岐(条件分岐やループなど)があると次に実行する命令が分からない場合があります。その際、CPUは以下のように動作します:

  1. 予測:CPUはどちらの分岐が選ばれるかを予測します。
  2. 投機的実行:予測した分岐の命令を事前に実行します。
  3. 確認:実際の分岐の結果が判明した時点で、予測が正しかった場合はそのまま実行結果を採用し、間違っていた場合は投機的に実行した命令を破棄して正しい分岐の命令を再実行します。

CPUのブロック図を下記に示します。従来のdsPIC33Cシリーズとは大きく構造が異なることが判ります。

(fig.3-1)CPUブロック図(dsPIC33AK128MC106 Family Data Sheet (microchip.com) p.33より抜粋)

CPU実行速度検証① 

100万回ループを繰り返し、ループカウンタが3の倍数で有る時にLED4をセットします。

ソースコード

#define LOOP_COUNT 1000000
int main(int argc, char** argv) 
{
	int i;   //CH はlong i
    /*----------------------------------------------------------------------------*/
    /* 初期化 */
    /*----------------------------------------------------------------------------*/
        vdg_Clock_Set_Register();
	TRISBbits.TRISB10 = 1u;
	TRISDbits.TRISD9 = 0u;
	TRISDbits.TRISD10 = 0u;
    /*----------------------------------------------------------------------------*/
    /* メインルーチン */
    /*----------------------------------------------------------------------------*/
        while(1)
        {
	  LATDbits.LATD9 ++;
	  for (i = 0; i < LOOP_COUNT; i++) 
	  {
		 if ((i % 3)== 0)
		 {
		   LATDbits.LATD10 = 1;
		 }
		 else
		 {
		   LATDbits.LATD10 = 0;
		 }
				
	  }
        }
        return (EXIT_SUCCESS);
}

結果

検証①結果dsPIC33CH
(90MHz Main Core)
dsPIC33AK
実行スピード(ms)
(コンパイラオプション = 0)
6560115
実行スピード(ms)
(コンパイラオプション = 1)
645068

CPU実行速度検証② 

条件分岐を使用して投機実行を誘発し、その結果を測定します。

ソースコード

#define LOOP_COUNT 1000000
#define ARRAY_SIZE 100

uint8_t array[ARRAY_SIZE];
uint8_t secret = 42;

void initialize_array() 
{
    for (int i = 0; i < ARRAY_SIZE; i++) {
        array[i] = 0;
    }
}

uint8_t access_array(uint8_t index) 
{
    if (index < ARRAY_SIZE) {
        return array[index];
    }
    return 0;
}

int main(int argc, char** argv) 
{
	int i;
    /*----------------------------------------------------------------------------*/
    /*初期化*/
    /*----------------------------------------------------------------------------*/
        vdg_Clock_Set_Register();
	TRISDbits.TRISD9 = 0u;
	initialize_array();
    /*----------------------------------------------------------------------------*/
    /*メインルーチン*/
    /*----------------------------------------------------------------------------*/
        while(1)
        {
	 LATDbits.LATD9 ++;
	 for (i = 0; i < LOOP_COUNT; i++) 
	 {
		 uint8_t index = (i & 1) ? secret : i % ARRAY_SIZE;
		 access_array(index);
	  }
        }
        return (EXIT_SUCCESS);
}

結果

検証②結果dsPIC33CH
(90MHz Main Core)
dsPIC33AK
実行スピード(ms)
(コンパイラオプション = 0)
3760195
実行スピード(ms)
(コンパイラオプション = 1)
88.85

CPU実行速度検証③

連続アクセスとランダムアクセスのパターンを比較し、キャッシュの効果を検証します。

ソースコード

#define ARRAY_SIZE 1024 * 2  //2kB
#define ITERATIONS 1000000

int array[ARRAY_SIZE];

// dsPIC33CHは int を longに置き換え
// 連続アクセス
void sequential_access() 
{
    for (int i = 0; i < ITERATIONS; i++) 
	{
        array[i % ARRAY_SIZE]++;
    }
}

// ランダムアクセス
void random_access() 
{
    for (int i = 0; i < ITERATIONS; i++) 
	{
        array[rand() % ARRAY_SIZE]++;
    }
}

int main() 
{
    /*----------------------------------------------------------------------------*/
    /*初期化*/
    /*----------------------------------------------------------------------------*/
        vdg_Clock_Set_Register();
	TRISDbits.TRISD9 = 0u;
	TRISDbits.TRISD10 = 0u;

    /*----------------------------------------------------------------------------*/
    /*メインルーチン*/
    /*----------------------------------------------------------------------------*/
        while(1)
        {

	 // 配列の初期化
	 for (int i = 0; i < ARRAY_SIZE; i++) 
	 {
	  array[i] = 0;
	 }

	 // 連続アクセスの計測
	 LATDbits.LATD9 = 1u;
	 sequential_access();
	 LATDbits.LATD9 = 0u;

	  LATDbits.LATD10 = 1u;
	 random_access();
	 LATDbits.LATD10 = 0u;
        }
        return (EXIT_SUCCESS);
}

結果

検証③結果 
シーケンシャルアクセス
dsPIC33CH
(90MHz Main Core)
dsPIC33AK
実行スピード(ms)
(コンパイラオプション = 0)
466150
実行スピード(ms)
(コンパイラオプション = 1)
22260

検証③結果 
ランダムアクセス
dsPIC33CH
(90MHz Main Core)
dsPIC33AK
実行スピード(ms)
(コンパイラオプション = 0)
644220
実行スピード(ms)
(コンパイラオプション = 1)
489120

CPU実行速度検証④

ソースコード

#define ARRAY_SIZE 1024 * 2  // 1MBの配列
#define ITERATIONS 1000000

uint8_t array8[ARRAY_SIZE];
uint16_t array16[ARRAY_SIZE / 2];
uint32_t array32[ARRAY_SIZE / 4];

void access_8bit() 
{
    for (int i = 0; i < ITERATIONS; i++) 
  {
        array8[i % ARRAY_SIZE]++;
    }
}

void access_16bit() 
{
    for (int i = 0; i < ITERATIONS; i++) 
  {
        array16[i % (ARRAY_SIZE / 2)]++;
    }
}

void access_32bit() 
{
    for (int i = 0; i < ITERATIONS; i++) 
  {
        array32[i % (ARRAY_SIZE / 4)]++;
    }
}

int main() 
{
    /*----------------------------------------------------------------------------*/
    /*初期化*/
    /*----------------------------------------------------------------------------*/
        vdg_Clock_Set_Register();
	TRISDbits.TRISD9 = 0u;
	TRISDbits.TRISD10 = 0u;
	TRISCbits.TRISC9 = 0u;
      // 配列の初期化
      for (int i = 0; i < ARRAY_SIZE; i++) 
    {
          array8[i] = 0;
          if (i < ARRAY_SIZE / 2) array16[i] = 0;
          if (i < ARRAY_SIZE / 4) array32[i] = 0;
      }
    /*----------------------------------------------------------------------------*/
    /*メインルーチン*/
    /*----------------------------------------------------------------------------*/
        while(1)
        {
       // 8ビットアクセスの計測
       LATDbits.LATD9 = 1u;
       access_8bit();
       LATDbits.LATD9 = 0u;

       // 16ビットアクセスの計測
       LATDbits.LATD10 = 1u;
       access_16bit();
       LATDbits.LATD10 = 0u;

       // 32ビットアクセスの計測
       LATCbits.LATC9 = 1u;
       access_32bit();
       LATCbits.LATC9 = 0u;

    }
    return 0;

結果

検証④結果 
8bitアクセス
dsPIC33CH
(90MHz Main Core)
dsPIC33AK
実行スピード(ms)
(コンパイラオプション = 0)
444110
実行スピード(ms)
(コンパイラオプション = 1)
21260

検証④結果 
16bitアクセス
dsPIC33CH
(90MHz Main Core)
dsPIC33AK
実行スピード(ms)
(コンパイラオプション = 0)
456140
実行スピード(ms)
(コンパイラオプション = 1)
21254.8

検証④結果 
32bitアクセス
dsPIC33CH
(90MHz Main Core)
dsPIC33AK
実行スピード(ms)
(コンパイラオプション = 0)
500145
実行スピード(ms)
(コンパイラオプション = 1)
21255.2

まとめ

コンパイル結果の詳細は今回は省略しますが、検証③④においては従来の約1/4の実行時間となり、純粋なCPUクロックスピードの違いが影響したと考えられます。 一方、検証①②ではMOD(%)を使用していたため、CPU速度だけでこの差が生じたわけではないかもしれません。

実際には割り込みなども影響するため、もう少し複雑な要因が絡んでいると思いますが、一つの参考値としてご覧いただければ幸いです。

記事についての注意点

本記事は慎重に内容を検討し正確さに努めておりますが、もし内容に誤りがあったとしても、この記事を参考にして生じた損害等については一切の責任を負いません。

コメント

タイトルとURLをコピーしました