リンクフリーを近日中にとりやめる予定です

すでにリンクを貼っていただいている方、ご一報頂きたくお願い申し上げます。


ごく少数ですが、リンクをお断りする場合があります



ブログ内 風景光景カテゴリー

続編記事などをご希望の方は こちらへどうぞ

スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

[未掲載分] 「除算が遅い」の補足 (2)

今回も「除算が遅い」、「除算の高速化」などのキーワードで検索サイトからお越しいただいている方々への対応記事となります。

前話で、
・除算そのもの以外にも速度低下の要因が含まれていないか?
・除算を行う近辺の贅肉をそぎ落とせないだろうか
といった点を触れました。
除数が定数ならば、さらに高速化を狙えます。前話が長くなったため後回とした分を・・・


お知らせ
活動休止にともない、この記事を事前に予約投稿してあります。
トップ記事の固定を目的としています


※ これを下書きしたのは2012年夏の終わり頃です。
ご覧いただいている時期によっては状況が異なっている可能性があります。
下書きした頃の環境は、OS - Microsoft Windows 7、開発環境 - Visual Studio 2008 , C/C++ 言語。

前回の続きに入る前に、

分数でいうところの分子は numerator もしくは fraction 、分母は denominator の 単語が該当。よって、x = n ÷ d や x = n / d と記したいところです。ここでは話を簡単に進めるため A = B ÷ C



と表現します。それぞれ、

A 演算結果、商。
B 被除数、割られるほうの数。
C 除数、割るほうの数。

さて、過去記事除算が遅いにおいて、浮動小数点演算ならば
・分母が定数ならば「割り算」を「逆数の掛け算」に置き換えると良い
と述べた。今回は想定しているのは整数の除算。

整数の除算において除数が固定できる場合、除算命令を使わずに済ますことが可能。結論を急いでしまえば、
・定数の乗算 ( 掛け算 ) と シフトに置き換える
ことができる。厳密にいえば、符号なし整数と符号付き整数で異なるのだが、話を簡単にするため符号なし整数を基に話を進めよう。

A = B ÷ 10 を求めたいとする ( ただし、A , B , C それぞれが32ビット幅に収まる数値 )。
16進数 0xcccccccd を掛けて、35ビット分右方向にシフトした値と一致する。

「シフト」や「ビット」や「バイト」について苦手なヒトは過去記事遅くない除算をご参照あれ。



右方向にシフトとは下方向にシフトすること。1ビット分右シフトすることは「÷ 2」と同じ、逆に1ビット分左シフトすることは「×2」と同等。



少し機械寄りな話、前回記したことと重複する。
32ビット版のOS上でも64ビット版のOS上でも32ビット版アプリを動かしている場合、ひとつのレジスタ ( 演算器 ) の幅は32ビット。
B と 0xcccccccd を掛けた値は32ビット幅に収まりきらない可能性も生じ、正しい結果を得るには64ビット分の幅が必要。
通常、乗算命令の結果は上位32ビット分が EDX レジスタ、下位32ビット分は EAX レジスタに格納される。
ということで、乗算命令の結果のうちEDX レジスタを3ビット分右にシフトした値と「B ÷ 10」が一致する。

64ビット版のOS上で64ビット版アプリを動かしている場合、ひとつのレジスタ ( 演算器 ) の幅は64ビット。
「B × 0xcccccccd」の結果もひとつのレジスタに収まり、32ビット以上のシフト操作も容易・・・

参考までに
「÷ 100」は 0x51eb851f で掛けて、37ビット右シフト、
「÷ 1000」は 0x10624dd3 で掛けて、38ビット右シフト、
「÷10000」は 0xd1b71759 で掛けて、45ビット右シフト・・・

ほかにも、○○で掛けて△△ビットシフトするの組み合わせは多数。

さすがに覚えきれないし、表計算では縦方向に制限がある。



印刷して持ち歩くのも大変なので、アプリを自作した・・・



例えば、除数を「44100」と入力すれば、0xbe37c63bで掛けて、47ビット右シフトといった具合・・・



