最初の投稿ですのでさすがにプログラミングに特化したことを書きたいと思います。
#パイプラインを考慮した処理の高速化
現在組み込みにおいてもアセンブラで書くことは少なくなってきたと思います。アセンブラは可読性が低くバグの温床になるからです。しかし信号処理など一部の処理のみをあえてアセンブラで書くとかなり高速化できることもあります。使用するプロセッサのパイプラインを理解し専用のコードを書くことで大量の処理を高速に行うことができます。
パイプライン、Threshold、Latencyについてはwikipediaに詳細に解説されていますので、ここでの説明は割愛します。
Interfaceの付録の冊子に主要なプロセッサのLatency,Thresholdの一覧が掲載されており大変便利です。
#実際のコーディング
まずpipelineを考慮しない例を示します。
VLD1.F32 D7, [R0]!
VMUL.F32 D24, D7, D1[1]
VLD1.F32 D8, [R0]!
VMUL.F32 D25, D8, D1[1]
VLD1.F32 D9, [R0]!
VMUL.F32 D26, D9, D1[1]
VLD1.F32 D10, [R0]!
VMUL.F32 D27, D10, D1[1]
VLD1.F32 D11, [R0]!
VMUL.F32 D28, D11, D1[1]
VLD1.F32 D12, [R0]!
VMUL.F32 D29, D12, D1[1]
VLD1.F32 D13, [R0]!
VMUL.F32 D30, D13, D1[1]
VLD1.F32 D14, [R0]!
VMUL.F32 D31, D14, D1[1]
これは極端な例ですが、ロードしたデータをすぐ次の命令で使用するためpipelineはstallし、上の行の命令が終わるまで待たされます。Latencyが10 stageの1GHzのプロセッサの場合、
11*8 = 88 step = 88ns
掛かることになります。
次にpipelineを考慮した例を示します。基本的にはストア先に同じレジスタを使わないようにし、また、ストアしたレジスタをすぐに使わないようにします。
VLDM R0, {D7-D14}
VMUL.F32 D24, D7, D1[1]
VMUL.F32 D25, D8, D1[1]
VMUL.F32 D26, D9, D1[1]
VMUL.F32 D27, D10, D1[1]
VMUL.F32 D28, D11, D1[1]
VMUL.F32 D29, D12, D1[1]
VMUL.F32 D30, D13, D1[1]
VMUL.F32 D31, D14, D1[1]
この例ですが、1行目から8行目まではストア先のレジスタ、ストア後の参照もないためpipelineで処理されます。同じくLatencyが10 stage、1GHzのプロセッサの場合、D31の結果が得られるまでに
10+7+10 = 27 step = 27ns
掛かることになります。
常にpipelineで処理できれば良いのですが、そうもいかないのが難しいところです。
・SRAM,DRAMとのRead/Write Latency.特にDRAMはburst lengthを考慮しないといけない。
これはCacheを駆使してなるべく外部メモリへのアクセスを行わないように工夫します。
・プロセッサのレジスタの数(pipelineを組みたくてもレジスタが足りないことがある)
・処理の内容によってはpipeline不可能な部分はかなりある。
これはもう仕方ありませんが、他の処理と並列に動かすことができたりすれば高速化の余地はあります。
開発終盤でCPUの性能不足に困ったときは一部の処理だけでも見直して書き直すとなんとか収まったりします。
#余談
ARMv7のR0からR15レジスタは仮想化されているため生のレジスタを示しておらず、プロセッサにより実際のレジスタにいい感じにアサインされるそうです。これを活用してARM純正C/C++コンパイラはパイプラインの恩恵を最大限引き出しているとのことです。こうなってしまうとプロセッサの癖が解らずアセンブラでは手出しできないので、悩ましいところです。