Optimizing

98/05/12(火)  − Optimizing − 最適化のノウハウの誤解

 この前、アセンブリを使っての最適化について、 ちょっと誤解を招く(勘違いされる可能性のある?)記述を見付けた。 プログラムの経験のある人なら変な誤解をすることはないだろうが、 初心者ではあの記事を何も考えずに鵜呑みにし、本末転倒な最適化をする人 がいないとも限らないので、ここで取り上げることにした。

 さて、その記述によれば、 「速度を上げるためにはアセンブリで記述し、 とにかく1クロックでも削れる所があればすべて削って速くする」 とのことであった。
 もちろん、できるだけ クロック数を少なくしてスピードを稼ぐという点では別に間違ってはいない。 問題は、「アセンブリでコーディングし、とにかく クロック数を少しでも減らせるところがあれば、すべて減らしていくように プログラムすれば、スーパー最適化できる」 というようなニュアンスを含む 安直な書き方だったことである。 しかもほとんどそれしか書かれていない。 特に プログラムの初心者があの記事を鵜呑みにすると、 単純に書いてある通りに実践し、誤った最適化をする可能性がある。
 例えば、 10回ループの内側に1000回ループのような二重ループ処理 がある場合、 10回ループの部分を10クロック削るために、内側の1000回ループ内を1クロック 犠牲にしようなどと考えてしまうかもしれない。
 記事の書き方が、そもそも 目先のクロック数のみにこだわっており、マクロ的な視点を一切無視している のである。 あたかもそれが全てであるかのように。 しかもそこに例として載っていた最適化は、 アセンブリをある程度使える人なら誰でも考えられるようなレベルのものである。
 上の例では、まず10回ループの部分の重みと、その内側の1000回ループ内の 重みの差を知り、その上で全体のパフォーマンスが向上するように 最適化すべきであろう。 当然 10回ループの部分の1クロックと、その内側の1000回ループの部分の1クロックでは、 重みに1000倍の差がある ことを考慮しなければ意味がない。

 具体的な例を挙げよう。 100MHzのマシンで、例えば 100FPSのフレームレートが出ているゲームを 作成中だとする。 とりあえずディスプレイのリフレッシュレートとの同期などは考慮に入れないことに しよう。
 このメインのループ内全体のクロック数を計算すると、

例えば100MHz / 100FPS = 100万クロック

この場合だと、 メインのループ内の最も外側の部分で100クロック削っても 無意味である。 全体のパフォーマンスは0.01%程度しか変わらない。時間の無駄。
 しかし、このメインループの中に100回ループの二重ループ(100回を 100回繰り返す)があったとすると、 最も内側で10クロック削ることは非常に有意義である。 全体のパフォーマンスは単純計算で10%以上向上するからだ。 実際にはこうも単純にはいかないが...。
  「こんなことは極当然じゃないか」と感じる方は多いだろう。 しかし、まったくの初心者で、しかも 与えられた知識を鵜呑みにする人がいるならば、 あまり笑っていられないのもまた事実なのである。

 さて、ここから先は少々余談。
 現在のコンピュータのアーキテクチャは複雑である。 10年前なら、自分が組むプログラムがどのくらいのパフォーマンスであるのか、 コーディング中にしっかり把握できた。 しかし今はそうもいかない。 メモリキャッシュ、命令キャッシュ、パイプライン、分岐予測等々、 クロック数以外にもパフォーマンスを大きく左右し、それでいて 動きを完全に把握することなどできないような要因は数え切れないほどある。
 昔のように、ほぼ クロック数だけで判断することはもうできないため、 本当に有効な最適化も、ますます難しくなることだろう。

 いやあしかし、最近のマシンの速いこと速いこと。 Pentiumも、 浮動小数点の乗算なら3クロックで終了する。 私が昔(と言っても数年前まで現役で)使っていた マシン(CPU V30 8MHz)など、 整数の乗算でさえ100クロックくらいかかった。
 何が凄いって、クロック周波数だけでも30倍ほど差がある のだから、単純に考えるなら 最近のCPUの浮動小数点の乗算は、 昔のCPUの整数の乗算より 1000倍くらい速い.....................................................をいをい(^^;;