if文の制御式は最適化の対象になるのか?

解決


is_merry  2005-04-07 12:20:32  No: 56922

[環境]
WindowsXP Home, SP2
Visual C++.NET

--------------------
はじめましてis_merryと申します。プログラムの経験は1年程度の独学で
C言語で簡単なWindowsアプリケーションを作り始めたばかりです。

コンパイラの最適化について疑問があります。

ゲームプログラムを相手にしているので、ループをかけて何度も
条件判断させるようなことをしています。
こんなときに、例えば

/*-------------------------------------*/
int  hoge = 0;
int  piyo = 100;

if (hoge == 50 && piyo == 100)
    実行されました;
/*-------------------------------------*/
int  hoge = 0;
int  piyo = 100;

if (hoge == 50)
    if (piyo == 100)
        実行されました;
/*--------------------------------------*/

では、一見後者の方が式の評価の数が少なく高速なように思えます。
なるべく高速化したかったので、後者のようにしようかと考えました。

しかしコンパイラには最適化という機能があります。
私はこれを、どんなに汚いコードでもそれなりに高速に動くようにしてくれる
魔法の機能だと考えているようなふしがあります。

それで、もしかしたら上記のような些細な違いであれば
同じように直してくれるのではと考えたのですが、
実際は最適化とはどういったところまで関与してくれるのでしょうか?


匿名さん  2005-04-07 12:44:27  No: 56923

一般論では、言語の規格書で禁止されていないこと、
規定されていないこと、処理系依存でいいとされていることなどは、
いかようにも関与できます。

どこまでやるかはコンパイラ(の製作者)次第です。

言語規格は、例えば JIS X3014 (ISO/IEC14882:C++の規格書)をぐぐれば、
トップの cppll ログで閲覧サイトの情報が見つかるでしょう。

昨今のまともなコンパイラなら、提示の例程度はまず最適化されて
同じコードになります。

> if (hoge == 50 && piyo == 100)

もっとも、コンパイラ依存の最適化以前に、
提示の例では、言語レベルで挙動に差がありませんが。

この場合、左の式の評価で結果が確定する場合、
右の式は評価しないことが保証されてます。

if(foo != NULL && foo->bar())

例えばこういう使い方をしても大丈夫なように。
( foo が NULL なら bar はよばないので NULL アクセスにならない )

なので、書き方の好みの問題ですね。


is_merry  2005-04-08 10:07:24  No: 56924

> この場合、左の式の評価で結果が確定する場合、
> 右の式は評価しないことが保証されてます。

知りませんでした・・・、勉強になります。
冗長なやり方で、いくら効率がよさそうに思えても
誰もそれをやってないのには何かしらの意味があることが多いんでしょうね。

VC++ のヘルプを『最適化』で参照したら、いろいろと出てきたので
そちらの方も参考にしようと思います。

匿名さんありがとうございました。


RAPT  2005-04-08 10:28:16  No: 56925

そもそも、if文自体が負荷が高いので、if分を使わずに
対処できるアルゴリズムがあるなら、そちらを選択して
みるというのも1つの手段ではあります。

どちらが速いかは実際に計測してみる必要があるかとは
思われますが。

そういう意味で言うなら、
if( A && B ) …if文1つ…のほうが、
if( A )
    if( B ) …if文2つ…よりも効率がいいことになるのではないでしょうか。

# もっとも、これらはそうしろ、とも、そうであるとも
# 断言している訳ではないのであしからず。
## あくまで、そういう考えもできるのでは?  という提案です。


is_merry  2005-04-08 11:06:58  No: 56926

if 文を減らすことで、逆に効率が上がることもあるんですね。

ifを使わずに処理を変更すると言われて私に思い浮かぶのは、
関数へのポインタぐらいのものなんですが、ifよりはずいぶん高速なんでしょうか。

実行速度の計測をすることは、今までにあまり経験がなく
やるといったら処理の始まりと終わりで、時間を記録して
参照するといった程度です。
ですがこの方法だと、勉強に使っているコンピュータが無駄に性能が
良いせいで多少の差なんかは誤差レベルにしかならないかなぁ、と思ったりします。

実行速度を調べるツールもあるみたいですが・・・。

処理速度をこちら側で強制的に落とすようなことは出来るのでしょうか?
速度を落とすことができれば、時間計測における差も明確になるかな
と思いまして。


匿名さん  2005-04-08 17:25:14  No: 56927

