CSAPP 第5章 プログラム性能の最適化
メモリエイリアシング
あるポインタの中身が他のポインタを指していること
int a = 2;
int *b;
b = &a;
インライン展開(インラインてんかい、英: inline expansion または 英: inlining)とは、コンパイラによる最適化手法の1つで、関数を呼び出す側に呼び出される関数のコードを展開し、関数への制御転送をしないようにする手法。これにより関数呼び出しに伴うオーバーヘッドを削減する。
最適化プログラミングその1
コード移動
void lower1(char *s)
{
long i;
for( i = 0; i <strlen(s); i++)
}
void lower2(char *s)
{
long i;
long len = strlen(s);
for( i = 0; i < len; i++)
}
lower1→lower2にすることで、for の繰り返し回数 × strlen
の処理を短縮できる
最適化プログラミングその2
不要なメモリ参照の削除
void combine3(data_t *data, data_t *dest)
{
long length = strlen(data)
for (i = 0; i < length; i++)
{
*dest = *dest * data[i];
}
}
void combine4(data_t *data, data_t *dest)
{
long length = strlen(data)
data_t acc = *data
for (i = 0; i < length; i++)
{
acc = acc * data[i];
}
*dest = acc;
}
計算をする際に毎回関節参照をすることで、mov命令などのメモリ参照系の命令が余計にかかる。
accumulateするための一時変数に結果を格納して、計算することにより速度が向上する
アウトオブオーダーユニット
アウトオブオーダー(Out-of-Order)は、プログラムに記述された命令の順番に関係なく、処理に必要なデータが揃った命令から実行する仕組みです。
インオーダーの場合は、命令順序に従って実行を行うが、アウトオブオーダーはメモリアクセスの時間などを短縮するために、アクセスなどが不要な命令から先に実行する。
そのときにメモリアクセスが必要な命令は並行でフェッチしておく。
分岐の場合においても、分岐命令がどちらかわかる前に実行をおこなう
ループアンローリング
ループの中身を展開して、プロセッサに並行演算をさせることで速度向上させる。
for(I =0; I < 10 ; I++) {
for(j = 0; j < 3; j++)
x(I, j) = m(I, j) * 10 + n(I, j);
}
↓
for(I =0; I < 10 ; I++) {
x(I, 0) = m(I, 0) *10 +n(I, 0);
x(I, 1) = m(I, 1) *10 +n(I, 1);
x(I, 2) = m(I, 2) *10 +n(I, 2);
}
レイテンシ限界
スループット限界
ハードウェアとしての限界。
スループット限界は並列処理によって改善することが可能
再結合変換
変数accに対して計算順序を変更することにより、メモリへのアクセスの依存性をなくし、並列処理を実現することができる
acc = (acc * 100) * data[i]
↓
acc = acc * (100 * data[i])
レジスタスピル
ループアンローリングによって並列化を確保することによりスループット高速化を実現するが、
並列処理がレジスタの最大数を超えてしまうと、レジスタはその並列処理した結果をスタック領域に格納するようになる。
これによりスタック領域から値を取り出す作業が必要となり、並列数が少ない処理の方が返って処理速度が高いという現象が起きてしまう。
これをレジスタ spill (もれ)という。
メモリ読み出しによるレイテンシ低下はアドレスを読み込んだ際に判断できるので、コンパイラ側がそれを認識することができない。
よって最適化などはできず、クリティカルパスになりやすい。
memcpyは高速にコピーするために必要な関数である。
gprofを使って関数毎に実行時間を表示する事ができる