さてさて、「除算」を「乗算とシフト」に切り替えることでどれ位の効果を期待できるのだろう。
これを書いている時点で出回っている CPU の傾向を載せる。それぞれ、CPU 名、除算に要するクロック数 ( 待ち時間 )、乗算に要するクロック数、シフトに要するクロック数は概ね以下の通り。

CPU除算乗算シフト
Core i72542
Core 2 duo4051
Pentium480111

大雑把に言えば、待ち時間が60分から10分前後へと縮まるイメージ。
細かく言えば、このクロック数は前後のメモリアクセス等を無視している。前後のメモリアクセス ( ロード / ストア) や他の命令との絡みで状況も変わる・・・

・・・とここまでの流れでは
除算を高速に処理したい部分を「乗算とシフト」に置き換えることを推奨していると思われるかも。実際のところ、
コンパイラの賢さに依存
する。例えば、C/C++ 言語 で「A = B ÷ C」のままコードを書いたとしよう。

64ビット向けアプリにビルドした場合、ほどよく「乗算とシフト」に変換された実行コードが生成される。しかし、同じソースコードを32ビット向けアプリとしてビルドした場合、除算命令や __aulldiv を呼び出すようなコードが生成されてしまう。

個人的な感触では除数が一定の数値までは「乗算とシフト」が生成される。もしかしたら、「ここは速度重視ではないので、除算命令を生成しよう」とコンパイラが判断した可能性も否定できない。詳しくは両方をビルド後にアセンブリ言語の出力ファイルを比較すれば良い・・・

高速さを求める部分で「A = B ÷ C」ではなく、「○○で掛けて△△ビットシフトする」のコードを意図的に書くのは「除算命令を生成させない」という意味で有効。以下の点も頭に入れておきたい。

・コンパイラのバージョンアップにともない改善されてゆく。
・将来的には、除算の待ち時間がさらに短くなるよう、ハードウェア面で改良されてゆく。
・保守する際に複雑。
とくに多人数で作成に携っている場合、トリッキーなコードはトラブルのもと。オリジナルコードとなぜこのコードに置き換えたのか等のコメントを忘れないようにしたいものだ・・・

ではでは、除数 ( A = B ÷ C の C の値) がランダムな場合でも
「乗算とシフト」のテーブルを埋め込んでおくことで高速化できるのでは?
ちょっとビミョー。除数の範囲が狭いならば高速化を期待できそう。テーブルがキャッシュメモリに収まるか否かで速度が格段に違ってくる。

乗数とシフトカウントは4バイト + 1 バイトでパックできる。一般的な PC の構造を考慮した場合、4バイトの倍数にデータが整列配置されていないとメモリから値を読み取る時に遅延が生じる ( 可能性がある ) 。
とすれば、「乗数とシフト」ひとつの組み合わせで8バイト。ちなみに、これを書いている時点で、一般的な PC の L1キャッシュは 32768バイト。ということは、32768÷8で
4096通りまでは L1 キャッシュに収まるので高速化を狙える???

現行の一般的な OS は複数のタスクを切り替えて実行している。人間から見れば、ワープロや表計算や Web ブラウザーを複数同時に実行しているように感じるのだが、ごく短い時間毎に切り替えている。
ほかのアプリに切り替わる際、たいていL1 キャッシュの内容が変更されてしまう。おそらく、再び実行の順番が巡ってきた際、L1キャッシュに残っていないなら L2 キャッシュなどから読み直す。さらに L2キャッシュにもデータがなければ・・・

Core i7 以降はメモリアクセスが改善されています。メモリからのロードを含めても、除算命令を遂行するより待ち時間を短縮できるかもしれません・・・

続きは後日。
スポンサーサイト

本日も最後までご覧いただきありがとうございます。

「つまらなかった」「判り辛った」という方もご遠慮なくコメント欄へどうぞ

テーマ : プログラミング
ジャンル : コンピュータ

[未掲載分] 「除算が遅い」の補足 (1)

当ブログで過去に、



除算が遅い
遅くない除算
三角関数はもっと遅い
等の記事を掲載しました。その続編も準備していたのですが、諸事情により見送りました。
「除算が遅い」、「除算の高速化」などのキーワードで検索サイトからお越しいただいている方々への対応・・・


