グリチャレのプログラムをさらに速くしようと,徹夜で研究室のドクターと一緒にプログラムを改良してみた.
ビット操作でちまちまやっていたのを一部バイト操作に置き換え高速化していたのだが,とりあえずそれを全体的にバイト操作にすることで倍以上のスピードアップを達成した.これに関しては誰もが思いつく単純な改良であり,これだけではつまらないと
少々壊れ気味の我々は続いてif 文を殲滅するという奇策に走った.よく知られていることだが,if 文とかでちまちまジャンプしまくるプログラムは遅くなる場合が多く,無駄な if を消すことは速度向上の上でとても好ましい(分岐したときにパイプラインで処理されてたものを破棄しなければならないし).
というわけで我々も無駄な if を探してみたのだが,既にかなり最適化したつもりのコードなのでそんな無駄な if はもう既にない.でもまあ,必要そうに見える if はまだごろごろいるわけで,そいつをどうにかして殺したい衝動に駆られるのは自然な流れである.そして,我々はいくつかの必要そうな if を愉快な方法で抹殺することに成功した.
抹殺例の一つは以下のような感じである.この次の if で分岐する部分が,
if(cnt < rem){ *p = (*p << cnt) | (((1<<cnt) - 1) * bit); rem -= cnt; } else { *p++ = (*p << rem) | (((1<<rem) - 1) * bit); cnt -= rem; rem = 8-cnt & 0x7; }
次のようなわけのわからないコードに置き換わった.
const int min = cnt - ( ( - 1 + ( ( unsigned ) ( cnt - rem ) >> 31 ) ) & ( cnt - rem ) ); rem -= min; cnt -= min; const int isRem0 = ( -1 + ( ( unsigned ) ( -rem + 1 ) >> 31 ) ) & ( -rem + 1 ); *p = ( *p << min ) | ( ( ( 1 << min ) - 1 ) * bit ); p += isRem0; rem += isRem0 * ( 8 - ( cnt % 8 ) );
これにより if 文が抹殺され,実際に実行時間も少しばかり(このルーチンのクロック数で半分くらい)速くなった.
ここでの肝は, signed の x に対して, max(x, 0) = (-1 + ((unsigned)x>>31)) & x のようにif を用いずに max を表現できることだったりする.上の例ではこれを改良して, x>0 なら 1 を,そうでないなら 0 を返す演算を記述している.
このように,あほな努力でちょっとでも速くてとても読みにくいコードが出来上がるのはとても面白い.
- Newer: ことはじめ