Pentium4 では , 丸めモードの変更や中 級編で紹介した丸めモードの影響を受けな いアルゴリズムを使用するよりも SSE , SSE2 の切り捨てを使用して浮動小数点を整 数に返還するコストは大幅に小さくなりま す。 プロセッサの判別 MMX, SSE , SSE2 , 3DNow! などの拡張 インストラクションは比較的新しい命令で Fig. 11 バージョン情報のフォーマット 1413 12 1 1 す。これらの命令は昔のプロセッサで利用 することができないため , まずはプロセッ サ自体が拡張インストラクションに対応し ているかどうかをチェックしなければなり ません。利用できないプロセッサで拡張イ ンストラクションを含むコードを実行しよ うとすると確実にエラーが発生します。も し , 拡張命令を利用したソフトをあなたの アプリケーションに組み込みたい場合は , アプリケーション起動時などにプロセッサ の判別を行うようにします。 31 プロセッサ・タイプ EAX モデル ( 0001B から始まる ) ファミリ (Pentium P 「 0 プロセッサ・ファミリの場合は 0110B ) Fig. 12 cpuid. exe の実行結果 0 P U Vender: Genu inehte に Fa m i レ : 6 ModeI: 8 Stepp ID: 6 Supported 0 P UID: Yes S 叩 ported M MX Yes Supported SS E: Yes S 叩 ported SS E2: NO S 叩 ported 3DN0 : NO S 叩 ported Enhanced 3 D Now! : NO 8 7 4 3 0 ファミリモテルステッピング ID 36 31 30 29 28 27 26 25 24 23 22 21 20 19 1 8 17 16 1 5 14 13 12 1 1 10 9 8 7 6 5 4 3 2 1 0 ・■亜聞 0 側 0 則聞則 SSE2—ストリーミング MD 拡張命令 2 SLFSNP—セルフ・スヌープ AMD Enhanced 3DNow! AMD 3DNow! Technology EDX CMOV 一条件付き転送および比較命令 PAT ーベージ属性テーブ丿レ PSE—ページ・サイズ拡張 PSN ープロセッサ・シリアル番号 CLFSH—CFLUSH 命令 予約済み DTES—デバッグ・トレース / イベント・モニタ AC 曰ープロセッサ・パフォーム監視 MMX—MMX テクノロジ FXSR—FXSAVE/FXRSTO 日 SSE—ストリーミング MD 拡張命令 MCA—マシン・チェック・アーキテクチャ C MAGAZINE 2g1 10 FPU—オンチップ FPU VME ー仮想 8086 モード強化 D E ーデバッグ拡張 PSE—ページ・サイズ拡張 TSC—タイム・スタンプ・カウンタ MS 日一日 DMS 日および W 日 MS 日サポート PAE—物理アドレス拡張 MCE—マシン・チェック例外 CXB—CMPXCHG8B 命令 AP ℃ーオンチップ AP ℃ 予約済み SEP—SYSENTER および SYSEXIT MTRR—メモリ・タイプ範囲レジスタ PGE—PTE グローバル・ビット
中級編 X86 命令 , X87 命令 , MMX 命令を 用いたプログラムの高速化 初級編のまとめで , アセンプリ言語についてそうとうお どしてしまいましたが , アセンブリ言語で記述すること で得られる利益もたくさんあります。使い方しだいでは , C 言語で高速化したプログラムをさらに高速化できるよ うにもなります。ともあれ , この中級編ではアセンブリ ロ語 ( 主に X86 命令 , X87 命令 , MMX 命令 ) を用いたプ ログラムの高速化について説明していきます。 実行環境 筆者の開発・実行環境は次のとおりです。 ・プロセッサ : Pentium 41.7GHz Pentium Ⅲ 500MHz ・メモリ : 256M ノヾイト ( Ⅲ MM -800 ) 128M バイト (DIMM-IOO) ・ソフトウェア : Windows 2000 SPI V1sual C + + 6.0 Professional SP4 V1sual C + + 6.0 Professional Processor pack IntelVTune Performance Analyzer 5.0 この環境が絶対に必要ではありませんが Windows 98 もしくは NT4 (SP4) 以降の環境 と , なるべく新しい CPU ( 高速である必要 はない ) を用意してください。また , 中級 編の最後で説明する MMX 命令を使ったプ ログラムの実行には Pen ⅱ um MMX 以上が 必要です。さらに上級編では SSE 命令を使 用できる Pentium Ⅲ ( 新しいタイプの Celero n でも可 ) 以上が必要です。それぞれの命令 を実行できる環境かどうかは , 付録 CD-RO M に収録の Cpuid. exe を実行してみてくださ こからはインテル系のプロセッ なお , 26 C MAGAZINE 2001 10 サがターゲットとなりますが , インテル系 以外のプロセッサでマイクロコードを使っ て最適化する場合にもヒントになることが あるはずです。 インラインアセンブラ アセンプリ言語を記述するには「アセン プラ」が必要になります。有名なところで はマイクロソフトの「 MASM ( マクロアセン プラ ) 」などがあり , ( マニアックな店以外 ではあまり見かけることはないかもしれま せんが ) パソコンショップなどで販売され ています。しかし Visual C + + をはじめ , 多 くのコンパイラは「インラインアセンプラ」 というモノを使うことができます。「インラ インアセンプラ」とは , コンパイラの機能 の 1 っとしてアセンプリ言語とほば同等の 記述ができるものです。要するに , アセン プラをわざわざ用意しなくてもアセンプリ 言語のプログラムを書けることになります インラインアセンプラの記述は , 一般的に 命令の前に —asm のキーワードを付けます。インラインアセ ンプラは , 一般のアセンプラに慣れたプロ グラマからすると気がきかない部分が多少 あることも事実ですが , ちょっとしたアセ ンプリ言語のプログラムを書きたいときに は非常に便利です。 また , マイクロソフトからリリースされ ている Visual C + + 6.0 Processor pack (http:/ /www.microsoft.com/japan/developer/vstu dio/download/ppack/ から無料で入手でき る ) というⅥ sualC + + 6 用の拡張モジュール を導入すると , Pentium Ⅲからサポートさ れた Streaming SIMD Extension (SSE) , Pe ntium 4 から導入された Streaming SIMD Ex tension 2 (SSE2) と AMD の 3DNow! テクノロ ジといった拡張命令 [ 第” 06 ] のアセンプリコー ドが記述できるようになります。ちなみに lntel C + + 5.0 コンパイラであれば同社の命 令セットは ( 当然ですが ) すべて標準サポー トされており , 拡張命令の組み込み関数も 使用することができます。 X86 系プロセッサ ()A -32 ) 「 X86 」という言葉を知らなくても「 Pen ⅱ u m 4 」はおそらくご存じでしよう。インテル が誇る優れたアーキテクチャを備え持つパ ソコン用のプロセッサです。 Pentium4 も x8 6 系のプロセッサです。正式には「 32 ( 32 ビットインテルアーキテクチャ ) 」と呼びま す。 インテルは日々 CPU を進化させてきまし たが , 命令系統は旧世代のもの ( 80X86 ) と 互換性を保っています。つまり昔作られた プログラムの資産を今日でも利用すること ができます。それが X86 系のプロセッサと 呼ばれるゆえんです。 X86 は厳密にいうと整数演算の処理を担 当するコアを指します。浮動小数点の演算 は X87 というコプロセッサが担当します。「 x 86 系」 , 「 X87 」 , 「い 32 」など , 書き方がコ ロコロ変わるとややこしいので , 便宜上こ れ以降は基本的にこれらのプロセッサのこ とを総称して X86 と呼びます。浮動小数点 を説明する部分では X87 と記述します。 本特集は , これよりこの X86 系列のプロ セッサをターゲットとします。
特集工 加殤ムの高速化・最適化 またプロセッサ以外に OS がそれらの命 していても , OS がそれに対応していない可ません。 令に対応しているかどうかも調べなければ 能性があるからです。たとえはⅲ dows95 プロセッサの判別は難しくありません。 なりません。プロセッサが拡張命令に対応 では通常 SSE の命令を実行することができプログラムを順番に眺めていくとわかりま すが , まず CPU の ID をチェックする命令で CPUID 機能が使えるか ユーザが使用している環境で利用できるか どうかを調べます。プロセッサの種類は基 本的に CPU の ID で判別しますが , この命令 自体 Pen ⅱ um から正式にサポートされた命 令なのでそれより古いプロセッサでは利用 できない可能性があります。 List39 の関数を呼んで戻ってきた値が TR UE の場合は , CPUID 命令が利用できるこ とになります。 CPUID 命令の機能を Table 1 に示します。拡張命令が使用できるかど うかの判別で使うのは e を 0 に設定したと きと 1 に設定したときです。 e の値を 1 に 設定して CPUID 命令を実行したとき戻って くるバージョン情報のフォーマットは Fig. 11 のとおりです。この図の機能情報レジス タの拡張インストラクションに関するビッ ト (MMX , SSE , SSE2 , 3DNow! , Enhanc ed3DNow ! ) をチェックしてプロセッサが拡 張インストラクションを使用できるかどう かを判別します。 次に各命令をユーザが使用している OS で 利用できるかどうかを調べます。これは実 際にプロセッサの拡張命令を実行すること でチェックします。 List 40 は , 拡張インス トラクションそれぞれの小さな命令を実行 するコードです。 付録 CD-ROM に収録されている Cpuid. ex e を実行すると Fig. 12 のようなダイアログ が表示されます。ダイアログに表示されて いる CPUVender はプロセッサのべンダによ って変わり , インテル製であれば 'GenuineI ntel' と表示されます。 Family はプロセッサ のファミリを表し , 5 は Pentium ファミリ , 6 は Pentium PRO ファミリ (Pentium Ⅱ , Ce leron , Pentium Ⅲなどがこのファミリ ) で す。 ModeI はプロセッサのモデル番号です。 これでプロセッサの種類がわかります。 Ste ppingID はプロセッサがいつごろの時期の ものかを区別します。 アプリケーションはユーザがどんな実行 特集 1 プログラムの高速化・最適イヒ 3 / 〃 CPUID 命令がサポートされているかどうか調べます。 / / 一応 pentium から正式サポートというかたちですが、一部の土 486 プロセッサでも動作します。 OSCheckCPUID() XO て eX, cpuid —except( c 取嘛 ON ー 30 E 一日 ANDL 新 . ) if ( ) = = ) return FALSE; return RUE; 拡張インストラクションそれぞれの小さな命令を実行 / / テクノロジ命令を OS がサポートしているかどうかを調べます。 / / 日はテクノロジの命令です。無事に実行できれば礪が戻ります。 〃 streaming SIMD 取に on ( SSE ) 命令を OS がサポートしているかどうかを調べます。 〃聞日は S 駆の命令です。この場合も無事に実行できれば礪が戻ります。 も OSCheckSSE ( ) —try { —asm andps ー 0 , ー 0 ) —except( c 取 TION - 駆 E 一日 D 新 . ) if ( —exception—code() = = ) return FALSE; 〃 streaming SI と日 ion 2 ( s 覊 2 ) 命令をがサポートしているかどうかを調べます。 / / は SSE2 の命令です。実行できれば駅が戻ります。 も OSCheckSSE2() —try ( —asm 聞 d 図 = 0 , 0 } —except( EXCEPTIONÄW.JTE—HANDI. 駅 ) if ( exception—code() = = STATUS—ILIANAL—INSTRUCTION ) return FALSE; return 1 沢 UE ・ 〃 AD 社の 3 ow ! テクノロジ命令をがサポートしているかどうかを調べます。 〃 femg は 3DNow! テクノロジの命令です。実行できればが戻ります。 も OSCheck3DNow() . —try ( -agm femmg } —except( c TIO ー衂 E ー L 、 ) —try { —asm emms ) —except( c 取嘛 ON ー新居 c [ 居ー H DL 駅 . ) if ( exception-code() = STATtJS—ILInAL—INSTRUCTION ) て e 加て n FALSE; return 沢 U を一 て e に u てれ TRUE; if ( —exception—code( ) = STATtJS. *. -INSTRUCTION ) return PALSE; return NUE; / / A 社の新 h 聞 0 3 0 ⅵ命令をがサポートしているかどうかを調べます。 / / 〆は新 n 。 3 ow ーテクノロジの命令です。実行できれば TRUE が戻ります。 OSCheckE3DNow() —try ( -asm pswagd m0, mmO ) —except( 皿 c 取 TI ー 80 衂 E ー地 L 駅つ 妊 ( = ) return FALSE; return RUE ・
環境で動作するかわかりません。賢いプロ リックス演算なので , 期待する結果は Fig. 度浮動小数点が主な計算なので , X87 FPU グラムは , アプリケーションの起動時にこ 13 のようになります。普通に C 言語で , 最 命令を用いて最適化されたプログラムも実 れらの ID をあらかじめチェックしておいて , 適化を意識せず記述すると List 42 のような 行してみます ( List43 ) 。命令をすべてアセ 使用できる拡張インストラクションによっ プログラムになります。また , 今回は単精 ンプリ言語で書いている点と , 普通にルー て最適化されたプログラムを自動的に選択 プのアンロールを行っている点で高速化を 4 回反復される簡単なループ したり , 手動で切り替えたりということが 図っています。 FPU レジスタの動きはコメ できるようにしているようです。 ント以降を見てください。配列それぞれの void add( float ね , 日 oat 物を日 oat * 0 ) 乗算・加算を繰り返し , 1 つずっ順番に結 SSE 命令と XMM レジスタの 果を求めています。このあたりの最適化方 簡単な使用例 法については初級編・中級編をご覧くださ この章の最初のほうで , SSE, SSE2 命令 は 128 ビットの新しいレジスタを 8 本使うこ これを SSE で最適化すると , List 44 のよ とができると紹介しました。 うになります。 SSE で最適化されたプログ こではその 128 ビットレジスタ (XMM レジスタ ) を用い ラムのレジスタの動きはコメント以降の状 た簡単な例を紹介します。 態になります。プログラムの流れをレジス List41 は , 4 回反復される簡単なループ タの動きとともに見るとおおよそ理解でき です。ポインタは 16 バイトにアライメント TabIe 1 CPU 旧命令の機能 されているものとします。最初に 3 つある m eax = 0 ov 命令は X86 命令でそれぞれのアドレスを 初期値 レジスタに格納しているだけなのでとりあ えず無視するとして , 4 ~ 6 番目の命令が SS E 命令です。意味ー灯 able2 のようになります。 出力値 SSE 最適化の具体例 ( 行列演算・パフォーマンステスト ) eax = 1 プロセッサの判別ができて , SSE 命令の 初期値 使い方が何となくわかったところで , 次は 実際のコードを書いてみることにしましょ 出力値 っこでは SSE 命令を利用したサンプル として , 単純な 4X1 マトリックス ( 行列 ) 演 eax = 2 算を使ったテストを紹介します。残念なが 初期値 ら SSE 命令を利用できない環境のほうは , P 出力値 entium Ⅲ以降のプロセッサを持っている友 eax = 3 人などに協力してもらってください。 初期値 付録 CD - ROM 収録のサンプルプログラム 出力値 Ma ⅵ x をご覧ください。 4X1 の単純なマト TabIe 2 SSE 命令 4 void add( float ね , 日 oat め , float *c ) fo て ( = i く十十 ) ctil = a[i] 十 b は ↓ : 三 eax = 0 eax = CPU 旧命令の入力値で渡せる値の最大値 ebx, edx, ecx = プロセッサのべンダ名。たとえばインテル製のプロセッサだった場 合は次の値が入る ebx = Genu edx = 'inel' ecx = ntel' = 1 eax = バージョン情報 ( プロセッサのタイプ , ファミリ , モデル , ステッピング D ) eax ebx = ブランドインデックスと CLFUSH のラインサイズ = 予約済み eCX edx = 機能情報 = 2 eax = キャッシュおよび TLB 情報 ebx, edx, ecx eax, = 3 eax ebx = 予約済み eax, = プロセッサシリアルナンバー ( Pentium Ⅲプロセッサ ) edx, ecx 意味 movaps xmmO , xmmword ptr[eax] xmmO ( 128 ビット ) レジスタにアドレス eax の内容を 128 ビット ( 32 ビットの値を 4 つ同時に ) コピーする addps xmmO, xmmword pt 「 [edx] xmmO レジスタとアドレス edx の内容を 128 ビット ( 32 ビットの値を 4 つ同時に ) 加算する アドレス ecx に xmmO レジスタの内容を 128 ビット ( 32 ビットの値を 4 つ同時に ) コピーする movaps xmmword ptrCecx] , xmmO 38 C MAGAZINE 2001 10
インテルソフトウェア開発ツール 高速化 今開発されているアプリケーションの実行速度は、 Pentium4 プロセッサで最高のスピードを出していますか ? これからもユーサーに喜んでいただくソフトを開発するには、最新プロセッサ上で最高のパフォーマンスを 発揮できるものでなければなりません。そこで、 Pentium4 プロセッサについて一番詳しいインテルが、 アプリケーションの高速化を支援します。 アプリ % 分析 ' ー 凹 0 5.0 パフォー 1 性能アナライザ 「 10 つ、 旧回 Pentium4 をはじめとする、各種旧回プロセッサ上でアプリケーションを ( 10 ・ 3 ) し地た“ 実行させた場合のパフォーマンスを分析し、実行速度に悪影響を及ぼしている部分 8 について最適化のアドバイスを提供します。 ( 1 0 ・ ーい一工可 れ 1 ・タイムペースまたはイベントペースにてシステム全体をサンプリング・モジュール、クラス、 関数単位で CPU を多く使用している部分を特定・ソース行ごとにパフォーマンスデータを表示 ・ソースレベル (C ℃ + + /Fortran/Java) にて最適化アドバイスを提供・関数の呼び出し関係に 依存するパフォーマンスをツリー形式で表示・複数のイベントサンプリングセッション結果を 統合して表示可能 Microsoft Windows 98 、 Windows NT (SP4 以降 ) 、 Windows ME 、 Windows 2000 (Build 2195 以降 ) 予 [ トトしととい引ーに准第′・ 1 と 5 、一しれに当、′ ( を′ ( 、を - す ~ ′ 3 第 : 破測 英語または日本語 Windows に対応 システム全体レベルからソースコード、そしてアセンプリ命令レベルまでパフォーマンスを分析します。 VTune performance Analyzer 5.0 : Web 価格 \ 72 OOO 提示される最適化アドバイスには、さらにサンプルを使ったコード記述例も用意されています。 ( 標準価格 \ 88 , 000 ) ~ 0000 れ最適された 0 而愈⑧ーコン′イラーツー A ト F12 。 Pentium4 を含む、各種回プロセッサに最適化されたアプリケーションを生成します。旧回コンバイラは MicrosoftVisuaI : トスフうウナ万イルを閂しる ( Studio 総合開発環境へ組み込まれるので、普段より操作し慣れている環境で使用できます。 また、 32 ビットべースの開発 醴ールの登録 0 テ検索 ( K) マシン上で、 ltanium プラット ・ MicrOSOft VisuaI StudiO 開発環境へ統合・ Mic 「 0S0 れ Visual C + + または Compaq Visual Fortran とソース互換 。み“ x ールテストテナ ・浮動小数点命令で優れたスルーブットを提供・データブリフェッチ機能・プロシージャ間の最適化 フォームを対象とした [ 声 50L ØM わラ・ト乙ーア ・プロファイルに基づく最適化・ストリーミング S 隔 D 拡張命令 2 の完全サポート アプリケーションがビルドできる、 ・新しい機械語命令の使用を簡易化する組み込み関数 一 1 MFC TI ′等 ・自動べクトライザ・各種旧回プロセッサのランタイムサポート ( プロセッサディスパッチ機能 ) ltanium プラットフォーム用の クロスコンパイラも添付されて カスタマイス。 0.- います。 オフン 0 … : : マ知 旧 t 引コンパイラは Mic 「 09 れ Visual うイマ知録⑧ C ⅳト新 + R StudiolDE に統合され、簡単に クイカマ知の実行 Ctrl+Shift+P メニューから選択できます。 アプリケー ションの 物・ 0 ” 0 ー を支援します。 Centet Performance Libraries intel intel 1 ト丘 1 冫を一 ー 03C0 」・ 主な特長 1 印幻を 司上まい : 対応 OS 価格 ( 税別 ) 1 主な特長 3 已房い 00 Ⅲ川 体験版無料タウンロードサービス実施中 ! www.xlsoft.com 対応 OS 価格 ( 税別 ) Microsoft Windows 98 、 Windows NT (SP4 以降 ) 、 Windows ME 、 Windows 2000 (Build 2195 以降 ) 回 C + + Compiler 5.0 : Web 価格 \ 58 OOO ( 標準価格 \ 68 , 000 ) 回 Fortran CompiIer 5.0 : Web イ面格 \ 66 OOO ( 標準価格 \ 75 , 000 ) * 製品の仕様及びパッケージ内容、価格は予告なく変更することがありますのでご了承ください。 開発元 lntel Corporation i ntel 販売元 工クセルソフト株式会社 〒 108-0014 東京都港区芝 5-1 -9 プゼンヤビル 4F TE L : 03-5440-7875 FAX : 03-5440-7876 E-mail : xlsoftkk@剌 so 化 com Wune ハンズオントレーニング随時開催中。 詳しくは www.xlsoft.com まで。 ◎ソフトバンクバブリッシング凸版印刷 Printed in Japan T 1 1 1 4 5 2 5 1 0 1 2 0 1 雑誌 14325-10
特集 1 プログラムはもっと速くなる 高速化・最適化 加ムの ch3 近年 , プロセッサ技術の進歩で CPU の速度 は飛躍的に向上しました。現在では標準とな りつつあるギガヘルツのプロセッサですが , ひと昔前までは夢のまた夢でした。おかげで 複雑な計算もずいぶん楽にできるようになり ました。プロセッサは十分速く , 日々進化し ていきます。だからもう高速化の必要はない のではないかと思うこともありますが , 計算 は速いにこしたことはありません。速く計算 できるということは , 同じ時間でそれだけ多 最適化の基本 C 言語での最適化にはたくさんの方法がありますが , くを実行できることになるからです。たとえ ば 3D ゲームでよりリアリティのある表現を したり , キャラクタの動きをもっとスムーズ にすることもできますし , 長時間かかってい た画像処理がたとえば半分の時間でできるよ うになれば作業効率も倍になります。また , ごく少数ではありますが , プログラムを高速 化することに喜びを覚えるような変人 ( 失礼 ) もいます。現在と同じ実行環境で時間に余裕 ができることはとても重要なのです。 の章ではもっとも一般的なものから順に説明していきま す。なかにはこんなものは当然だと考えられる方もいら っしやると思いますが , そのあたりはプログラミングを 再確認するといった感覚で眺めていってください。もち ろん , 当然のごとく使われているようなアルゴリズムな どはどんどん読み飛ばしてもらってかまいません。 20 C MAGAZINE 2001 10 最適化の概要 プログラマにとって理想的な世界は , プ ログラマの設定した仕様に従ってソフトウ ェアが自動的に実行プログラムのサイズを 縮小してメモリ消費量を最小限に抑え , た だちにコンパイルが完了し , 完成したソフ トウェアが常に最高速で動作する環境です。 しかし現実には , 開発者はプログラムを自 分で書く必要があります。 ほとんどのプログラマは , C 言語などの 高級言語を使ってコーティングし , アセン プリ言語にコンパイルして , それぞれをリ
が圧倒的に少ない計算量で解を導いていま す。さらにそのぶんプログラムサイズも小 さくなっているはずです。 List3 に示す例のように , 規則性のある 計算の場合にも演算回数を減らしてプログ ラムを高速化することができます。 理由 : ムダな計算をしないように改善す れば高速化が見込めるため 使用される場所 : 計算される場所すべて ( とくに複雑な計算が行われる場所 ) ポイント : アルゴリズムに気をつける ループをまとめる List4 はかなり強引な例ですが , ケース 1 はケース 2 のようにまとめることができま す。これはループの終了条件をチェックす る際に分岐が起こるため , チェック回数 ( 分岐回数 ) の少ないケース 2 のほうが速く 計算方法の工夫② 十 5*x*x*x*x 十 4*x*x*x 十 3*x*x 十 2 1 x*5 ( x * 4 十 ( x * 3 十 ( x * 2 十 て・ tu てれ ( 1 十 int defo て新 a 0n2 ( int x ・ケース 2 てれ ( int d ・ fo ェ ma 0n2 ( に x ・ケース 1 3 ループをまとめる lSt fo て ( int 土 = 土 < 丐土十十 = ⅵ幻 + 新 fo て ( int 土第 ・ケース 1 4 void mma 工臧ル 00P ( int *x, 加セ *y, int n ) 土十十 なります。また変数 i が配列のインデックス として用いられているため , データを参照 する回数も減らすことができます。ただし , こういったケースでは計算順序を間違えな いように注意しなければなりません。 理由 : 条件分岐やデータの参照回数を減 らすため 使用される場所 : 同じ条件でループする ような場所 命令の優先順位 ( その 1 ) 浮動小数点の計算では , 除算ではなくな るべく乗算を使います。世の中にあるほと んどのプロセッサでは除算は乗算に比べて きわめて低速に動作します。つまり List5 は ケース 2 のほうが高速ということになりま す。 List6 のような場合は , 乗算を加算で 置き換えたケース 2 のほうが速くなります。 加算は乗算よりも高速に動作します。 実行にかかる時間は「加算く乗算く除算」の 順であると覚えておいてください [ 1 い 01 ] 。 理由 : プロセッサの作業効率を上げるた め 使用される場所 : ループ中繰り返し実行 される計算が含まれる場所 命令の優先順位 ( 乗算・除算 ) void Array1( double *x, double d ) ・ケース 2 x[il / = の fO て ( int 土 = く 1000 十十 void Array1( do 地 *x, double d ) ・ケース 1 5 fo て ( 土れし i = 土《 1000 土十十 float rd = 1. Of / 命令の優先順位 ( 加算・乗算 ) List xti * 引 = fO て ( int = 1 く void Array2( int , int 日 , int れ ) ・ケース 1 6 命令の優先順位 ( その 2 ) 先ほどの優先順位の補足的な形になって しまいますが , データのシフトをうまく利 用して計算方法を工夫することもできます。 説明するまでもありませんが , データはバ イナリレベルで左にシフトされると値は 2 倍 , 4 倍 , 8 倍・・・・・・と増えていき , 右にシフ トすると値は 1 / 2 , 1 / 4 , ・・・となります。 ・ケース 2 void gumma 2 ・石 00P ( int *x, int *y, fO て ( int = 土く土十十 ) = x は = x は = ⅵ幻 * 新 int n VOid て ay2 ( え n セ社 , int 町 int n ) ・ケース 2 fO て ( 土れセ 1 = 土十十 ) = く土十十 , ゴ十 = 8 この性質をうまく使えば , List 7 のような乗 算も , シフトと足し算の命令に置き換える ことができます。 理由 : プロセッサの作業効率を考えるた め 使用される場所 : 簡単な計算が実行され るところ 条件分岐の順序 条件分岐 ( if 文 ) を使うときは発生しそう な条件から順番に並べます。 List8 のように 発生する確率が高いほうからチェックする ようにしておけば , 余分な条件分岐をしな くて済みます。また同じ分岐先であるのな ら , 論理演算を使ってそれぞれのフラグを ひとまとめにしてから分岐するというテク ニックもあります 理由 : 条件分岐をなるべく減らすため 使用される場所 : 条件が重なるような分 岐ポイント 条件分岐を減らす 最近の長いパイプラインを備えたプロセ ッサでは , 条件分岐がポトルネックになり ます。なぜかというと , プロセッサは実行 されるコードが命令プリフェッチによって 実行前にあらかじめパイプラインに読み込 まれてから処理される仕組みだからです このときプロセッサは条件分岐を予測する のですが , 読み込まれた命令はプロセッサ が命令ストリームを正しく予測できるとき にのみ機能することになります。実行時 , どこに分岐するかという予測をプロセッサ 0 0 ラインはいったんフラッシュされ , 正しい が誤った場合 , プリフェッチされたパイプ 22 c MAGAZINE 2001 10
特集工 加殤ムの高速化・最適化 m Ⅲでのコードのパフォーマンスが多少低 下することがあります。 ・アライメント と , コードのサイズが大きくなります。 非常に大きいループをアンロールする ・必要以上にアンロールを使用したり , ループアンロールによるコスト ません。 ープを 5 回以上アンロールしてはいけ Pentium Ⅱまたは Pentium Ⅲでは , ル アンロールするようにします。ただし , 数が 16 以下になるまで , 内側ループを 回数がわかっている場合は , 反復の回 プ本体のサイズが大きすぎず , 反復の が存在しない場合 ) 。したがってルー 可能であり , ループ内に条件付き分岐 正確に予測できます ( 反復回数が予測 れより少ない内側ループの終了分岐を ・ Pentium4 は , 反復回数が 16 またはそ ます。 イン化 ) して , レイテンシを隠蔽でき にスケジューリング ( またはパイプラ ・アンロールによって , ループを積極的 されます。 れるため , 分岐のオーバヘッドが軽減 管理するためのコードの一部が除去さ ・アンロールによって分岐と誘導変数を ループアンロールによる利点 紹介します。 よる利点と特徴について , そのポイントを サ ( とくに Pentium 4 ) ループアンロールに 解説しました。 こでは , 最近のプロセッ ループのアンロールについては初級編で ループアンロールによる利点 バイト単位で行われます。 です。キャッシュラインへの読み込みは 64 ン当たり 2 セクタ , セクタ当たり 64 バイト ) ャッシュのラインサイズは 128 バイト ( ライ ントを合わせなければいけません。 2 次キ SIMD コードは 16 バイト境界にアライメ アンロールされたループがトレースキ ャッシュの容量を超えると , パフォー マンスが低下することがあります。 ・ループの本体に分岐が含まれる場合 , それらのループをアンロールすると , 必要な分岐予測の容量が増えます。ア ンロールされたループの反復の回数が 16 またはそれより少ない場合 , 分岐プ レディクタは , ループ本体内の分岐を 正確に予測できるはずです。 キャッシュ利用の最適化 この 10 年でプロセッサの速度はおよそ 10 倍以上に向上しました。しかしメモリのア クセス速度はわずか 2 倍程度になったにす ぎません。このパフォーマンスの差を埋め るため , アプリケーションをうまくチュー ニングしてデータアクセスの多くをプロセ ッサのキャッシュで実行させることが重要 な課題となっています。必要なデータをメ インメモリからフェッチするのではなく , プロセッサのキャッシュからフェッチでき れば , ほとんどのアプリケーションでパフ ォーマンスが格段に向上するはずです。 これらの一般的なガイドラインとして以 下のようなことが考えられます。 ・最新のコンパイラを使用する ・コンパイラの最適化を容易にするため に次の点に気をつける ーグローバル変数およびグローバルポ インタの使用を最小限に抑える ー複雑な制御フローの使用を最小限に 抑える ー con 修飾子を使用する ーテータタイプの選択には注意し , タ イプキャストはなるべく避ける ・プリフェッチのスケジューリング距離 を最適化する ・プリフェッチ連結を使用する ・プリフェッチの回数を最小限に抑える ( プリフェッチ命令は , バスサイクル , マシンサイクル , およびリソースとい った観点から見た場合 , 必ずしも完全 に自由に使用できるわけではなく , ア プリケーションのパフォーマンスに悪 影響を与えることもある ) ・プリフェッチ命令の間に演算命令をい くつか挿入する ・ストリッブマイニングなど , キャッシ ュプロッキング手法を使用する ・シングルバス実行とマルチパス実行の 釣り合いを図る ( シングルバス実行と は計算パイプラインの 1 つの全長を経 由してデータ要素を 1 つだけ通過させ るもの , マルチパス実行とは複数のデ ータ要素からなる 1 つのデータ群を対 象にしてパイプラインのステージを 1 段階実行してから , そのテータ群を次 のステージに渡す方法のこと ) ・メモリバンクの競合問題を解決する ( 連 続使用データをまとめてグループ化す るか , 4K バイトのメモリページに収ま るようにテータを割り当てる ) ・キャッシュ管理の問題を解決する ( プ ロセッサのキャッシュに書き込まれて いるテンボラルなデータの乱れをでき るかぎり抑えるため , ストリーミング ストア命令を必要に応じて使用する ) プリフェッチ プリフェッチ命令でデータのキャッシュ ラインにアクセスすることで , データアク セスに要するレイテンシが生じなくなりま す。プリフェッチ命令を使用しても , ユー ザから見たプログラムの機能は変わりませ んが , プログラムのパフォーマンスに影響 することがあります。プリフェッチ命令は ハードウェアにヒントを与えるにすぎない ため , それによって例外や障害の発生する ことはありません (Fig. 18 ) 。プリフェッチ 命令には Table4 の 4 種類があり , これらは プログラマまたはコンパイラによって挿入 されます。ただし , プリフェッチ命令を使 いすぎるとメモリ大域幅が浪費され , その 結果リソース上の制約を受けてパフォーマ ンスが低下する可能性があります。それで 特集 1 プログラムの高速化・最適化イ 5
特集 1 加殤ムの高速化・最適化 て MMX テクノロジのプログラミングに慣 れたプログラマであれば比較的楽に 3DNow ! テクノロジの世界に入っていくことができ ます。残念なことに 3DNow ! テクノロジ向 けにチューニングされたプログラムは , 通 常はインテルのプロセッサで動作させるこ とができません。すばらしいテクノロジで はありますが , プロセッサの互換性という 点で少々不利になってくることが残念です。 しかし , 現在の AMD 製のプロセッサでは 逆にインテルの SSE2 命令を利用することが できないので , AMD プロセッサユーザのた めのコードを組むというのも 1 つのアイデア です。 3DNow ! テクノロジについては , 本特集 ではこの程度にとどめさせていただきたい と思います。 X87 コードと S D 浮動小数点 コードのトレードオフ X87 浮動小数点コードと SSE, SSE2 ある いはその両方を使用したスカラ浮動小数点 コードの間には多くの違いがあります。 れらの違いを考慮に入れて , どのレジスタ アライメントされていないデータをアライメントが必要な命令でアクセスした Microsoft Visual 0 + + ルドルされていない例外はト brmalizeVectorSSE.exe にあります 96 : Priyileged lnstn-rtvono Fig. 10 SSE コードを使用するまでの流れ 浮動小数点 浮動小数点か なぜ いいえ 範囲または 精度 整数に 変換できるか 単精度に いいえ 変換できるか はい はい コード内のホット スポットを設定する コードに MD による メリットが あるか はい 整数か 浮動小数点か パフォーマンス S Ⅳ D 整数を使用 するように変更する 単精度を使用する ように変更する 整数 可能な場合は , テータを再配置 して S Ⅳ D 処理の効率を上げる データ構造のアライメントを合 わせる S Ⅳ D 変換を使用するようにコ ードを変換する 一般的なコーディンクのガイド ラインと SIMD コーディングの ガイドラインに従う 必要に応じて , メモリの最適化 とプリフェッチを使用する 命令をスケジューリングしてパ フォーマンスを最適化する いいえ 終了 およびそれに付随する命令を使用するかを 決定することができます ( Fig. 10 ) 。 SIMD 浮動小数点命令の入力オペランド に , そのデータタイプで表現可能な範囲よ り小さい値が含まれている場合は , デノー マル例外が発生し , パフォーマンスが大き く低下します。ところで SIMD 浮動小数点 操作には , ゼロフラッシュモードがありま す。ゼロフラッシュモードでは , 結果のア ンダーフローは発生しません。したがって , それ以降の計算に , デノーマル人力オペラ ンドの処理によるパフォーマンスの低下は 生じません。たとえば , 一般的な 3D アプリ ケーションのライティング処理などで多数 のアンダーフローが発生する場合 , ゼロフ ラッシュモードを利用すると , パフォーマ ンスは約 50 % 向上します。 スカラ浮動小数点のほうがレイテンシが 小さくて済みます。ただしリソースの利用 率が低い場合 , 通常これはそれほど大きな 問題ではありません。 特集 1 プログラムの高速化・最適イヒ 35 することができます。 タスタックと XMM レジスタを同時に使用 ェアが使用されますが , 浮動小数点レジス m 4 では , どちらの命令にも同じハードウ アクセスすることができます。さらに Pentiu とトッブオプスタックの制限なしに , 直接 スカラ浮動小数点レジスタは , fxch 命令 コードを使用するほうがよいでしよう。 dd の数が多いアプリケーションでは , X87 たがって , 浮動小数点の mul の数に対して a 場合もパイプライン化されていません。し ていません。浮動小数点乗算は , いずれの ラ浮動小数点コードはパイプライン化され ではパイプライン化されていますが , スカ Pentium 4 で浮動小数点加算は X87 コード サポートしています。 SIMD 拡張命令 2 は , 最大 64 ビットの精度を 度をサポートしています。ストリーミング ング SIMD 拡張命令は , 最大 32 ビットの精 小数点をサポートしています。ストリー X87 命令は , 80 ビットの拡張倍精度浮動 います。 X87 だけが超越関数命令をサポートして
特集 1 加殤ムの高速化・最適化 レジスタ X86 系のプロセッサには複数のレジスタ があります。レジスタとは簡単にいうとテ ータを保持する器のことで , 基本的にはこ のレジスタを介してデータのやりとりを行 うことになります。 現在の X86 系プロセッサでは , 1 つのレジ スタは 32 ビットのデータを持っています。 このうちもっともよく使われるレジスタは eax , ecx , edx ebx , esp , ebp , esi , edi の 8 つの汎用レジスタでしよう。ちなみに x 87 は X86 のような汎用レジスタを持ってお らず , FPU レジスタスタックという古典的 なスタックベースによってデータを管理し ます。 FPU レジスタは 80 ビットの幅があり , 主に浮動小数点数の演算に利用されます。 しかし FPU レジスタスタックは基本的に最 上位にある「トッブオプスタック」にしかメ モリとの直接アクセスが許されていません。 後ほど詳しく説明しますが , Pentium M MX 以降のプロセッサはこのほかに mm0— mm7 という 64 ビットの汎用レジスタを持っ ています。ただし , このレジスタは内部で FPU レジスタと交互に切り替えながら使用 されます。また Pentium Ⅲ以降では xmmO —xmm7 という 128 ビットレジスタを持って います。このレジスタはほかのレジスタと 除算の結果で商と剰余を算出する ( C 言語 ) DWORD d 土 v 土 ( DWORD x, DWORD y ) DWORD a, b; a = x / b = x 宅 y; List 1 はまったく独立して存在しています。 アセンブリ言語の基礎 高級言語と呼ばれる C 言語などに対し , アセンプリ言語は俗に低級言語と呼ばれま す。アセンプリ言語はプロセッサが直接理 解できる ( 正確には専用のデコーダを介す ) マシン語と 1 対 1 で対話する形式だからです。 アセンプリ言語は一般的にニモニックと オペランドで構成されています。ニモニッ クとはアセンプリ言語の命令のことをいい ます。オペランドにはニモニックで指定し た命令のパラメータが入ります。ニモニッ クによってはオペランドが必要ないものや 複数のオペランドを指定するものがありま す。たとえば , 次の例は e レジスタの値 に 2 を足して結果を eax レジスタに格納しな さいという命令です。 —asm add eax, 2 ちょっと極端な例ですが , 次のように C 言語で書くよりもアセンプリ言語で書いた ほうがシンプルな場合もあります。 —asm mov eax, d1234 —asm bswap eax —asm mov d4321 , eax bsw 叩はデータバイトの上下を入れ替え る命令です。工ンティアンを変更する場合 などに使用されます。 C 言語では次のよう になるでしよう。 ( d1234 ” 24 ) ー d4321 = ( ( d1234 8 ) & 0xff00) ー ( ( d1234 & 0xff00) くく 8 ) ー ( d1234 くく 24 なお , 本特集の目的は「プログラミング の高速化」であり , アセンプリ言語を解説 するものではないのでこれ以上の説明はし ませんが , 内容をよく理解するにはアセン プリ言語に関する書籍などを読むほうがい いでしよう。 X86 の特徴と アセンブリレベルでの最適化 X86 の特徴を生かすと , C 言語では表現が 難しかった最適化も簡単にできることもあ ります。たとえば整数の除算の結果で商と 剰余の値が必要なことがあります。 C 言語 では List21 のように除算を 2 度実行すること になりますが , アセンプリ言語で書くと List 22 のように 1 度の除算で済みます。これは x 86 命令がもともと整数の除算の結果を商と 剰余で出しているからです d ⅳ命令は , edx に上位 32 ビットを , eax に下位 32 ビットを格納して実行すると , オ ペランドの値で除算した結果を e , edx レ ジスタに返します。ちなみに xor は排他的 論理和 , mov は移動の命令です。インライ 0 サインとコサインを使った座標計算 ( C 言語 ) void n003 ( float x, float * 0 , 日 oa に * 日 ) List * 0 = 00 日 ( x * 3 = 日加 ( x ist 4 サインとコサインを使った座標計算 ( アセンブリ言語 ) IS 2 除算の結果で商と剰余を算出する ( アセンブリ言語 ) divide( DWORD x, DWORD y ) DWORD a, b; xoredx, dx レジスタを 0 にする。 題 0 eax, x ー eax レジスタに x を格納 div y 経 / y 重 0 a, eax = x / y 新 0 b, edx = x 物 y void 日 inco 日 ( float も float * 0 , oa し * 日 ) も dvordptr[cl cx にコサインの値を格納するアドレスをロード ー浮動小数点の計算はスタックベースで行うため。 を F 部レジスタスタックに積む。 fld x fs 加 00 日 fstp fstp 0 て d pt て [ 幻ーコサインの値をロード と 0 。 9 を計算 も d ェ dp い ] にサインの値を格納するアドレスをロード dvord ptrteax) ーサインの値をロード 特集 1 プログラムの高速化・最適化 2 /