お知らせ
活動休止にともない、この記事を事前に予約投稿してあります。
トップ記事の固定を目的としています

アプリを作ってみたところ、
「除算が遅いので何とかしたい!」。その心意気、何かに挑もうとする姿は応援したくなる。
しかし、車輪の再発明 (1)から車輪の再発明 (11)辺りで述べた通り、何もかも高速化、最適化、チューニング、すれば良いというワケではない。最適化作業に掛かる労力、時間、費用が見合うか否かの見極めも肝心。
現状で提供されているハードウェアで時間が掛かるとしても、数年後に技術改良が施され高速に処理できるようになる可能性もある。
数年前まで一定の時間内でより多くの演算をこなせるかの方向で進化してきた。手元にある Intel の CPU を搭載した PC で試してみたところ、Pentium 4 から Core 2 Duo への進化で除算に要する時間 ( 待ち時間 )は約半分、Core 2 Duo から Core i7 では 33%減と改良されている。
ここ数年は、高速性の追求はひと段落し、省電力、小スペースの方向へ進化を続けている・・・

※ これを下書きしたのは2012年夏の終わり頃です。
ご覧いただいている時期によっては状況が異なっている可能性があります。
下書きした頃の環境は、OS - Microsoft Windows 7、開発環境 - Visual Studio 2008 , C/C++ 言語。

過去記事除算が遅いにおいて、浮動小数点演算、整数演算ともに
・乗算は遅くなりにくい。
・除算は加算、減算、乗算に比べ数倍、数十倍の時間を要する。

このことから、
・分母が定数ならば「割り算」を「逆数の掛け算」に置き換えると良いと綴った。
実際のところ、近代的な開発環境を利用しているのであれば、コンパイラが自動的にこの辺の置き換えを行ってくれる・・・

ところで、検索サイトよりお越しの方々の探している情報は
「アプリの実行速度を上げたい」
といったところだろうか?
ついつい、「高速化のために何かを加える」と考えガチである。その「何か」に当たるエッセンスや裏技的なモノを探したくなる気持ちも判らなくはない。その方向性ならば、アプリの改修に四苦八苦するのではなく、資金を惜しまず、最新鋭かつグレードの高い機種を導入すれば悩みは解消するだろう。

それよりも、「阻害要因の除去」に視点を切り替えてみよう。悩みを解消するカギが見つかるかもしれない。
除算命令の遂行そのものが遅い。それ以外にもアプリの実行速度を低下させる要因があるのではないかを探り、「速度低下のもとを削る」と考えれば道が開けるかも・・・

OS やアプリが32ビットなのか64ビットなのか、さらに浮動小数点演算なのか整数演算なのかでそれぞれ話が違なる。今回は32ビット環境、整数演算を前提に話を進める。

除算の話に入る前に、「32ビットって何」というヒト向けに補足。「ビット」「バイト」に関しては過去記事 遅くない除算 で触れていますので省きます。

PC を購入する際、カタログのスペック ( 仕様 ) 覧を眺めていると、CPU の種類や搭載メモリ容量などが載っています。PC でいう「CPU」とは自動車でいえばエンジンにあたる部分。CPU の項目にはCore i7 、Pentium や ATOM , 少し前ならば Core 2 duo などと表記されていることでしょう。これらの CPU は Intel 80386 の流れを受け継ぐプロセッサー。



Intel 80386 ( 以降「i386」と記す ) は 西暦1985年頃登場、i386 の 2世代後がPentium、4~5年後に後継の Pentium II と廉価版の Celeron が登場しました。現在でも Pentium や廉価版 ( 機能削減版 ) の Celeron といった 名称が受け継がれています。

CPU の進化の流れに関しては車輪の再発明 (8)でも触れましたので興味のある方はご参照あれ。

i386 の登場により、レジスタ( 演算器 ) が32ビットに拡張された。32ビット幅の数値を扱いやすくなった。
「1ビット」が「0か1の2通り」、つまり「32ビット幅の数値」とは「2×2×2×...2」と32回掛け算した値。符号なし整数ならば 0 から 4294967295、符号付き整数 -2147483648から+2147483647までを簡単に扱えるようになった。
大雑把に書くとすれば、「プラスマイナス21億までの計算がしやすくなった。」と言ったところ。

