はじめに
既に気付いている人も多いかもしれないが、std::complex
の積和の計算を約20%高速化する書き方に気付いたのでメモしておく。今担当している組込リアルタイムDSPの時間制約がキツく、僅かでも高速化したいと思っていた。この方法は手軽さの割に効果が大きい。
問題と解決法
std::complex
の積和(例えばベクトルの内積)を計算するとき、普通は次のように書くだろう(x
,y
等の変数は適当に定義されているものとする)。
これだとx[i]*y[i]
の結果が一時オブジェクトとして生成され、それがsum
に加算されるという形をとり、一時オブジェクトの生成でstd::complex
のコンストラクタが走るのでコストが掛かる。
しかし、std::complex
は実部と虚部がメモリ上で隣接して配置されるという規格を利用し、浮動小数点数の配列(長さ2)として中身を直接扱うことで一時オブジェクトの生成を省略できる。
ループ中でのポインタ定義の実行時コストは次に挙げる理由で無視できる。
reinterpret_cast
はコンパイル段階で完結する処理なので実行時コストはない。float *const sum_ptr = reinterpret_cast<float *>(sum)
:sum
のアドレスがループ中で不変であることをコンパイラは知っているからsum_ptr
はループに入る前に解決され、ループ中での更新はない。sum_ptr
自体のアドレスをコード中で使用していないのでRAM上に配置する必要性が無く、おそらくレジスタに割り当てられる。x_ptr, y_ptr
はトリップ毎に変わるが、それ以外はsum_ptr
と同じ条件が成り立つのでおそらくレジスタに割り当てられる。
関数化
積和の計算の度に煩雑なコードを書くのは生産性・保守性において非効率極まりないので、強制的にインライン展開される関数として定義するのがよい。処理時間計測機能付きのサンプルコードを次に示す。
性能比較
次の表に示すのは、前掲のコードで処理時間を測定した結果である。繰り返し回数は結果を安定させるのに十分な値とし、10回以上実行した結果を平均した。最適化レベルは最高に設定している。
表の3行目は今使っている組込DSPであり、恐ろしいほどに効果が認められる。このプロセッサにはHWによるループ支援機能があるため、ループ内での一時オブジェクトの生成処理を取り去ったことで極めて高効率な機械語が生成されたものと考えられる。
HW | OS | コンパイラ | 繰り返し回数 | 処理時間 [ms] | 処理時間削減量[%] |
CPU: Core 2 Quad Q9650 RAM: DDR2 800MHz 8GiB | Windows 10 64bit | clang 11.0.1-2 | normal method: 411 special method: 330 | 19.7 | |
CPU: Core-i5 1035G7 RAM: DDR4 8GiB | Windows 10 64bit | clang 11.0.1-2 | | normal method: 161 special method: 127 | 21.0 |
CPU: TMS320C6748 RAM: DDR2 256MiB | TI-RTOS 6.83.0.18 | C6000 CGT v8.3.11 | | normal method: 763 special method: 191 | 75.0 |