アプリケーションの最適化には,作成プロセスの変更,ソース・コードの変更,またはその両方を含みます。
多くの場合,アプリケーションを最適化すると,主に実行時性能が改善されます。 ただし,アプリケーション・プログラムの実行時性能を計測して,性能の改善方法を分析する前に,次の 2 つの点について確認してください。
システムのソフトウェアをチェックして,アプリケーション・プログラムの作成にコンパイラとオペレーティング・システムの最新バージョンを使用していることを確認してください。 通常,新バージョンのコンパイラの方がより効果的な最適化を実現し,新バージョンのオペレーティング・システムの方が効率的に動作します。
アプリケーション・プログラムをテストして,エラーなしで実行できることを確認してください。
アプリケーションを 32 ビット・システムから Tru64 UNIX にポーティングした場合でも,新規に開発した場合でも,アプリケーションを十分にデバッグしてテストするまでは,最適化を行わないでください。
C で記述されたアプリケーションをポーティングする際には,C コンパイラの
-message_enable questcode
オプションを用いてコンパイルするか,あるいは,-Q
オプションを指定して
lint
コマンドを使用することにより,解決する必要のある移植性の問題がないか調べます。
これらの事項について確認してから,最適化処理を開始してください。
アプリケーションの最適化プロセスは,補完的ではあるが,2 つの別個のアクティビティに分けることができます。
アプリケーションの作成プロセスを調整して,たとえば,自動前処理の最適なセットやコンパイル時の最適化を使用するようにします (10.1 節 を参照)。
アプリケーションのソース・コードを分析して,ソース・コードで効果的なアルゴリズムを使用し,性能を低下させる可能性のあるプログラミング言語構造を使用していないことを確認します (10.2 節 を参照)。 この手動フェーズには,第 8 章 で説明しているような,性能を分析するプロファイリング・ツールの使用も含まれます。
以降の各節で,チューニング・プロセスのこれら 2 つのアスペクトに関して詳細に説明します。
10.1 アプリケーション・プログラム作成のガイドライン
アプリケーションの実行時性能の自動的な改善は,作成プロセスのあらゆるフェーズにおいて行うことができます。
以降の項で,コンパイル,リンクとロード,前処理と後処理,および,ライブラリ選択において行うことができる主な改善について説明します。
特に効果のある手法は,spike
ツールを用いたプロファイル主導の最適化です (10.1.3 項)。
10.1.1 コンパイルに関する考慮事項
アプリケーションのコンパイルを,設定できる最高の最適化レベル,つまり,最高の性能と正しい結果が得られるレベルで行います。
一般に,言語使用の標準に準拠しているアプリケーションは,最高の最適化レベルでコンパイルすることができますが,そのような標準に準拠していないアプリケーションは,低い最適化レベルでコンパイルしなければならないことがあります。
詳細については,
cc
(1)
アプリケーションを最高のレベルでコンパイルできる場合には,すべてのソース・ファイルを 1 回のコンパイルで一緒にコンパイルしてください。 複数のソース・ファイルをコンパイルすると,コンパイラが最適化のために調査するコードの量が増加します。 これにより,次のような効果が得られます。
これらの最適化を行うには,コンパイル・オプションの
-ifo
と
-O3
または
-O4
を使用します。
最高レベルの最適化が特定のプログラムで効果があるかどうかを判断するには,プログラムを 2 回コンパイルして,それぞれの結果を比較します。 1 回目は,最高の最適化レベルでコンパイルし,2 回目には,1 つ下の最適化レベルでコンパイルします。 いくつかのルーチンは最高の最適化レベルでコンパイルできないことがあるため,そのようなルーチンは別々にコンパイルする必要があります。
実行時性能に重大な影響を及ぼす可能性があるその他のコンパイル時の考慮事項には,次の事柄があります。
多数の浮動小数点演算を行う C アプリケーションでは,結果がわずかに異なっても問題ない場合は,-fp_reorder
オプションの使用を考慮してください。
C アプリケーションでループ内に多数の
char
,short
,または
int
データ項目を使用している場合には,C コンパイラの最高レベルの最適化オプションを使用して,性能を改善できます。
最高レベルの最適化オプション (-04
) は,Alpha システムに対し,とりわけバイトのベクトル化による最適化を実現します。
1 回または数回のサンプル実行で性能を調べることができる C および Fortran アプリケーションでは,-feedback オプションの使用を検討してください。 このオプションは,10.1.3.2 項 で説明しているように -spike と併用したり,また -ifo オプションと併用すると,よりよい結果を得ることができます。
完全にデバッグして,例外を生じない C アプリケーションに対しては,-speculate
オプションの使用を考慮してください。
このオプションを使用してコンパイルしたプログラムを実行すると,さまざまな実行パスに関連する値が前もって計算されるため,必要な場合はすぐに使用できます。
この先行操作は,アイドル状態のマシン・サイクルを利用します。
そのため性能が低下することはなく,通常,前もって計算された値が使用されるたびに向上します。
-speculate
オプションは,次の 2 つの形式で使用できます。
-speculate all
-speculate by_routine
どちらのオプションも,例外を破棄します。
-speculate all
オプションは,プログラムのすべてのコンパイル単位で生成された例外を破棄し,-speculate by_routine
オプションは,そのオプションを適用されたコンパイル単位で生成された例外だけを破棄します。
テスト実行で,膨大な数の例外が破棄された場合は,性能が低下します。
-speculate all
オプションは,特に浮小数点演算を行うプログラムに対して,-speculate by_routine
オプションよりもアグレッシブで,より性能を向上させる可能性があります。
プログラム内に例外処理を行うルーチンがある場合には,-speculate all
オプションは使用できません。
ただし,-speculate by_routine
オプションは,そのオプションが使用されるコンパイル単位の外に例外処理がある場合には,使用することができます。
デバッグの最中は,いずれの
-speculate
オプションも使用しないでください。
プログラムが正常終了した場合に,破棄された例外数のカウントをプリントするには,次の環境変数を指定します。
% setenv _SPECULATE_ARGS -stats
現在,-speculate all
オプションでは統計機能は使用できません。
-speculate all
および
-speculate by_routine
の各オプションを使用すると,境界合わせのフィックスアップについてのメッセージがすべて抑制されます。
テストまたは本番の境界合わせフィックスアップに関するメッセージを生成するには,次の環境変数を指定します。
% setenv _SPECULATE_ARGS -alignmsg
両方のオプションを指定するには,次のようにします。
% setenv _SPECULATE_ARGS -stats -alignmsg
次のコンパイル・オプションを一緒に,または個別に使用することにより,実行時性能が改善できます
(詳細は,
cc
(1)
オプション | 説明 |
-arch |
命令を生成する Alpha アーキテクチャのバージョンを指定する。
-arch
と
-tune
の違いについては,
cc (1) |
-ansi_alias |
ソース・コードが ANSI C の別名化規則に準拠しているかどうかを指定する。 ANSI C 別名化規則では,よりアグレッシブな最適化が許可されている。 |
-ansi_args |
ソース・コードが引数に関して ANSI C の規則に準拠しているかどうかを指定する。 ANSI C の規則に準拠している場合には,特別な引数クリーニング・コードを生成する必要がない。 |
-fast |
性能向上のため,次の最適化オプションをオンにする。
|
-feedback |
最適化の際に,コンパイラが指定されたファイル内のプロファイル情報を使用することを指定する。 詳細については,10.1.3.2 項を参照。 |
-fp_reorder |
浮動小数点演算に影響する特定のコード変換が許可されるかどうかを指定する。 |
-G |
スモール・データ・セクション(sbss または sdata) におけるデータ項目の最大バイト・サイズを指定する。 |
-inline |
関数のインライン展開を実行するかどうかを指定する。 |
-ifo |
ファイルが別々にコンパイルされた場合,可能な限り,ファイル境界を超えて,最適化の向上 (ファイル間の最適化) とコード生成を行う。 |
-O |
コンパイルにより到達可能な最適化レベルを指定する。 |
-om |
さまざまなポストリンク・コード最適化を実行する。
-non_shared
オプションを用いてコンパイルしたプログラムで,最も効果がある (付録 F
を参照)。
このオプションは,-spike
オプションに置き換えられている (10.1.3 項
を参照)。 |
-preempt_module |
モジュールごとのシンボルの優先使用をサポートする。 |
-speculate |
実行パスが取られる前に,その実行パスで実行中のプログラム内で動作 (たとえば,ロードや演算操作) が行われるようにする。 |
-spike |
さまざまなポストリンク・コード最適化を実行する (10.1.3 項 を参照)。 |
-tune |
Alpha アーキテクチャ固有の処理系に対して,プロセッサ固有の命令のチューニングを選択する。
-tune
と
-arch
の違いについては,
cc (1) |
-unroll |
レベル
-O2
以上の最適化プログラムで行われるループの展開を制御する |
上記のオプションを使用すると,正確さと標準に対する準拠が損なわれることがあります。
C アプリケーションでは,浮動小数点の例外処理に有効なコンパイル・オプションは,次のように実行時間に重大な影響を及ぼすことがあります。
省略時の例外処理 (特別なコンパイル・オプションはない)
省略時の例外処理モードでは,オーバフロー,ゼロによる除算,および無効時処理の例外で,必ず
SIGFPE
例外ハンドラがシグナル通知されます。
また,IEEE 無限大,IEEE NaN (not-a-number),または IEEE 非正規化数のいずれかを使用すると,SIGFPE
例外ハンドラがシグナル通知されます。
省略時の設定では,アンダフローはサイレントに結果がゼロになりますが,コンパイラは,アンダフローで
SIGFPE
ハンドラがシグナル通知されるようにする別のオプションをサポートしています。
省略時の例外処理モードは,特定の浮動小数点フォーマットの特殊な特性に依存していない移植可能なプログラムに適しています。 省略時のモードで最適な例外処理性能が提供されます。
移植可能な IEEE 例外処理モードでは,特別な呼び出しを行って,フォールトを可能にしておかない限り,浮動小数点例外はシグナル通知されません。
このモードは,IEEE 無限大,IEEE NaN,および IEEE 非正規化数を正確に生じさせて,処理します。
また,このモードは IEEE 浮動小数点のほとんどの移植不可能なアスペクトに対して次のようなサポートを提供しています。
つまり,厳密でない例外を除き,すべての状態オプションとトラップ使用をサポートします。
厳密でない例外機能 (-ieee_with_inexact
)
についての詳細は,
ieee
(3)
移植可能な IEEE 例外処理モードは,IEEE 浮動小数点規格の移植可能なアスペクトに依存しているプログラムに適しています。 このモードでは,通常,プログラム内の浮動小数点のコンパイル量に応じて,省略時のモードより 10 〜 20 パーセント処理速度が遅くなります。 状況によっては,このモードで実行時間を 3 ファクタ以上速くすることができます。
アプリケーションで多数の大きなライブラリを使用しない場合には,それを共用しないでリンクすることを考慮してください。
このようにすると,リンカがライブラリへの呼び出しを最適化できるため,(呼び出しが頻繁に行われる場合には) アプリケーションの起動時間が短縮されて,実行時性能が改善されます。
ただし,共用されないアプリケーションは呼び出し共用のアプリケーションより多くのシステム資源を使用することがあります。
多数のアプリケーションを同時に実行しているとき,アプリケーションに共通なライブラリのセットがある (たとえば,libX11
や
libc
) 場合には,それらのライブラリを呼び出し共用としてリンクすると,システム全体の性能を向上させることができます。
詳細については,第 4 章を参照してください。
シェアード・ライブラリを使用するアプリケーションでは,それらのライブラリがクイックスタートできることを確認してください。
クイックスタートは,アプリケーションのロード時間を大幅に減少させる Tru64 UNIX の機能です。
アプリケーションの数が多い場合には,アプリケーションの起動と実行に必要な全時間のうち,かなりの割合をロード時間が占めます。
オブジェクトをクイックスタートできない場合には,実行はできますが,起動時間が遅くなります。
詳細については,4.7 節を参照してください。
10.1.3 spike およびプロファイル主導の最適化
この項では,ポストリンク最適化プログラム
spike
について説明します。
10.1.3.1 spike の概要
spike
ツールは,リンク後のコードの最適化を行います。
プログラム全体を操作できるため,spike
ではコンパイラにはできない最適化を行うことができます。
10.1.3.2 項
で述べているように,spike
は,最適化を先導するプロファイル情報を使用すると,最も効果があります。
spike
は Tru64 UNIX Version 5.1 から提供されるようになったツールであり,om
および
cord
に代わるものです。
制御や最適化の効率が改善されており,実行可能プログラムとシェアード・ライブラリのどちらでも使用できます。
spike
は,om
や
cord
とは併用できません。
om
および
cord
についての詳細は,付録 F
を参照してください。
spike
が実行する最適化には,コード・レイアウト,実行されないコードの削除,アドレス計算の最適化などがあります。
spike
は,Tru64 UNIX の V4.0 以降のシステムでリンクされたバイナリを処理できます。
V5.1 以降のシステムでリンクされたバイナリには,spike
がさらに最適化を行えるようにする情報が含まれています。
注意
spike
は,Tru64 UNIX V5.1 以降のイメージに対して一部のアドレス最適化のみを行いますが,om
は V4 イメージに対して最適化を行います。 V5.1 より前のバイナリに対してspike
を使用し,リンカの最適化を有効にしている (リンクの段階でcc
に -O を渡す) 場合,om
とspike
の性能の違いはあまりありません。
spike
は,2 通りの方法で使用できます。
コンパイルの後に,spike
コマンドをバイナリ・ファイルに適用する方法
cc
コマンド (または,対応するコンパイラがシステムにインストールされている場合は,cxx
,f77
,f90
のいずれか) に
-spike
オプションを付けて,コンパイル処理の一部として使用する方法
この項と
10.1.3.2 項
の例は,spike
のこの 2 つの使用方法を示しています。
実行可能ファイルを再リンクしたくない場合 (Example 1) や,コンパイルの後にプロファイル情報を使用する場合 (Example 5
および
Example 6) は,spike
コマンドが便利です。
プロファイル情報を使用しない場合 (Example 2) や,コンパイラでもプロファイル情報を使用する場合 (Example 3
および
Example 4) は,-spike
オプションの方が便利です。
Example 1
および
Example 2
は,最適化を先導するプロファイル情報を使用せずに
spike
を使用する方法を示しています。
10.1.3.2 項
では,pixie
プロファイラからのフィードバック情報を用いて
spike
を使用する方法を示しています。
例 1
この例では,spike
はバイナリ
my_prog
に適用され,最適化された出力ファイル
prog1.opt
を生成します。
%
spike my_prog -o prog1.opt
この例では,cc
コマンドの
-spike
オプションを使って,コンパイル時に
spike
を適用しています。
%
cc -c file1.c%
cc -o prog3 file1.o -spike
最初のコマンド行は,オブジェクト・ファイル
file1.o
を作成します。
2 番目のコマンド行は
file1.o
をリンクして実行可能ファイルを作成し,spike
を用いて実行可能ファイルを最適化します。
spike
コマンドのオプションはすべて,(cc
)
-WS
オプションを用いて,cc
コマンドの
-spike
オプションに直接渡すことができます。
次の例に構文を示します。
%
cc -spike -feedback prog -o prog *.c \
-WS,-splitThresh,.999,-noaggressiveAlign
spike
コマンドのオプションと
spike
を使用する際の制限についての詳細は,
spike
(1)10.1.3.2 プロファイル主導の最適化での spike の使用
自動最適化は,前項で述べた -O,-fast,-inline などのコンパイラの自動最適化オプションを使用すると,ある程度までは達成できます。 これらのオプションは,CPU アーキテクチャとキャッシュ・メモリを最善の方法で使用するための最小限の命令シーケンスを生成する際に役立ちます。
ただし,プログラムが通常の入力データおよび通常の環境のもとで実行された場合に,どの命令が最も多く実行されるかという情報を指定すると,コンパイラとリンカは,この最適化を改善できます。 Tru64 UNIX では,プロファイラの結果を再コンパイルにフィードバックすることで,この情報を指定できます。 このカスタマイズされたプロファイル主導の最適化は,自動最適化と組み合わせて使用できます。
以下の例では,spike
を
pixie
プロファイラと併用し,さまざまなフィードバック手法を用いてプログラムから生成される命令シーケンスを調整する方法を示しています。
例 3
この例は,spike
を用いたプロファイル主導最適化の基本的な 3 つの手順,すなわち,(1) プログラムの最適化を準備する,(2) 計測機構付きのプログラムを作成して実行し,プロファイリング統計情報を収集する,(3) その情報をコンパイラとリンカにフィードバックして,実行可能コードの最適化に役立てる,という手順を示しています。
後の例では,これらの手順を詳細化して,開発中に行われた変更と複数のプロファイリング実行で得たデータを合わせて調節する方法を示しています。
%
cc -feedback prog -o prog -O3 *.c [1]%
pixie -update prog [2]%
cc -feedback prog -o prog -spike -O3 *.c [3]
プログラムが最初に
-feedback
オプション付きでコンパイルされたときに,拡張された特別な実行可能ファイルが作成されます。
これには,コンパイラが実行可能ファイルをソース・ファイルに対応させるために使用する情報が含まれています。
また,コンパイラへのプロファイルのフィードバック情報を格納するために後で使用するセクションも含まれています。
このセクションは,最初にコンパイルしたときは,pixie
プロファイラ (手順 2) がフィードバック情報をまだ生成していないため,空のままです。
-feedback
オプションに指定するファイル名は,必ず実行可能ファイルと同じ名前にしてください。
この例では
prog
(-o
オプションで指定) です。
特に指定しないかぎり,-feedback
オプションでは
-g1
オプションが適用され,プロファイルに最適なシンボルが付けられます。
-On
オプションを試して,対象のプログラムとコンパイラで実行時性能が最高になる最適化のレベルを調べてください。
最初のコンパイルの際には,まだフィードバック情報がないので,コンパイラは次のメッセージを出力します。
cc: Info: Feedback file prog does not exist (nofbfil) cc: Info: Compilation will proceed without feedback optimizations (nofbopt)
pixie
コマンドは,計測機構付きのプログラム (prog.pixie
) を作成して,それを実行します (prof
オプション,-update
が指定されているため)。
実行の統計情報とアドレスのマッピング・データは自動的に命令カウント・ファイル (prog.Counts
) と命令アドレス・ファイル (prog.Addrs
) に収集されます。
-update
オプションにより,このプロファイル情報は拡張された実行可能ファイルに格納されます。
[例に戻る]
-feedback オプションを指定した 2 度目のコンパイルでは,拡張された実行可能ファイル内のプロファイル情報がコンパイラと (-spike オプションを通じて) ポストリンク最適化プログラムを先導します。 このカスタマイズされたフィードバックは,-O3 および -spike オプションによる自動最適化よりも優れています。 コンパイラの最適化は,-ifo オプションや -assume whole_program オプションを -feedback オプションと組み合わせて使用すると,さらに効果が上がります。 ただし,10.1.1 項 で述べているように,大きいプログラムはソース・ファイルが 1 つしかない場合と同じようにコンパイルすることができないことがあります。 [例に戻る]
詳細は,
pixie
(1)cc
(1)
拡張された実行可能ファイルは,内部にプロファイル情報があるため,通常の実行可能ファイルよりも大きくなります (通常は 3 〜 5 パーセント)。
開発が完了したら,strip
コマンドを用いてプロファイル情報とシンボル・テーブル情報を削除できます。
たとえば次のようにします。
%
strip prog
spike
コマンドは,strip を実行したイメージは処理できません。
例 4
一般的な開発過程では,Example 3 の手順 2 と 3 を必要な回数だけ繰り返して,ソース・コードの変更による影響が反映されるようにします。 たとえば次のようにします。
%
cc -feedback prog -o prog -O3 *.c%
pixie -update prog%
cc -feedback prog -o prog -O3 *.c [modify source code]%
cc -feedback prog -o prog -O3 *.c ..... [modify source code]%
cc -feedback prog -o prog -O3 *.c%
pixie -update prog%
cc -feedback prog -o prog -spike -O3 *.c
拡張された実行可能ファイル内のプロファイル情報は,コンパイル操作では失われないので,情報を更新する
pixie
の処理手順は,ソース・モジュールが変更されて再コンパイルされるたびに繰り返す必要はありません。
ただし,変更のたびに,変更内容に応じて,実際のコードと古いフィードバック情報の違いが大きくなるため,最適化の有効度が低下します。
最後の変更および再コンパイルの後に
pixie
処理手順を行うと,最後に行ったコンパイルに合わせてフィードバック情報が正確に更新された状態になります。
例 5
プロファイルの正確な情報を得るために,計測機構付きプログラムを異なる入力で複数回実行したい場合があります。
この例では,プログラム
prog
の実行を 2 回計測して,そのプロファイル統計情報をマージすることでプログラムを最適化する方法を示しています。
このプログラムの出力は,異なる入力で実行するたびに異なります。
%
cc -feedback prog -o prog *.c [1]%
pixie -pids prog [2]%
prog.pixie [3] (input set 1)%
prog.pixie (input set 2)%
prof -pixie -update prog prog.Counts.* [4]%
spike prog -feedback prog -o prog.opt [5]
特に指定しなければ,計測機構付きプログラム (prog.pixie
) を実行するたびに,prog.Counts
という名前のプロファイル・データ・ファイルが作成されます。
-pids
オプションを指定すると,計測機構付きプログラムを実行するたびに,作成されるプロファイル・データ・ファイルの名前にプロセス ID が付加されます (prog.Counts.
pid)。
したがって,後の実行で生成されるデータ・ファイルで上書きされることはありません。
[例に戻る]
計測機構付きプログラムは 2 回実行され,そのたびに一意の名前でデータ・ファイルが生成されます。
たとえば,prog.Counts.371
と
prog.Counts.422
のようになります。
[例に戻る]
prof -pixie
コマンドは,2 つのデータ・ファイルをマージします。
-update
オプションにより,この結合された情報で実行可能ファイル
prog
が更新されます。
[例に戻る]
-feedback
オプション付きの
spike
コマンドは,最適化を先導するために 2 回のプログラム実行で得られたプロファイル情報を結合したものを使用し,最適化された出力ファイル
prog.opt
を生成します。
[例に戻る]
この例の最後の手順は,次のように変更できます。
%
cc -spike -feedback prog -o prog -O3 *.c
-spike
オプションでは,プログラムを再リンクする必要があります。
spike
コマンドを使用した場合は,spike
を実行するためにプログラムをリンクし直す必要はありません。
例 6
この例は,通常の (拡張されていない) 実行可能ファイルが作成され,spike
コマンドの
-fb
オプションが使用される (-feedback
オプションではない) 点で,Example 5
と異なります。
%
cc prog -o prog *.c%
pixie -pids prog%
prog.pixie (input set 1)%
prog.pixie (input set 2)%
prof -pixie -merge prog.Counts prog prog.Addrs prog.Counts.*%
spike prog -fb prog -o prog.opt
prof -pixie -merge
コマンドは,2 回の計測実行で得られた 2 つのデータ・ファイルを 1 つの
prog.Counts
ファイルにマージします。
この形式のフィードバックでは,-g1
オプションを明示的に指定して,プロファイリングに最適なシンボルを付ける必要があります。
spike -fb
コマンドは
prog.Addrs
および
prog.Counts
の情報を使用して,最適化された出力ファイル
prog.opt
を生成します。
Example 5
の方法をお勧めします。
Example 6
の方法は互換性のためにサポートされているので,実行可能ファイルに格納されたフィードバック情報を使用する
-feedback
オプションを指定してコンパイルすることができない場合にのみ使用してください。
10.1.4 前処理と後処理に関する考慮事項
性能に影響を及ぼす前処理オプションと後処理 (実行時) オプションには,次のようなものがあります。
Kuck & Associates Preprocessor (KAP) ツールを使用して,さらに最適化を行うことができます。 プリプロセッサは最終的なソース・コードを入力として使用し,最適化されたソース・コードを出力として生成します。
KAP は,シンメトリック・マルチプロセシング・システム (SMP) と単一プロセッサ・システムの両方で実行される次のような特性を持つアプリケーションに対して特に有効です。
SMP システムの並列処理機能を利用するため,KAP プリプロセッサは C プログラムに対して自動および指示による分解をサポートしています。 KAP の自動分解機能では,既存のプログラムを分析して,並列実行の候補となるループを探索します。 その後,ループを分解して,必要なすべての同期ポイントへ挿入します。 さらに制御が必要な場合には,プログラマが手操作で指示文を挿入して,個々のループの並列化を制御することができます。 Tru64 UNIX システムでは,KAP は POSIX Threads Library を使用して,並列処理を実現します。
C プログラムでは,KAP は
kapc
コマンド (単独の KAP 処理を起動する) または
kcc
(KAP 処理と HP C コンパイルを組み合わせて起動する) で起動します。
C プログラムで KAP を使用する方法については,『KAP for C for Tru64 UNIX』を参照してください。
Tru64 UNIX システムでは,KAP は別注文のレイヤード・プロダクトとして入手可能です。
特にプロファイル主導のフィードバックでは,ポストリンク最適化とプロシージャの再編成のために次のツールを使用します。
性能に影響を及ぼすライブラリ・ルーチンのオプションには,次のものがあります。
数値演算を集中して行うアプリケーションに対しては,CXML (Compaq Extended Math Library,以前の DXML: Digital Extended Math Library) を使用します。 CXML は Alpha システム (SMP システムと単一プロセッサ・システムの両方) 用に最適化された算術ルーチンの集まりです。 CXML に格納されているルーチンは,次の 4 つのライブラリに編成できます。
BLAS
基本的な線形代数サブルーチンで構成されるライブラリ。
LAPACK
線形システムと固有システムの問題を解決するための線形代数のパッケージ。
スパース線形システム・ソルバ
直接的な反復型スパース・ソルバのライブラリ。
シグナル処理
1 次元,2 次元,および 3 次元の高速フーリエ変換 (FFT),グループ FFT,正弦/余弦変換,重畳関数,相関関数,およびデジタル・フィルタを含むシグナル処理関数の基本セット。
CXML を使用することにより,数値演算を集中して行うアプリケーションは Tru64 UNIX システム上での実行速度がかなり向上します。
KAP と一緒に使用している場合は特に向上します。
CXML ルーチンは,ユーザのプログラムから明示的に呼び出すことができ,場合によっては,KAP から (つまり,KAP が CXML ルーチンを使用できると認識した場合) 呼び出すことができます。
コンパイル・コマンド行に
-ldxml
オプションを指定して,CXML にアクセスします。
CXML についての詳細は,『Compaq Extended Math Library Reference Guide』を参照してください。
CXML ルーチンは Fortran で書かれています。 C プログラムから Fortran ルーチンを呼び出す方法については,Tru64 UNIX についての Compaq Fortran (以前の Digital Fortran) のユーザ・マニュアルを参照してください。 C プログラムから CXML ルーチンを呼び出す方法については,『TechAdvantage C/C++ Getting Started Guide』にも説明があります。
アプリケーションで拡張精度が必要ない場合には,精度は多少落ちますが,実行速度の速い算術ライブラリ・ルーチンを使用することができます。
コンパイル・コマンド行で
-D_FASTMATH
オプションを指定すると,コンパイラは,浮動小数点精度の 3 ビットを犠牲にして,実行速度の速い浮動小数点ルーチンを使用するようになります。
詳細については,
cc
(1)
-D_INTRINSICS
および
-D_INLINE_INTRINSICS
オプションを使用して,C プログラムをコンパイルすることを検討してください。
このようにすると,コンパイラは特定の標準 C のライブラリ・ルーチンへの呼び出しをインライン化します。
アプリケーションを修正したい場合には,プロファイラ・ツールを使用して,アプリケーションの実行に最も時間がかかっている部分を判断します。 多くのアプリケーションでは,実行時間のほとんどは特定のルーチンで費やされています。 このような頻繁に使用されているルーチンについて,特に改善の努力を注ぐようにしてください。
Tru64 UNIX では,C および他の言語で作成されたプログラムに対して動作するいくつかのプロファイリング・ツールを提供しています。
詳細については,第 7 章,第 8 章,第 9 章
,
prof_intro
(1)gprof
(1)hiprof
(1)pixie
(1)prof
(1)third
(1)uprofile
(1)atom
(1)
アプリケーションで頻繁に使用されているルーチンが識別できたならば,そのコードが使用しているアルゴリズムについて検討してください。 より効率的なアルゴリズムと置換が可能なものはありませんか。 処理速度の遅いアルゴリズムは,既存のアルゴリズムに手を加えるより,処理速度の速いものと置換する方が,性能が大幅に改善されることがよくあります。
アルゴリズムの効率に問題がない場合は,コードを変更して,アプリケーションのオブジェクト・コードを生成するコンパイラが最適化を行いやすくすることを検討してください。 コンパイラによる最適化が最大限に行われるようなソース・コードの記述方法については,『High Performance Computing』(Kevin Dowd 著,O'Reilly & Associates, Inc., ISBN 1-56592-032-5) に,一般的な説明があります。
以降の各項で,性能改善について考慮の対象となるデータ型,入出力処理,キャッシュ使用とデータの境界合わせ,および言語固有の問題について説明します。
10.2.1 データ型についての考慮事項
性能に影響を及ぼすデータ型に関する考慮事項には,次のものがあります。
Alpha システム上で効率的なアクセスを行う最小単位は 32 ビットです。 32 ビットまたは 64 ビットのデータ項目は,単一の効率的な機械語命令でアクセスすることが可能です。 Alpha アーキテクチャの旧処理系 (プロセッサが EV56 より前のもの) でアプリケーションの性能が重要である場合には,次の点を考慮してください。
特に頻繁に使用されるスカラに対しては,32 ビットより小さい整数および論理データ型の使用は避けてください。
C プログラムでは,char
および
short
宣言を,int
および
long
宣言で置換することを検討してください。
整数値の除算は,浮動小数点数値の除算より,演算速度が遅くなります。 可能な場合には,そのような整数値演算を,浮動小数点演算と置換することを考慮してください。
整数の除算演算は Alpha プロセッサにネイティブなものではなく,ソフトウェアでエミュレートする必要があるため,演算速度が遅くなります。 その他のネイティブでない演算には,超越演算 (たとえば,正弦と余弦) と平方根があります。
直接入出力を使用すると,ファイル管理,オンライン・バックアップ,およびオンライン・リカバリなどの AdvFS (Advanced File System) が提供するファイル・システム機能を利用できるようになります。 しかも,ユーザ・データを AdvFS キャッシュにコピーすることによるオーバヘッドはありません。 直接入出力では,直接メモリ・アクセス (DMA) コマンドを使用して,アプリケーションのバッファとディスク間でユーザ・データを直接コピーします。
通常のファイル・システム入出力では,ファイル・ページをキャッシュ内に保持します。 これにより,入出力が非同期に完了します。 いったんデータがキャッシュ内に蓄えられて,入出力のスケジュールが設定されると,アプリケーションはデータがディスクに転送されるのを待つ必要はありません。 さらに,データが既にキャッシュ内にあるため,その後このページにアクセスする際には,ディスクからデータを読み込む必要はありません。 ほとのどのアプリケーションは通常のファイル・システム入出力を使用します。
通常のファイル・システム入出力は,ディスク上のデータに稀にしかアクセスせずに,スレッド間の競合を管理するアプリケーションには適しません。
この種のアプリケーションでは,直接入出力による低いオーバヘッドの利点を利用できます。
ただし,データはキャッシュされないため,指定されたページへのアクセスを,競合するスレッド間で直列化する必要があります。
これを行うため,省略時の設定では,直接入出力は同期入出力を実行します。
これは,read()
ルーチンがアプリケーションに制御を戻す時点で,入出力が完了しており,データはディスク上にあることを意味します。
その後,そのデータを取得する場合は,常にディスクからデータを取得するための入出力操作が行われます。
アプリケーションは,非同期入出力 (AIO) を利用することもできますが,その場合でも
aio_read()
および
aio_write()
システム・ルーチンを使用することにより,基礎となる直接入出力のメカニズムを使用しています。
これらのルーチンは,データがディスクに転送される前にアプリケーションに制御を戻します。
また
aio_error()
ルーチンは,アプリケーションが入出力の完了に対してポーリングを行うことを可能にします (カーネルは,ファイル・ページへのアクセスの同期処理を行って,2 つのスレッドが同時に同じページへ書き込みを行わないようにします)。
直接入出力を使用して指定されたファイルへアクセスする複数のスレッドは,同じページ範囲にアクセスしなければ,その作業を同時に行うことができます。 たとえば,スレッド A がページ 10 から 19 までに書き込みを行い,スレッド B がページ 20 から 39 までに書き込みを行う場合,これらの操作は同時に実行されます。 これに続いて,スレッド B がページ 15 から 39 まで単一の直接入出力転送で書き込みを行う場合には,ページ範囲が重複しているため,スレッド A の書き込みが完了するまでスレッド B は待機させられます。
直接入出力を使用するときには,要求された転送がディスク・セクタ境界に合っていて,転送サイズが基本セクタ・サイズの偶数倍である場合に,最高の性能が得られます。 最適な転送サイズはデータ格納用のハードウェアに依存しますが,一般に転送サイズが大きいほど,転送効率は向上します。
注意
直接入出力モードとマップされたファイル領域 (
mmap
) の使用は,排他的な操作です。 マップされたファイル領域を使用するファイルに対して,直接入出力モードを設定することはできません。 直接入出力用に既にオープンされているファイルに対してマッピングを行う場合も,操作は失敗します。直接入出力およびアトミック・データ・ロギング・モードも,相互に排他的です。 ファイルがこれらのいずれかのモードでオープンされている場合に,同じファイルを他のモードでオープンしようとすると,操作は失敗します。
AIO アプリケーションおよび非 AIO アプリケーションの両方について,直接入出力機能を有効にして,AdvFS ファイルで使用できます。
この機能を利用できるようにするには,O_DIRECTIO
ファイル・アクセス・フラグを設定し,アプリケーションで
open
関数を使用してます。
次に例を示します。
open ("file", O_DIRECTIO | O_RDWR, 0644)
直接入出力モードは,すべてのユーザによりファイルがクローズされるまで有効です。
fcntl()
関数にパラメータ
F_GETCACHEPOLICY
を指定して使用すると,ファイルのキャッシング・ポリシ (FCACHE
または
FDIRECTIO
モード) を返すことができます。
次に例を示します。
int fcntlarg = 0; ret = fcntl( filedescriptor, F_GETCACHEPOLICY, &fcntlarg ); if ( ret != -1 && fcntlarg == FDIRECTIO ) { . . .
直接入出力および AdvFS
の使用法についての詳細は,
fcntl
(2)open
(2)10.2.3 キャッシュ使用とデータの境界合わせに関する考慮事項
キャッシュ使用パターンは,次のように,性能にクリティカルな影響を及ぼします。
アプリケーションにいくつか頻繁に使用されるデータ構造体がある場合は,そのデータ構造体を 2 次キャッシュ内のキャッシュ・ライン境界に割り当てるようにしてください。 このようにすることにより,ユーザのアプリケーションがキャッシュを効率的に使用するように改善することができます。 詳細については,『Alpha Architecture Reference Manual』を参照してください。
頻繁に使用されるデータ構造体間の潜在的なデータ・キャッシュ衝突を捜してください。
メモリ内に割り当てられている 2 つのデータ構造体間の距離が一次 (内部) データ・キャッシュのサイズに等しい場合,このような衝突が起こります。
データ構造体が小さい場合は,それらをメモリ内に連続して割り当てることにより,これを回避することができます。
uprofile
ツールを使用すると,キャッシュ衝突数とその場所を判断することができます。
データ・キャッシュ衝突についての詳細は,『Alpha Architecture Reference Manual』を参照してください。
データの境界合わせも性能に影響を及ぼします。 省略時の設定では,C コンパイラは,各データ項目を自然境界に合わせます。 つまり,各データ項目を,その開始アドレスが,宣言に使用されたデータ型のサイズの倍数になるように位置付けます。 自然境界に位置合わせされていないデータを境界合わせの間違ったデータと呼びます。 境界合わせの間違ったデータは,実行時に,ソフトウェアで必要な調整を行うため,性能が落ちることがあります。
C プログラムでは,ポインタ変数を,あるデータ型からサイズの大きなデータ型へ型キャストした場合に,境界合わせ間違いが起こることがあります。
たとえば,char
ポインタ (1 バイトの境界合わせ) を
int
ポインタ (4 バイトの境界合わせ) へ型キャストした後,新しいポインタを逆参照すると,境界合わせされていないアクセスが発生することがあります。
また,C では,#pragma pack
指示文を使用してパック構造体を作成しても,境界合わせされていないアクセスが行われることがあります。
#pragma pack
指示文についての詳細は,第 3 章を参照してください。
C プログラムでの境界合わせの問題を修正するには,-misalign
オプション (または
-assume noaligned_objects) を使用するか,ソース・コードに必要な変更を行います。
何らかの理由によって,ユーザ・プログラムで境界合わせ間違いのインスタンスを必要とする場合には,境界合わせの間違ったデータを呼び出すすべてのポインタ宣言で
_ _unaligned
データ型識別子を使用します。
_ _unaligned
として宣言されたポインタを使用してデータがアクセスされると,コンパイラは,境界合わせエラーを発生させずにデータのコピーや格納を行うために必要な追加コードを生成します。
境界合わせエラーは,性能に対して,生成される追加コードよりもはるかに重大な影響を与えます。
C プログラムのコンパイル中には,境界合わせの間違ったデータを示す警告メッセージは表示されません。 ただし,プログラムの実行中には,境界合わせが間違っているデータがあると,カーネルが警告メッセージ ("unaligned access") を表示します。 メッセージには,境界合わせ間違いを引き起こした命令のアドレスを示すプログラム・カウンタ (PC) 値が示されます。
次のいずれかの方法を使用して,unaligned access の問題を引き起こすコードにアクセスすることができます。
デバッガを使用して "unaligned access" メッセージに表示されている PC 値を調べることにより,境界合わせ間違いを引き起こしている命令のあるルーチン名と行番号を見つけることができます。 ("unaligned access" メッセージは,呼び出しルーチンから渡されたポインタが原因で表示される場合もあります。 リターン・アドレス・レジスタ (ra) の中身が呼び出されたルーチンによって変更されていない場合,そのレジスタには呼び出し側ルーチンのアドレスが入っています。 )
コマンド行で -align オプションをオフにして,デバッガ・セッションでプログラムを実行すると,unaligned access のためにデバッガが停止した位置で,プログラムのスタックと変数を調べることができます。
データの境界合わせについての詳細は,『Alpha Architecture Reference Manual』を参照してください。
コンパイル・コマンド行に指定できる境界合わせを制御するオプションについての詳細は,
cc
(1)10.2.4 一般的なコーディングに関する考慮事項
libc
関数 (たとえば,strcpy
,
strlen
,
strcmp
,
bcopy
,
bzero
,
memset
,
memcpy
) を使用し,同様のルーチンやユーザ独自のループを書かない。
これらの関数は,効率を良くするためハード・コードされています。
可能な場合には,変数には
unsigned
データ型を使用する。
これは,次の 2 つの理由に因ります。
変数は必ずゼロ以上であるため,コンパイラは,変数がゼロより小さい場合には適用できない最適化を実行する。
符号なしの除算演算では,コンパイラの生成する命令が少なくなる。
次の例を見てください。
int long i; unsigned long j; . . return i/2 + j/2;
この例では,i/2
は不経済な式ですが,j/2
は経済的です。
コンパイラは符号付きの
i/2
演算に対しては,次の 3 つの命令を生成します。
addq $l, l, $28 cmovge $l, $l, $28 sra $28, l, $2
一方,符号なし
j/2
演算に対しては,1 つの命令だけを生成します。
srl $3, 1, $4
-unsigned
オプションを使用して,すべての
char
宣言を
unsigned char
として処理することも考慮してください。
アプリケーションが一時的に大量のデータを必要とする場合は,データを静的に宣言するのではなく,malloc
関数または
alloca
組み込み関数を使用することを考慮する。
alloca
関数は,スタックからメモリを割り当てます。
そのメモリは,割り当てた関数が戻ると自動的に解放されます。
初めて
alloca
を使用するコードでは,alloca.h
をインクルードしてください。
そうしないと,コードが正常に動作しない場合があります。
アプリケーションで,特定の関数呼び出しのコンテキストを越えて存在するメモリが必要な場合は,malloc
関数の使用を考慮します。
malloc
関数はプロセスのヒープからメモリを割り当てます。
このメモリは,free
を呼び出して明示的に解放するまで使用可能のままになります。
これらの関数を使用すると,物理メモリが不足しているアプリケーションで性能が改善されます。
マルチスレッド・アプリケーションでは,alloca
が呼び出し元スレッドのスタックからメモリを割り当てることに注意してください。
すなわち,このメモリを割り当てたり解放したりしても,争奪は起こりません。
malloc
関数 (および関連する関数) は,ロックおよびアトミック・オペレーションを用いて共通プールからメモリを割り当て,同時アクセスを制御します。
malloc
を使用するシングル・スレッドおよびマルチスレッド・アプリケーションの性能を簡単に改善する方法については,
malloc
(3)
また,マルチスレッド・アプリケーションの場合は,arena malloc (amalloc
) 機構を用いて,マルチスレッド・アプリケーションの各スレッドに対して別々のヒープをセットアップすることも考慮してください。
キャッシュ・ミスを回避するため,多次元配列が自然の記憶順にトラバースされるようにする。
つまり,行優先順で,右端の添字が 1 づつ最も速く変化するようにします。 列優先順 (Fortran で使用) は避けてください。
アプリケーションが 32 ビット・アドレス空間に適合し,多数のポインタを含む構造体を割り当てることによって,大量の動的メモリを割り当てる場合は,-xtaso
オプションを使用すると,かなりの量のメモリを節約することができる。
このオプションを使用する場合には,ポインタのサイズ割り当てを制御する C 言語プラグマを使用して,ソース・コードを修正する必要がある。
詳細については,
cc
(1)
C プログラムでは,間接呼び出し (つまり,ルーチンまたは関数へのポインタを引数として使用する呼び出し) を使用しない。
間接呼び出しを使用すると,グローバル変数を変更する可能性があります。 そのため,最適化プログラムによって安全に行われる最適化が少なくなります。
参照パラメータではなく値を返す関数を使用する。
できる限り,while
や
for
よりも
do while
を使用する。
do while
を使用すれば,最適化プログラムは,コードをループの内側から外側に移動するために,ループ条件を複写する必要がなくなります。
ローカル変数を使用し,グローバル変数は使用しない。
関数の外部変数は,別のソース・ファイルによって参照されない限り,static
として宣言します。
グローバル変数の使用を少なくすれば,コンパイラによって最適化しやすくなります。
参照パラメータやグローバル変数を使用しないで,値パラメータを使用する。
参照パラメータを使用すると,ポインタと同様に,最適化しにくくなります。
簡潔なコードを使用する。
たとえば,式の中では
++
や
--
はなるべく使用しないようにします。
これらのコードがもたらす副作用を考慮しないでその有用性の目的で使用すると,コード全体に悪影響を及ぼします。
たとえば,なるべく次のようなコードは避けます。
while (n--) { . . }
なるべく次のようなコードを使用してください。
while (n != 0) { n--; . . }
&
値を使用したアドレスの受け渡しは避ける。
&
を使用すると別名が生成され,最適化プログラムがレジスタから省略時の格納ディレクトリに変数を格納し,最適化が困難になります。
引数の数が可変である関数は使用しない。
このような関数を使用すると,最適化プログラムは入力されるすべてのパラメータ・レジスタを保存します。
他のソース・モジュールから参照されない場合は関数を静的に宣言する。
静的な関数を使用すると,より効果的な呼び出しシーケンスが使用されます。
また,逆参照の結果を格納する場合にはローカル変数を使用し,できるだけ別名を使用しないようにしてください。
逆参照の結果とは,指定されたアドレスから得られる値のことです。
逆参照の結果は,間接参照の演算および呼び出しによって影響を受けますが,ローカル変数は影響を受けません。
これは,ローカル変数は,レジスタに保持できるためです。
例 10-1
は,ポインタを適切に配置し,別名を削除することによって,コンパイラがより良いコードを生成することを示したものです。
例 10-1: ポインタと最適化
int len = 10; char a[10]; void zero() { char *p; for (p = a; p != a + len; ) *p++ = 0; }
例 10-1
のポインタの使用方法について考慮してみてください。
文
*p++ = 0
は
len
を変更することがあるので,コンパイラはループの外側でレジスタを使用して
a + len
を 1 度だけ演算するのではなく,ループを 1 回実行するたびにメモリから
len
をロードして,a
のアドレスに加算しなければなりません。
次の 2 つの方法を使用すると,例 10-1 のコードの効率を改善することができます。
ポインタの代わりに添字を使用する。
次の例に示すように,azero
プロシージャで添字を使用すれば,別名を指定しなくてもすみます。
コンパイラは,len
の値をレジスタに保持し,2 つの命令をセーブしています。
ポインタがソース・コードに指定されていなくても,コンパイラは,ポインタを使用して
a
を効率的にアクセスします。
ソース・コード
char a[10]; int len; void azero() { int i; for (i = 0; i != len; i++) a[i] = 0; }
ローカル変数の使用。
次の例に示すように
len
をローカル変数または仮引数として指定すると,別名が使用不能になるため,コンパイラが
len
をレジスタに置くことができます。
ソース・コード
char a[10]; void lpzero(len) int len; { char *p; for (p = a; p != a + len; ) *p++ = 0; }