それまでのPC、いわゆる「16ビットCPU」 ではプログラミングの際、16ビット幅の数値を標準としていた。
16ビット幅の数値というのは、符号付き整数 -32768 から + 32767、符号なし 0から65535を扱える。実際の給与計算などを想定した場合、この数値幅では足りない。
では、「16ビットCPUが搭載されていた頃の PC で16ビット幅を超える数値を扱えない???」という誤解も生じやすい。
32ビット幅の数値であれば16ビットずつ上位、下位に分けて扱うことは可能であった。
ちなみに、「i386 が64ビット幅の数値を扱えるのか否か?」も同様で、ひとつのレジスタでは扱えないが上位、下位に分けて扱うことは可能である。

上位、下位に分けるという表現が難しいかもしれない。例えば、スイカ4玉を運ぶように頼まれたとしよう。旧来は2玉ずつ2回に分けて運んでいた。16ビットから32ビットへ拡張されたことで4玉を一度に運べるようになった。
同様に、32ビットから64ビットへ拡張され「スイカ8玉を一度に運べるようになった」。・・・「4トントラックで1往復で運べる」か、「2トントラックで2往復するか」の違いと表現したほうが判り易いかも・・・

演算結果は正しいのか???

さてさて、スイカを運ぶだけの話ならばここで終わり。わざわざ「32ビット幅」や「上位」「下位」「有効桁」など遠回りに思えるかもしれない。32ビット環境では「桁あふれ」への対処部分が速度を落とす ( 遅くなる ) 原因となりうる。

例えば 0から99の2桁(100通り)の数。2つの値で掛け算した場合、最も小さいのは0、最大は 99×99 = 9801。
もし、下2桁しか表現できなければ、上2桁は無視される(無効になる)。
66 × 77 の答えは5082。しかし、下2桁しか扱えない ( 上2桁が示されない ) ならば「82」となり不正解。

人間なら多少の融通が効く。現在西暦2012年だとして、日常的な会話の中で「95年」と出てくればおそらく「西暦1995年のこと」と推測できるし、「10年の春」と聞けば「平成10年の春」もしくは「西暦2010年の春」のどちらかを会話の前後から判断できる。しかし、機械はこの辺が苦手。

過去記事、2012年12月17日分で、アナログ的な体重計、1周100kg を例に挙げた。



・体重 130kg の立派な体格のスポーツ選手
・体重 30kg の小学生



どちらが体重計に乗っても針は 30kg をさす。
人間の目で考えれば、子供なのか立派な体格のヒトが乗っているのかは区別が難しくない。ところが、機械にとっては両方の区別が難しい・・・

典型的な式を使って考えてみよう。


Aが「66」Bが「77」、Cが「44」と仮定。A×Bの段階で「5082」、それを「44」で割れば「115と余り22」が正しい。もし上位桁を正しく処理できない場合、A×Bの段階で「82」、「82÷44」の答え「1と余り38」となってしまう。
桁あふれへの対処を怠ると正しい結果を得られないことがある・・・

演算結果の上位はEDXレジスタ、下位はEAXレジスタ。
(1-1) 下位32ビットを指すEAXレジスタに66、上位32ビットを示すEDXをゼロにする。
(1-2) EBXレジスタに「77」を入れ、EAXレジスタと乗算。
(1-3) ECXレジスタに「44」を入れ、EDX:EAX レジスタ内の値(被除数)をECXレジスタで割り、結果EDX:EAX レジスタにストアする。
もし、アセンブリ言語を理解できるレベルのヒトであれば、の3ステップで通じるハズ。MOV 、MUL 、DIV の3種類 (ゼロクリアに XOR 命令 を使うとしても4種類の命令) を用いて「A×B÷C」となる。

おそらく、検索サイトからお越しの方々はアセンブリ言語を理解できるレベルまで到達していないことだろう。さらに、Visual Studio で 64ビット用のアプリ作成する際にはインラインアセンブラが利用できない。
少し緩めて、C/C++ 言語でも通じるように進めよう。

