0

単純なコードを最適化するためのいくつかの提案が必要ですが、高速である必要があり、高速とは250 ns未満を意味します。
私の最初のコードは遅く、約 1000 ns でしたが、いくつかの作業の後は約 550 ns になりましたが、より速く実行できると思いますが、方法がわかりません :<
80 MHz システム クロックの PIC32 を使用して
います私のコード:

void main()
{
    unsigned long int arr_1[4095]; 
    unsigned long int arr_2[4095]; 

    //here I assign arr_1 and arr_2 values
    //...
    //...

    TRISC = 0;
    TRISD = 0;

    while(1){
         LATC = arr_1[PORTE];
         LATD = arr_2[PORTE];
    }

}

仕事としては非常に単純であることがわかるように、唯一の問題は速度です。
命令の数を確認するためだけにアセンブリのリストを見ましたが、それを最適化するためのアセンブリ言語がわかりません。

;main.c, 14 ::      LATC = arr_1[PORTE];
0x9D000064  0x27A30000  ADDIU   R3, SP, 0
0x9D000068  0x3C1EBF88  LUI R30, 49032
0x9D00006C  0x8FC26110  LW  R2, 24848(R30)
0x9D000070  0x00021080  SLL R2, R2, 2
0x9D000074  0x00621021  ADDU    R2, R3, R2
0x9D000078  0x8C420000  LW  R2, 0(R2)
0x9D00007C  0x3C1EBF88  LUI R30, 49032
0x9D000080  0xAFC260A0  SW  R2, 24736(R30)
;main.c, 15 ::      LATD = arr_2[PORTE];
0x9D000084  0x27A33FFC  ADDIU   R3, SP, 16380
0x9D000088  0x3C1EBF88  LUI R30, 49032
0x9D00008C  0x8FC26110  LW  R2, 24848(R30)
0x9D000090  0x00021080  SLL R2, R2, 2
0x9D000094  0x00621021  ADDU    R2, R3, R2
0x9D000098  0x8C420000  LW  R2, 0(R2)
0x9D00009C  0x3C1EBF88  LUI R30, 49032
;main.c, 16 ::      }
0x9D0000A0  0x0B400019  J   L_main0
0x9D0000A4  0xAFC260E0  SW  R2, 24800(R30)  

コードを最適化するための提案はありますか?

編集:
*PORTE、LATC、および LATD は I/O マップされたレジスターです *PORTE が変更されたときに LATC および LATD レジスターをできるだけ速く変更するコードの目標 (したがって、PORTE は入力であり、LATC および LATD は出力です)、出力PORTEの値に依存

4

1 に答える 1

1

潜在的な制限要因はPORTELATCおよびLATDが通常のメモリではなく I/O レジスタであるため、I/O バス速度がメモリ バス速度よりも遅く、プロセッサがアクセス間に待機状態を挿入する可能性があることです。PIC32 の場合はそうかもしれませんし、そうでないかもしれませんが、どのアーキテクチャでも考慮する必要がある一般的なポイントです。

I/O バスが制限ではない場合、まずコンパイラの最適化を適用しましたか? このようなマイクロ最適化については、通常、最善の策です。このコードは簡単に最適化されているように見えますが、アセンブラーはそれを反映していないようです (私は MIPS アセンブラーの専門家ではありませんが、コンパイラーのオプティマイザーは専門家です)。

I/O レジスタは揮発性であるため、オプティマイザはループ本体を大幅に最適化すると失敗する可能性があります。しかし、それらは揮発性であるため、コードはおそらく安全ではありません。なぜなら、 が とのPORTE代入の間で値を変更する可能性がある (そして実際にその可能性が高い) ためです。その場合は、コードを次のように変更する必要があります。LATCLATD

int porte_value_latch = 0 ;
for(;;)
{
     // Get a non-volatile copy of PORTE.
     porte_value_latch = PORTE ;  

     // Write LATC/D with a consistent PORTE value that 
     // won't change between assignments, and does not need 
     // to be read from memory or I/O.
     LATC = arr_1[porte_value_latch] ;
     LATD = arr_2[porte_value_latch] ;
}

PORTEこれは、揮発性が一度だけ読み取られるため、安全で潜在的に高速であり、porte_value_latch値は毎回メモリから読み取るのではなく、両方の配列アクセスに対して一時レジスタに保持できます。通常のコンパイルでは最適化されなくても、オプティマイザはほぼ確実にレジスタ アクセスに最適化します。

for(;;)むしろthenを使用してもwhile(1)おそらくほとんど違いはありませんが、一部のコンパイラは不変のwhile式に対して警告を発行し、ビットはfor(;;)イディオムを静かに受け入れます。13 行目のコード アセンブラが含まれていないため、コンパイラが何を生成したかを判断することはできません。

LATCLATDが隣接するアドレスに配置されている場合は、さらに最適化の可能性がありますunsigned long long int。もちろん、64 ビット アクセスは依然として非アトミックですが、コンパイラはどのような場合でもより効率的なコードを生成する可能性があります。porte_value_latchまた、変数への参照が 1 つしかないため、変数の必要性もきちんと回避されPORTEます。ただし、LATCand をLATD特定の順序で記述する必要がある場合は、そのレベルの制御が失われます。ループは次のようになります。

for(;;)
{
    LATCD = arr_1_2[PORTE] ;
}

ここで、 のアドレスはLATCD隣接するLATCandLATDレジスタの下位アドレスであり、型はunsigned long long intです。LATC下位アドレスがある場合:

unsigned long long int LATCD = (unsigned long long int)LATC ;

そのため、LATCD への書き込みは LATC と LATD の両方に書き込みます。その後、Toy はarr_1anを適切な語順arr_2で の 1 つの配列に結合して、1 つの値に C と D の両方の値が含まれるようにする必要があります。unsigned long long

別の提案: >=4MHz のクロック信号からトリガーされる DMA を使用して PORTE を 1 つの場所に読み取るようにハードウェアを構成します。その場合、ループは PORTE を読み取る必要はまったくなく、DMA メモリ ロケーションを読み取るほうが高速である場合もそうでない場合もあります。また、ループが I/O をまったく実行しないように、メモリー位置から LATC/LATD を書き込むように DMA をセットアップすることもできます。この方法では、LATC と LATD が実際には隣接していなくても、「隣接メモリ」方法を使用できます。

最終的に、問題がコンパイラのコード生成だけにある場合は、インライン アセンブラでループを実装し、手動で最適化することが理にかなっている可能性があります。

于 2015-11-01T19:06:47.697 に答える