> 実行速度を調べるツールもあるみたいですが・・・。

プロファイラ。

VC6のプロ以上ならついてるはずです。速度を落とす必要なし。
但しVC6はIDEのダイアログがバグってるので設定方法はぐぐってください。

VC.NET以降はなぜかついてません。
コンピュウェア社からどうぞ。


WIZ  2005-04-08 22:34:51  No: 56928

> ifを使わずに処理を変更すると言われて私に思い浮かぶのは、

既に匿名さんが仰っているように、C/C++ の組み込み型に対する && と || は
言語仕様でショートカットされるので、例えば

if (f() != 0) {
  if (g() > 5) {
    if (h() == 0) {
    ...;
  }
  }
}

なんていうのは、

f() && (g() > 5) && !h() && (...);

と書けると思います。
# 関係無いですけど、Perl では好んで使われる記法です。

> 処理速度をこちら側で強制的に落とすようなことは出来るのでしょうか?

何万回とかループして、その時間を計測して、後てループ回数で割れば1回
の時間を求める事ができますよね?


KING・王  2005-04-09 02:50:51  No: 56929

> 何万回とかループして、その時間を計測して、後てループ回数で割れば1回
>の時間を求める事ができますよね?

ループ内の処理内容によっては、コンパイラの最適化機能によっては、
ループ内の処理内容が飛ばされることもあったと思います。

また、関数呼び出しなどの場合、ループ内で同じ関数を繰り返してコールする場合、
2回目以降は、関数のコードがキャッシュされており、ループ回数で求めた1回の時間と、
実際のプログラムで1回だけその関数が呼ばれる時間との間に、
大きな差がでることもありますよ。

一応、上記のような内容もあることを頭のすみにでも入れておかれては。


is_merry  2005-04-09 12:46:10  No: 56930

> プロファイラ
調べてみます。 私は VC.NET を使用しているので、付属していないのは
ちょっと悲しいですね(--;

> 何万回とかループして、その時間を計測して、後てループ回数で割れば1回
> の時間を求める事ができますよね?
なるほど、簡単なことでした。
過去ログにも同じような指摘がありましたね。
速度を求めるときには、最適化の動きにも注意して計測しようと思います。

http://madia.world.coocan.jp/cgi-bin/Vcbbs/wwwlng.cgi?print+200503/05030005.txt

> f() && (g() > 5) && !h() && (...);
初めてみた手法です。
処理系に依存しているわけではないのですか?


RAPT  2005-04-10 18:16:41  No: 56931

>> f() && (g() > 5) && !h() && (...);
>初めてみた手法です。
>処理系に依存しているわけではないのですか?
ちゃんとレスを読みましょう。
匿名さんも、WIZさんも、規格で保証、言語仕様、と書いてありますよね?

C++のクラスなどでoperator && などをオーバーライドしていない限り、
その処理は有効です。

逆にいえば、int char などのP.O.D.型以外はその限りではありませんが。

もっとも、C/C++言語では、可読性という観点から、この手法は(最近では)さほど見られません。
if( foo != NULL && foo.bar() ) とかいったものはとくありますが。

例えば、ある整数が偶数か奇数かを判定したいとします。
下記 is_odd1 と is_odd2 は同じ処理結果を返します。

int is_odd1(int n)
{
    if( (n % 2) == 0 ){
        return 0;
    }else{
        return 1;
    }
}

int is_odd2(int n)
{
    return (n >> 1) << 1 == n;
}

is_odd1 では、剰余演算子 % と if 文が使われています。
is_odd2 では、高速なシフト演算子 >> , << が使われその結果をそのまま返します。
※C言語では、式の評価結果が真のとき、1、偽のとき、0 となることが
言語仕様で定められています。


is_merry  2005-04-11 01:26:08  No: 56932

>> f() && (g() > 5) && !h() && (...);
> ちゃんとレスを読みましょう。
> 匿名さんも、WIZさんも、規格で保証、言語仕様、と書いてありますよね?
本当だ。ごめんなさいm(_ _;)m
ちゃんと意味を汲み取るように今後気をつけます。

> シフト演算子
知らないだけで、色々な方法があるのですね。
横道にそれた質問まで回答していただき、申し訳ありませんでした。
勉強を続けます。


※返信する前に利用規約をご確認ください。

※Google reCAPTCHA認証からCloudflare Turnstile認証へ変更しました。






  このエントリーをはてなブックマークに追加