「A×B」の結果が32ビット幅を超えてしまう可能性がある。さらに、「÷C」の結果も32ビット幅に収まらないかもしれない。
64ビット版のWindows が登場する前、つまり32ビット版のOSで32ビット版のアプリを作成することが前提だった時代でもこの辺は考慮されていた。
教科書的な書き方をすれば、C/C++ 言語で32ビット版のアプリで64ビット整数型 (64ビット幅の数値) を扱うには long long や unsigned long long を用いる。

過去記事 車輪の再発明 (5)車輪の再発明 (9)でも触れた通り、Microsoft の Visual Studio に限定すれば LONGLONG や ULONGLONG、LONG64やULONG64などの変数型を用いることも可能である。さらに、LARGE_INTEGER や ULARGE_INTEGER といった構造体も容易されている。



リトルエンディアンを想定して下位、上位といった並びになっている。
LARGE_INTEGER の 使い方も難しいところはない。32ビット幅に収まる数値、66を代入するしたいならば
LARGE_INTEGER li;
li.HighPart = 0;
li.LowPart = 66;

LARGE_INTEGER li;
li.QuadPart = 66;

と書くことができる。

さらに 77 で掛けて、44で割りたいならば
ULONG32 uB = 77 , uC = 44;
li.QuadPart = li.QuadPart * (ULONG32) uB;
li.QuadPart = li.QuadPart / (ULONG32) uC;

と書ける。
実際のところ、近代的な開発環境を用いる限り、定数での除算はコンパイラが適切な乗算に置き換えてくれる。



「将来の移植を考え、LARGE_INTEGER 構造体を用いるように」と提唱されていた覚えがある。が、いつ頃からだったのか定かではない。Visual studio 6.0 付属のヘルプディスクを漁ってみたところ、Windows NT 3.1 やWindows 95 以降となっていた・・・

そろそろ、本題。削減できる可能性が高い部分を探るため、32ビット向けに生成されたアセンブリ言語コードと64ビット向けに生成されたコードを比べる。詳しくないヒトのために補足すると、「機械が直接理解できる命令」を診ることで遅くなる原因を探る。

アプリをビルドする際、アセンブリ言語ファイルを出力する方法は車輪の再発明 (7)で記したので省く。

「A」「B」「C」はそれぞれ32ビット幅の範囲の数値、ただしゼロではない。「C」の値は毎回変わるとする。
「A×B÷C」の「÷C」の部分がどのように翻訳、機械が直接理解できる命令に変換されるのかを確認してみよう。

まず、32ビット環境向けに生成されたコード。



次に64ビット環境向けに生成されたコード。



64ビット向けのコードは 数値を代入する mov と divでシンプル。
xor edx,edx は mov edx,0と同等。作業台の上に移動するのが mov 、div で割り算を行っている。

それに比べ、32ビット版のコードはシンプルとは言えない。
数値を代入する mov 命令ではなく、push ~~、割り算を意味する div 命令の部分は call ~~となっている。
push の部分は作業台の上がいっぱいなので、棚や倉庫など別の場所に置き換えるようなもの。call 命令はサブルーティンを呼び出す。
表現が適切かビミョウだが、64ビット向けのコードは自前で処理、32ビット向けのコードは外注するくらい意味が異なる。

ちなみに、MSDN などで __aulldiv を検索しても欲しい情報に辿り着けない可能性が高い。
__aulldiv の部分がどうなっているのか知りたいならば、Visual Studio をインストールしたフォルダー内にソースファイルが含まれている。



拡張子「.asm」でファイルを検索を行い、該当したファイルの中から「lldiv.asm」や「ulldiv.asm」を開いてみると参考になる。
これらのファイルはアセンブリ言語で書かれている。アセンブリ言語を読み解くのは難しいかもしれないが、コメント部分を読めば概要は把握できるハズ。

64ビット環境であれば32ビット幅の数値の除算が div 命令ひとつで済むのに比べ、条件比較や移動、多くの分岐処理が行われる。
「lldiv.asm」や「ulldiv.asm」を眺めるかぎり工程数が多く感じてしまうが、32ビット環境において、より正しい結果を得るために最低限必要な工程。さらに、サブルーティンの呼び出し、戻りも実行コストが増える・・・

__aulldiv を呼び出さない、直接 div 命令に翻訳されるようにコードを修正する
ことで手間を軽減できる可能性が高い。
判り辛い表現かもしれない。例えば、書類のコピーが必要だとしよう。職場、作業場で身近にコピー機が備わっていない。その場合、「ちょっとコンビニまで出かけて・・・」となる。一日に一・二回なら休憩も兼ねてコンビニまで出かけるのも良いだろう。しかし、何回も繰り返すならば、身近にコピー機が備わっていることに比べ手間が増える。その手間を削る策を練るということだ。

全ての除算が __aulldiv を呼び出すように翻訳されるのではない。被除数、つまり「A×B」の結果が32ビット幅に収まりきらない可能性があるとコンパイラが判断したことにより __aulldiv を呼び出すコードに翻訳される。
逆にいえば、演算結果が32ビット幅に収まることが明確ならば除算部分で __aulldiv を呼び出すコードの生成を避けられそうだ。

例えば、「A」「B」の数値がともに16ビット幅、と決めうちできれば「A×B」の結果が32ビット幅に収まる。
そのような場合 64ビット幅の LARGE_INTEGER 構造体 を用いる必要は無い。「A」「B」は short や unsigned short 型 で扱い、「A×B」の結果は int や unsigned int 型 へ納めるば良い。

ほかにも24ビット幅と8ビット幅の数値の掛け算などでも良いのだが24ビット幅の数を扱うには少々複雑なので今回は略。
要は、被乗数と乗数 ( 掛けられる方の数と掛けるほうの数 ) のとりうる値のビット幅を足して32を超えなければ良いのだ。

人間が見て演算結果が32ビット幅に収まる場合でも、コンパイラのご機嫌や賢さに依っては__aulldiv を呼び出すコードに翻訳されることもある。
それだけではないが、人間が「ここは○○」が明確と判断できる部分でもコンパイラには判断できないケースも生じる。
オプティマイザによる最適化が足りない。まだまだ最適化の余地が残ってしまう。オプティマイザとは翻訳の後、最適化を行う担当者といったところ。

開発環境が Microsoft の Visual Studio で C/C++ 言語を用いている場合に限定して言えば、__assume キーワードを挿入することで改善される可能性が高い。詳しくは__assumeをご参照。
MSDN では switch ステートメントの例を取り上げているハズ。無駄な条件比較、分岐が削減される。
ほかにも、ポインタのNULL チェックを削減したい、演算前の条件比較を削減したい、for や whileなどループ部のカウンター初期値のチェックを省きたいといった部分で __assume キーワードを挿入することにより、より最適化されたコードが生成される。

__assume キーワードはMicrosoft 以外の コンパイラでは利用できない。ほかの開発環境での利用を考え
#if defined(_MSC_VER) && (_MSC_VER >= 1000)
__assume( 条件式 )
#endif // defined(_MSC_VER) && (_MSC_VER >= 1000)

のように使うのが無難である。

まれに、for 文の前に __assume キーワードを追加しても無駄、効果が無いといった記述を見かける。筆者が眺める限り、使い方が間違っているだけに思える。__assume キーワードの位置が合っていないと無駄になるの間違いでは・・・

#if defined(_MSC_VER) && (_MSC_VER >= 1000)
__assume(i>0);
#endif // defined(_MSC_VER) && (_MSC_VER >= 1000)

for(ULONG i = XXX ; i ; i --){~~~

こんな書き方だと __assume キーワード が無駄になる。理由はカウンターとなる変数の定義位置。
ちょっと手直しして、カウンターとなる変数をfor の外で定義します。
ULONG i = XXX;

#if defined(_MSC_VER) && (_MSC_VER >= 1000)
__assume(i>0);
#endif // defined(_MSC_VER) && (_MSC_VER >= 1000)

for(; i ; i --){~~~

筆者の環境ではこのように修正することで、ループ部のカウンター初期値のチェックを削減できました。
適切な使い方をするかぎり、Visual C++ 6.0でも __assume は無駄ではありません。

証として、Visual C++ 6.0 の生成したアセンブリ言語コードを載せます。
まずは、__assume キーワード の位置が誤っていると思われる方。



次に、__assume キーワード の作用により最適化が向上したコード。



パッと見ただけでも短くなっているのが判るのではないでしょうか。
興味があるヒト向けに補足しますと、「$L128968:」がループ部分の先頭、前者の00031でゼロか否かの比較分岐があります。一方、後者は「$L128968:」直前のゼロ比較が取り除かれました・・・

ここまで飲み込めれば、他にも削減できそうな部分に気が付くことでしょう。先の「A×B÷C」で考えれば、いずれの数値もゼロでないことが明確ならば 先ほどの __assume キーワード を用いることで無駄なコードを削減できるハズ。

今回は「C」の値が変わる前提です。よって、変数の値がゼロになることも想定されます。どれかの変数がゼロならば演算する必要はありません。
厳密には、「C」がゼロならゼロ除算エラーの対処コードを書きます。しかし、今回は話を簡略化するため「C」の値がゼロの場合でも演算結果ゼロを返すとして話を進めます。

演算の前に各変数のゼロチェックを行うこと。いずれかがゼロなら乗算と除算をスキップ させます。

教科書的な書き方をすれば、条件判断の部分は
if (A== 0 || B == 0 || C == 0) ~~~
もしくは
if (A!= 0 && B != 0 && C != 0) ~~~
といった感じでしょうか。それぞれ
if (!A || !B || !C) ~~~

if (A && B && C) ~~~

と書いても同じ意味です。
ビルドしてこの部分のアセンブリ言語コードを確認すれば、ゼロ比較と分岐が3組並ぶハズ。

書籍やWebサイトに「高速化のために条件判断や分岐を減らそう」と載っていると思います。
この例も工夫することで分岐を2回分減らせるかもしれません。

例えば、論理演算のうち 論理積 ( AND 演算 ) が役に立ちそうです。
この辺の話題が苦手なヒトのために書けば、2つの変数の値のうちどちらかがゼロならば演算結果はゼロ。

if (A && B && C)・・・


if (((A & B) & C) != 0)・・・
などに変更します。

「A」と「B」のAND値と「C」のAND値を取得しゼロと比較。ゼロでないならば~~~を実行する・・・となります。
どれかひとつでも変数の値がゼロであればAND 値がゼロになります。これでゼロ比較と分岐は1回で済むハズ。

ここまでは除数が固定されない場合のお話。除数が定数の場合はもっと絞り込めます。

続きはまた後日・・・

本日も最後までご覧いただきありがとうございます。

「つまらなかった」「判り辛った」という方もご遠慮なくコメント欄へどうぞ

テーマ : プログラミング
ジャンル : コンピュータ

検索サイトからお越しの方へ
検索サイトからお越しの方は、ブラウザのアドレス欄vitalaboloveおよび、fc2.comが含まれているかご確認ください。
含まれていない場合、偽サイトを閲覧なされている可能性があります。

偽サイトは、当ブログの文字部分や画像部分が有害サイトへのバナーと置き換わっているようです。
プロフィール

Author:Vitalabolove
ご訪問ありがとうございます。
店長を任されておりますVitalaboloveです。

コメントはお気軽に。
今のところリンクフリーですが、あと数日でとりやめます。

画像データ、文言の引用は事前連絡くださるようお願い申し上げます。事前連絡の際は、左下、メールフォームを経由をご利用ください。

最新記事
カレンダー
06 | 2015/07 | 08
- - - 1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31 -
カテゴリ
ランキング
いつも応援いただきありがとうございました。ただいま休養中につきランキングへ参加していません・・・

フリーエリア
内緒話などはおきてがみをご利用ください。
月別アーカイブ
メールフォーム
掲載された記事について、ご不明な点はここからお問い合わせください

名前:
メール:
件名:
本文:

最新コメント
最新トラックバック
スパムと思われるトラックバックは削除しました
QRコード
QR
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。