分類
地域的範囲とグローバルな範囲 スコープとは 、入力コードのどの部分を最適化の適用対象とするかを示すものです。
ローカルスコープの最適化では、基本ブロック にローカルな情報を使用します。[ 4 ] 基本ブロックには制御フロー文が含まれていないため、これらの最適化では最小限の分析で済み、時間とストレージの要件が削減されます。ただし、ジャンプをまたいで情報は保持されません。
グローバルスコープ最適化(手続き内最適化とも呼ばれる)は、個々の関数に対して実行されます。[ 4 ] これにより、処理に使用できる情報が増えますが、多くの場合、コストのかかる計算が必要になります。関数呼び出しやグローバル変数へのアクセスが発生する際には、それらに関する情報がほとんどないため、最悪のケースを想定して処理する必要があります。
覗き穴最適化 ピーフホール最適化は通常、 マシンコード が生成された後、コンパイルプロセスの後半で実行されます。この最適化では、隣接するいくつかの命令を調べて(コードを「覗き穴から見る」のに似ています)、それらを単一の命令またはより短い命令のシーケンスに置き換えることができるかどうかを確認します。 [ 3 ] : 554 たとえば、値を2倍する演算は、値を左シフトする か、値自身に加算することでより効率的に実行できる場合があります(この例も強度削減 の例です)。
プロセス間最適化 プロシージャ間最適化は、 プログラムのソースコード全体を分析します。利用できる情報が多いほど、最適化の効果は高まります。この情報は、関数インライン化など、さまざまな最適化に利用できます。関数インライン化 では、関数呼び出しが関数本体のコピーに置き換えられます。
リンク時間最適化 リンク時最適化 (LTO)、またはプログラム全体最適化は、プロシージャ間最適化のより一般的なクラスです。LTOでは、コンパイラは翻訳単位全体にわたって可視性を持つため、モジュール間インライン化や仮想化解除 などのより積極的な最適化を実行できます。
マシンコードとオブジェクトコードの最適化 マシンコード最適化とは、すべてのマシンコードがリンクされた後に、 オブジェクトコードオプティマイザ を使用してプログラムを分析することです。共通の命令シーケンスを圧縮してスペースを節約するマクロ圧縮などの技術は、実行可能なタスクイメージ全体が分析に利用できる場合に、より効果的になります。[ 5 ]
言語非依存 vs. 言語依存 ほとんどの高級プログラミング言語は 、分岐構造(if、switch)、ループ構造(for、while)、カプセル化構造(構造体、オブジェクト)など、共通のプログラミング構造と抽象化を共有しています。そのため、同様の最適化手法を複数の言語で使用できます。ただし、特定の言語機能によって、一部の最適化が困難になる場合があります。たとえば、C およびC++ のポインタは配列の最適化を困難にします(エイリアス解析を 参照)。しかし、PL/I のようにポインタをサポートする言語では、配列の最適化が実装されています。逆に、一部の言語機能によって、特定の最適化が容易になる場合があります。たとえば、一部の言語では、関数に副作用 を持たせることは許可されていません。そのため、プログラムが同じ引数で同じ関数を複数回呼び出す場合、コンパイラは関数の結果を一度だけ計算すればよいと推論できます。関数に副作用を持たせることが許可されている言語では、コンパイラは副作用がないと判断できる関数にのみ、このような最適化を制限できます。
マシン非依存 vs. マシン依存 抽象的なプログラミング概念(ループ、オブジェクト、構造体など)に基づいて動作する最適化の多くは、コンパイラが対象とするマシンに依存しませんが、最も効果的な最適化の多くは、対象プラットフォームの特殊な機能を最大限に活用するものです。例えば、レジスタをデクリメントし、ゼロでない場合は分岐するなど、複数の処理を同時に実行する命令が挙げられます。
以下は、ローカルマシン依存の最適化の例です。レジスタ を 0 に設定するには、レジスタ値を定数に設定する命令で定数 '0' を使用するのが明白な方法です。あまり明白ではない方法としては、レジスタをそれ自身とXOR する か、レジスタからそれ自身を減算する方法があります。どの命令バリアントを使用するかはコンパイラの判断に委ねられます。多くのRISC マシンでは、どちらの命令も同じ長さで同じ時間で実行されるため、どちらも適切です。Intel x86 ファミリーなどの他の多くのマイクロプロセッサ では、即値オペランドをデコードしたり、内部の「即値オペランド レジスタ」を使用したりする必要がないため、XOR バリアントの方が短く、おそらく高速であることがわかります。減算バリアントについては、IBM System/360 とその後継機でも同様です。[6] これに関する潜在的な問題は、XOR または減算によってレジスタの前の値がデータに依存するようになり、パイプライン ストールが 発生 する可能 性 が あることです。パイプライン ストールは、プロセッサが前の命令の結果に依存するため、命令の実行を遅延させる必要がある場合に発生します。しかし、プロセッサは多くの場合、レジスタとそれ自身とのXOR演算や、レジスタとそれ自身との減算を、ストールを引き起こさない特殊なケースとして扱います。
最適化に影響を与える要因 ターゲットマシン 特定の最適化を適用できるかどうか、また適用すべきかどうかは、対象マシンの特性によって決まる場合があります。GCCやClangなどの一部のコンパイラは 、 マシン依存の係数をパラメータ化して、異なるマシン向けに最適化できるようにしています。[ 7 ] 対象CPU アーキテクチャ レジスタ の数:レジスタはパフォーマンスを最適化するために使用できます。ローカル変数は スタック ではなくレジスタに格納できます。一時的/中間的な結果は、低速なメモリではなくレジスタからアクセスできます。RISC とCISC の比較: CISC 命令セットは、命令長が可変であることが多く、[ 8 ] 使用できる命令の数が多く、各命令の実行時間が異なる可能性があります。RISC 命令セットは、これらの変動を制限しようとします。命令セットは通常、例外を除いて長さが一定であり、レジスタとメモリ操作の組み合わせは通常少なく、メモリの遅延が問題にならない場合は、命令発行率 (時間間隔で完了する命令の数、通常はクロックサイクルの整数倍) は通常一定です。特定のタスクを実行する方法は複数ある可能性があり、CISC は通常、RISC よりも多くの選択肢を提供します。コンパイラは、さまざまな命令間の相対的なコストを把握し、最適な命令シーケンスを選択する必要があります (命令選択を 参照)。パイプライン:パイプラインとは、CPUを アセンブリライン のように分割したものです。命令の実行を命令デコード、アドレスデコード、メモリフェッチ、レジスタフェッチ、計算、レジスタストアなど、さまざまな段階に分割することで、CPUの各部分を異なる命令に使用できるようにします。ある命令がレジスタストア段階にある間に、別の命令がレジスタフェッチ段階にあることもあります。パイプラインの競合は、パイプラインのある段階の命令が、パイプライン内でその前にある命令の結果に依存しているが、まだ完了していない場合に発生します。パイプラインの競合は、パイプラインストール につながる可能性があります。パイプラインストールとは、CPUが競合の解決を待つためにサイクルを無駄にする状態です。コンパイラは、パイプラインストールの発生頻度を減らすために、命令をスケジュールし たり、順序を変更したりできます。機能ユニットの数 :一部のCPUは、複数の命令を同時に実行できるALU とFPUを 複数備えています。どの命令をどの命令とペアリングできるか(「ペアリング」とは、2つ以上の命令を同時に実行すること)、どの機能ユニットがどの命令を実行できるかには制限がある場合があります。また、パイプラインの競合と同様の問題も発生します。機能ユニットが完全にロードされるように命令をスケジュールすることができます。マシンアーキテクチャ CPUキャッシュの サイズと種類(ダイレクトマップ、2ウェイ/4ウェイ/8ウェイ/16ウェイ連想、完全連想):インライン展開 やループ展開 などの手法を用いると、生成されるコードのサイズが大きくなり、コードの局所性が低下する可能性があります。コードサイズが大きくなる最適化の結果、頻繁に使用されるコード部分(各種アルゴリズムの内部ループなど)がキャッシュに収まらなくなると、プログラムの実行速度が大幅に低下する可能性があります。また、完全連想でないキャッシュは、キャッシュが空いている場合でもキャッシュ衝突が発生する可能性が高くなります。キャッシュ/メモリ転送速度:これは、コンパイラにキャッシュミスによるペナルティの目安を示します。これは主に特殊なアプリケーションで使用されます。 使用目的 デバッグ :開発中は、コンパイル速度を向上させたり、実行可能コードのデバッグを容易にするために、最適化を無効にすることがよくあります。特にコードの順序を変更するような最適化変換を行うと、実行可能コードとソースコードの関連付けが難しくなる場合があります。汎用的な用途:パッケージ化されたソフトウェアは、多くの場合、同じ命令セットを共有しながらも性能特性が異なる様々なマシンで動作することが想定されています。そのため、特定のマシン向けに最適化されていないコードや、最も普及しているマシンで最適に動作するように調整されている一方で、他のマシンでは最適に動作しない場合があります。 特殊用途:ソフトウェアが均一な特性を持つマシン向けにコンパイルされている場合、コンパイラは生成されたコードをそれらのマシン向けに高度に最適化することができます。 注目すべき例としては、並列 プロセッサやベクトルプロセッサ 向けに設計されたコードがあり、これらのコードには専用の並列化コンパイラ が使用されます。組み込みシステム 向けファームウェアは、ターゲットとなるCPUとメモリに合わせて最適化できます。システムのコストや信頼性は、コードの実行速度よりも重要視される場合があります。例えば、組み込みソフトウェア用のコンパイラは通常、速度を犠牲にしてコードサイズを削減するオプションを提供しています。コードのタイミングは、可能な限り高速であることよりも予測可能であることが求められる場合があるため、コードキャッシュや、それを必要とするコンパイラの最適化機能が無効になることがあります。
共通のテーマ 最適化には、以下のような、時に相反するテーマが含まれる。
一般的なケースを最適化する 一般的なケースでは、遅い経路 を犠牲にして速い経路 を選択できるような特有の性質が存在する場合があります。速い経路がより頻繁に利用されれば、結果として全体的なパフォーマンスが向上します。 重複を避ける 既に計算済みの結果を再利用し、再計算するのではなく、後で使用するために保存する。 コードの削減 不要な計算や中間値を削除しましょう。CPU、キャッシュ、メモリの負荷が軽減されれば、通常は実行速度が向上します。また、組み込みシステム においては、コード量を減らすことで製品コストを削減できます。 直線コードを 使用することでジャンプ回数を減らす(分岐のないコード とも呼ばれる)。よりシンプルなコード。ジャンプ(条件分岐または無条件分岐 )は命令のプリフェッチを妨げ、コードの速度を低下させます。インライン展開またはループ展開を使用すると分岐を減らすことができますが、その代償として、繰り返しコードの長さに応じてバイナリファイルのサイズが増加します。これにより、複数の 基本ブロックが 1つに統合される傾向があります。 地域 時間的に近接してアクセスされるコードとデータは、参照の 空間的局所性を高めるために、メモリ内で近接して配置されるべきである。メモリ階層を活用する メモリ階層 の各レベルにおいて、メモリへのアクセスコストはますます高くなるため、最も頻繁に使用される項目は、まずレジスタに、次にキャッシュに、そして最後にメインメモリに配置してから、ディスクにアクセスします。並列化する 命令レベル、メモリレベル、またはスレッドレベルのいずれかで、複数の計算を並列実行できるように、処理の順序を変更します。 より正確な情報の方が望ましい コンパイラが持つ情報が正確であればあるほど、これらの最適化手法のいずれか、またはすべてをより効果的に活用できる。 ランタイムメトリクスは役立つ テスト実行中に収集された情報は、プロファイル誘導型最適化 に利用できます。実行時に収集された情報は、理想的には最小限のオーバーヘッドで、 JIT コンパイラによって最適化を動的に改善するために使用できます。 強度低下 複雑で難解、あるいはコストのかかる演算を、より単純な演算に置き換える。例えば、定数による除算をその逆数による乗算に置き換えたり、帰納法を用いた変数解析 によってループインデックスによる乗算を加算に置き換えたりする。
具体的なテクニック
ループ最適化 ループ最適化は、 for ループなどのループを構成するステートメントに対して作用します。例えば、ループ不変コード移動などが 挙げられます。多くのプログラムは処理時間の大部分をループ内で過ごすため、ループ最適化は大きな効果を発揮する可能性があります。[ 3 ] : 596
ループ処理を主な目的とした最適化手法には、以下のようなものがあります。
誘導変数分析 大まかに言うと、ループ内の変数が、 などのインデックス変数の単純な線形関数である場合、ループ変数が変更されるたびに適切に更新できます。これは強度の低下 であり、インデックス変数の定義がデッドコード になる可能性もあります。[ 3 ] : 596-598 この情報は、境界チェックの排除 や依存関係の分析 などにも役立ちます。j := 4*i + 1 ループ分裂 またはループ分布ループ分割は、同じインデックス範囲でループを複数のループに分割し、それぞれの新しいループが元のループ本体の一部のみを使用するようにする手法です。これにより、ループ内でアクセスされるデータとループ本体内のコードの両方に対する参照の局所性を向上させることができます。 ループ融合 、ループ結合、ループ衝突、ループジャミングループのオーバーヘッドを削減するためのもう一つの手法は、隣接する2つのループがコンパイル時にその回数が分かっているかどうかに関わらず同じ回数だけ反復する場合、互いのデータを参照しない限り、ループ本体を結合できるというものです。 ループ反転 この手法では、標準的なwhile ループを、if 条件で囲まれたdo/while ( repeat/until とも呼ばれる) ループに変更し、ループが実行される場合のジャンプ回数を 2 回削減します。この方法では条件チェックが重複するためコードサイズは大きくなりますが、ジャンプは通常パイプラインの停止を引き起こすため、より効率的です。さらに、初期条件がコンパイル時に既知であり、 副作用が ないことがわかっている場合は、if ガードをスキップできます。 ループ交換 これらの最適化では、内側のループと外側のループを入れ替えます。ループ変数が配列のインデックスである場合、このような変換は配列のレイアウトによっては参照の局所性を向上させることができます。 ループ不変コードモーション ループ内で各反復処理中に量が計算され、その値が各反復処理で同じである場合、ループの外に持ち上げて、ループが始まる前に一度だけその値を計算すると、効率が大幅に向上します。[ 3 ] : 596これは、配列に対するループによって生成されるアドレス計算式で特に重要です。正しく実装するには、この手法をループ反転 と組み合わせて使用する必要があります。すべてのコードがループの外に持ち上げても安全とは限らないためです。 ループネストの最適化 行列乗算など、広く用いられているアルゴリズムの中には、キャッシュの動作が非常に悪く、メモリへのアクセスが過剰になるものがあります。ループネスト最適化は、小さなブロック単位で処理を行い、ループの交換を用いることで、キャッシュヒット数を増加させます。 ループ反転 ループ反転は、インデックス変数に値を割り当てる順序を逆にします。これは、依存関係 を排除し、他の最適化を可能にするのに役立つ微妙な最適化です。さらに、一部のアーキテクチャでは、ループ反転によってコードが小さくなります。ループインデックスがデクリメントされるとき、実行中のプログラムがループを終了するために満たす必要がある条件は、ゼロとの比較だからです。これは多くの場合、比較対象の数値が必要な数値との比較とは異なり、特別なパラメータなしの命令です。したがって、ループ反転を使用することで、パラメータを格納するために必要なバイト数を節約できます。さらに、比較する数値がプラットフォームのワードサイズを超える場合、標準のループ順序では、比較を評価するために複数の命令を実行する必要がありますが、ループ反転ではそのようなことはありません。 ループ展開 ループ展開では、ループ条件のテスト回数とジャンプ回数を減らすために、ループ本体を複数回複製します。テストとジャンプは、命令パイプラインを阻害することでパフォーマンスを低下させる可能性があります。「ジャンプ回数を減らす」最適化です。ループを完全に展開すると、すべてのオーバーヘッドがなくなりますが、コンパイル時に反復回数がわかっている必要があります。 ループ分割 ループ分割は、ループを複数のループに分割することで、ループを簡略化したり、依存関係を解消したりする手法です。分割されたループは、同じ本体を持ちながら、インデックス範囲の異なる連続した部分を反復処理します。ループピーリング は、ループの最初の反復処理に問題があるループを簡略化するために、ループに入る前にその反復処理を個別に実行できる便利な特殊なケースです。 ループの切り替え解除 アンスイッチングとは、条件文のif節とelse節のそれぞれの中にループ本体を複製することで、条件文をループの内側から外側へ移動させる処理です。 ソフトウェアパイプライン ループは、1回の反復処理で行われる作業を複数の部分に分割し、複数回の反復処理にわたって実行するように再構成されます。タイトなループでは、この手法によって値の読み込みと使用の間の遅延が隠蔽されます。 自動並列化 ループは、マルチコアマシンを含む共有メモリ型マルチプロセッサ(SMP)マシンで複数のプロセッサを同時に使用するために、マルチスレッドコードまたはベクトル化コード(あるいはその両方)に変換されます。
先見性のある店舗最適化 先見的なストア最適化により、スレッド とロックのコンテキストで通常許可されるよりも早くストア操作を実行できます。プロセスは、実行されるべき代入によって格納される値を事前に知る方法が必要です。この緩和の目的は、コンパイラ最適化が、適切に同期されたプログラムのセマンティクスを維持する特定の種類のコード再配置を実行できるようにすることです。[ 9 ]
データフローの最適化 データフロー分析 に基づくデータフロー最適化は、主に 制御フローグラフ 内の制御エッジによってデータの特定の特性がどのように伝播されるかに依存します。これらの特性には以下のようなものがあります。
共通サブ発現の除去 式において(a + b) - (a + b)/4、「共通部分式」とは、複製された を指します(a + b)。この手法を実装するコンパイラは が(a + b)変化しないことを認識し、その値を一度だけ計算します。[ 3 ] : 592-594 絶え間ない折り畳みと伝播 定数(例:)で構成される式を、実行時に計算するのではなく、コンパイル時に3 + 5最終値()に置き換える。 [ 10 ] ほとんどの現代的な言語で使用されている。8 誘導変数の認識と排除 誘導変数分析 に関する上記の議論を参照してください。エイリアス分類とポインタ分析 ポインタ が存在する場合、メモリ位置が割り当てられる際に潜在的にあらゆる変数が変更されている可能性があるため、最適化を行うことは困難です。どのポインタがどの変数にエイリアスできるかを指定することで、無関係なポインタを無視することができます。デッドストアの 排除変数の有効期間が終了した場合、または後続の代入によって最初の値が上書きされる場合など、後続の代入によって読み取られない変数への代入を削除します。
コードジェネレータの最適化 レジスタ割り当て 最も頻繁に使用される変数は、アクセス速度を最速にするためにプロセッサレジスタに保持する必要があります。レジスタに格納する変数を特定するために、干渉グラフが作成されます。各変数は頂点であり、2つの変数が同時に使用される場合(共通する有効範囲を持つ場合)、それらの間にエッジが存在します。このグラフは、レジスタの数と同じ数の色を使用して、例えばChaitinのアルゴリズム で色付けされます。色付けが失敗した場合は、1つの変数がメモリに「スピル」され、色付けが再試行されます。 命令の選択 ほとんどのアーキテクチャ、特にCISC アーキテクチャや多くのアドレッシング モード を持つアーキテクチャでは、まったく異なる命令シーケンスを使用して、特定の操作を実行するための複数の異なる方法が提供されています。命令セレクタの役割は、低レベルの中間表現 でどの演算子を実装するかを全体的に適切に選択することです。たとえば、68000 ファミリ や x86 アーキテクチャの多くのプロセッサでは、 のようなステートメントで複雑なアドレッシング モードを使用できlea 25(a1,d5*4), a0、1 つの命令で少ないストレージでかなりの量の算術演算を実行できます。 授業スケジュール 命令スケジューリングは、現代のパイプラインプロセッサにとって重要な最適化手法であり、依存関係のない命令をまとめて配置することでパイプライン内のストールやバブルを回避しつつ、元のセマンティクスを維持するように注意を払う。 再物質化 リマテリアライゼーションは、メモリから値を読み込む代わりに値を再計算することで、メモリへのアクセスをなくします。これは、メモリのスピルを防ぐためにレジスタ割り当てと並行して実行されます。 コードファクタリング 複数のコードシーケンスが同一である場合、または同一になるようにパラメータ化または並べ替えることができる場合は、それらを共有サブルーチンへの呼び出しに置き換えることができます。これにより、サブルーチンの設定や場合によっては末尾再帰のコードを共有できます。[ 13 ] トランポリン 多くのCPUは、低位メモリにアクセスするためのより小さなサブルーチン呼び出し命令を持っています。コンパイラは、コードの本体でこれらの小さな呼び出しを使用することでスペースを節約できます。低位メモリのジャンプ命令は、任意のアドレスのルーチンにアクセスできます。これにより、コードファクタリングによるスペース節約効果が倍増します。[ 13 ] 計算の順序変更 整数線形計画法 に基づく再構築コンパイラは、計算の順序を変更することでデータ局所性を高め、並列性を向上させます。空間最適化コンパイラは、サブルーチンに分割可能なシーケンスを長くするために、コードの順序を変更する場合があります。
関数型言語の最適化 これらの多くは非関数型言語にも当てはまりますが、Lisp やML のような関数型言語 に由来するものか、あるいは関数型言語において特に重要なものです。
末尾呼び出し最適化 関数呼び出しはスタック領域を消費し、パラメータの受け渡しや命令キャッシュのフラッシュに関連するオーバーヘッドを伴います。末尾再帰 アルゴリズムは、末尾再帰除去または末尾呼び出し最適化と呼ばれるプロセスによって反復処理 に変換できます。 森林破壊 (データ構造 融合)リストに対して一連の変換を適用することが一般的な言語では、デフォレーションは中間データ構造の構築を排除しようとする試みである。 部分的な評価 実行時の動的な入力に関わらず同じ出力を生成する計算は、コンパイル時に評価することができる。
その他の最適化 境界チェックの排除 Java などの多くの言語では、すべての配列アクセスに対して境界チェックが 強制されます。これは、科学計算コードなどの特定のアプリケーションにおいて、深刻なパフォーマンスボトルネック となります。境界チェックの排除により、コンパイラは、インデックスが有効な範囲内に収まる必要があると判断できる多くの状況(例えば、単純なループ変数など)で、境界チェックを安全に削除できます。分岐オフセット最適化(マシン依存) 目標地点に到達する最短の分岐変位を選択してください。 コードブロックの並べ替え コードブロックの並べ替えとは、プログラム内の基本ブロック の順序を変更することで、条件分岐を減らし、参照の局所性を向上させることです。デッドコードの削除 プログラムの動作に影響を与えない命令、例えば、使用されていない定義(デッドコード と呼ばれる)を削除します。これにより、コードサイズが削減され、不要な計算が排除されます。 不変条件(ループ不変条件 )の因数分解条件が満たされた場合と満たされない場合の両方で実行される式は、条件文の外で一度だけ記述すればよい。同様に、特定の種類の式(例えば、定数を変数に代入する式)がループ内に現れる場合、それらの式は複数回実行されても一度だけ実行されても効果が同じであるため、ループの外に移動できる。これは完全冗長性除去とも呼ばれる。これと似ているが、より強力な最適化手法として、部分冗長性除去 (PRE)がある。 インライン展開 またはマクロ 展開あるコードがプロシージャ を呼び出す際、制御をプロシージャに渡すのではなく、呼び出し元のコード内にプロシージャ本体を直接挿入することが可能です。これにより、プロシージャ呼び出しに伴うオーバーヘッドを削減できるだけでなく、様々なパラメータ固有の最適化を行う機会も得られますが、スペースの損失という代償が伴います。プロシージャがインラインで呼び出されるたびに、プロシージャ本体が複製されるためです。一般的に、インライン化は、小さなプロシージャを多数呼び出すパフォーマンス重視のコードで有効です。これは「ジャンプ回数を減らす」最適化の一種です。命令型プログラミング 言語のステートメントも、このような最適化の一例です。ステートメントは 関数呼び出し で実装することも可能ですが、ほとんどの場合、コードインライン化によって実装されます。ジャンプスレッド この最適化では、完全にまたは部分的に同じ条件に基づく連続する条件付きジャンプが統合されます。 例えば: if ( cond ) { foo (); } if ( cond ) { bar (); } // 次のように変更されます: if ( cond ) { foo (); bar (); } そして: if ( cond ) { foo (); } if ( ! cond ) { bar (); } // 次のように変更されます: if ( cond ) { foo (); } else { bar (); } マクロ圧縮 共通するコードシーケンスを認識し、共通コードを含むサブルーチン(「コードマクロ」)を作成し、共通コードシーケンスの出現箇所を対応するサブルーチンへの呼び出しに置き換えるスペース最適化。[ 5 ] これは、すべてのコードが存在する場合に、マシンコード最適化として最も効果的に実行されます。この手法は、 マイクロコンピュータ 上のMacro Spitbol の実装で使用される解釈型バイトストリーム のスペースを節約するために最初に使用されました。[ 14 ] 特定のコードセグメントに必要なスペースを最小化する最適なマクロセットを決定する問題はNP 完全で あることが知られていますが、[ 5 ] 効率的なヒューリスティックによりほぼ最適な結果が得られます。[ 15 ] キャッシュ 衝突の削減(例えば、ページ内の配置を乱すことによって) スタック 高さの削減式評価に必要なリソースを最小限に抑えるように、式ツリーを再配置します。 テストの並べ替え 2つのテストが何らかの条件となっている場合、まず単純なテスト(例えば、変数と何かを比較するテスト)を処理し、その後で複雑なテスト(例えば、関数呼び出しを必要とするテスト)を処理することができます。この手法は遅延評価 を補完するものですが、テスト同士が相互に依存していない場合にのみ使用できます。短絡評価 セマンティクスでは、この方法が難しくなる場合があります。
プロシージャ間最適化 プロシージャ間最適化は 、プロシージャやファイルの境界を越えてプログラム全体に対して機能します。これは、ローカル部分とグローバル部分の連携によって実行されるプロシージャ内最適化と密接に連携します。典型的なプロシージャ間最適化には、プロシージャのインライン化 、プロシージャ間のデッドコード削除、プロシージャ間の定数伝播、プロシージャの並べ替えなどがあります。通常どおり、コンパイラは実際の最適化の前にプロシージャ間解析を実行する必要があります。プロシージャ間解析には、エイリアス解析、配列アクセス解析、 呼び出しグラフ の構築などが含まれます。
プロシージャ間最適化は、 SGI 、Intel 、Microsoft 、Sun Microsystems の最新の商用コンパイラでは一般的です。長い間、オープンソースのGCC は 強力なプロシージャ間分析と最適化が不足していると批判されていましたが、現在は改善されています。[ 16 ] 完全な分析と最適化インフラストラクチャを備えた別のオープンソースコンパイラはOpen64 です。
プロシージャ間解析には余分な時間とメモリが必要となるため、ほとんどのコンパイラはデフォルトでは実行しません。ユーザーはコンパイラオプションを明示的に使用して、コンパイラにプロシージャ間解析やその他のコストのかかる最適化を有効にするように指示する必要があります。
実務上の考慮事項 コンパイラが実行できる最適化には、コンパイル時間がほとんどかからない単純で直接的な最適化から、コンパイル時間がかなりかかる複雑で高度な最適化まで、幅広い種類があります。[ 3 ] : 15 そのため、コンパイラは、コンパイラのユーザーがどの程度の最適化を要求するかを選択できるように、制御コマンドまたはプロシージャにオプションを提供することがよくあります。たとえば、IBM FORTRAN H コンパイラでは、最適化なし、レジスタレベルでの最適化のみ、または完全な最適化を指定できます。[ 3 ] : 737 2000 年代までには、 Clang などのコンパイラでは、おなじみのスイッチから始まるさまざまな最適化の選択に影響を与えることができる複数のコンパイラ コマンド オプションを持つことが一般的でした-O2。[ 17 ]
最適化を分離するアプローチの一つとして、いわゆるポストパスオプティマイザ の使用が挙げられる(その商用版の中には、1970年代後半のメインフレームソフトウェアにまで遡るものもある)。[ 18 ] これらのツールは、最適化コンパイラによって生成された実行可能出力を受け取り、さらに最適化を行う。ポストパスオプティマイザは通常、アセンブリ言語 またはマシンコード レベルで動作する(プログラムの中間表現を最適化するコンパイラとは対照的である)。その一例として、 1980年代のPortable C Compiler (PCC)が挙げられる。PCCには、生成されたアセンブリコードに対してポスト最適化を実行するオプションのパスがあった。[ 3 ] : 736
もう一つ考慮すべき点は、最適化アルゴリズムは複雑であり、特に大規模で複雑なプログラミング言語のコンパイルに使用される場合、生成されたコードにエラーを導入したり、コンパイル中に内部エラーを引き起こしたりするバグが含まれている可能性があるということです。コンパイラエラーはどのような種類であってもユーザーを困惑させる可能性がありますが、この場合は最適化ロジックに問題があることが明確でない可能性があるため、特に困惑します。[ 19 ] 内部エラーの場合、コンパイラの最適化ロジックを、失敗を捕捉し、警告メッセージを発行し、コンパイルの残りの部分が正常に完了するようにコーディングする「フェイルセーフ」プログラミング手法によって、問題は部分的に軽減できます。[ 20 ]
参考文献 ↑ ゴッドボルト、マット(2019年11月12日)。「C++コンパイラにおける最適化」。ACM Queue 。第 17巻、第 5号。 ↑ 「講義15:NP完全性、最適化、および分離」 (PDF) 。IE 511:整数計画法、2021年春学期 。 1 2 3 4 5 6 7 8 9 10 11 Aho, Alfred V.; Sethi, Ravi; Ullman, Jeffrey D. (1986). Compilers: Principles, Techniques, and Tools . Reading, Massachusetts: Addison-Wesley. ISBN 0-201-10088-6 。1 2 クーパー、キース D. ; トルツォン、リンダ (2003) [2002-01-01]. コンパイラのエンジニアリング . モーガン・カウフマン . pp. 404, 407. ISBN 978-1-55860-698-2 。1 2 3 Goss, Clinton F. (2013 年 8 月) [初版発行 1986 年 6 月]. マシン コード最適化 – 実行可能オブジェクト コードの改善 (PDF) (博士論文). Vol. コンピュータ サイエンス学科技術報告書 #246. クーラント研究所、ニューヨーク大学. arXiv : 1308.4815 . Bibcode : 2013arXiv1308.4815G . 2022 年 10 月 9 日にオリジナルからアーカイブ ( PDF) . 2013 年 8 月 22 日 に取得 . クリントン・F・ゴス(2013)[1986]。マシンコード最適化 - 実行可能オブジェクトコードの改善 (博士論文)。クーラント研究所、ニューヨーク大学。 ↑ IBM System/360 アセンブリ言語のプログラマ入門 (PDF) . IBM . p. 42. GC20-1645-5. ↑ 「GCC – マシン依存オプション」 。GNU プロジェクト 。 ↑ 「RISC vs. CISC」 . cs.stanford.edu . 2024年10月15日 取得 。 ↑ James Gosling ; Bill Joy ; Guy Steele . "17 スレッドとロック" . Java 言語仕様 (1.0 版). 17.8 予見ストアアクション。 ↑ スティーブン・マックニック、マックニック・アンド・アソシエイツ(1997年8月15日)。 『 Advanced Compiler Design Implementation』 。モーガン・カウフマン。329ページ 以降 。ISBN 978-1-55860-320-2 連続的な折りたたみ 。↑ Wegman, Mark N.; Zadeck, F. Kenneth (1991 年 4 月). "条件分岐による定数伝播" (PDF) . ACM Transactions on Programming Languages and Systems . 13 (2): 181– 210. doi : 10.1145/103135.103136 . ↑ Click, Clifford; Cooper, Keith. (1995 年 3 月). "分析の組み合わせ、最適化の組み合わせ" (PDF) . ACM Transactions on Programming Languages and Systems . 17 (2): 181– 196. doi : 10.1145/201059.201061 . 1 2 Cx51コンパイラマニュアル、バージョン09.2001、p.155、Keil Software Incorporated。 ↑ Dewar, Robert BK ; Golumbic, Martin Charles ; Goss, Clinton F. (2013 年 8 月) [1979 年 10 月]. MICRO SPITBOL . コンピュータサイエンス学科技術報告書。第 11 巻。クーラント数理科学研究所 。arXiv : 1308.6096 . Bibcode : 2013arXiv1308.6096D . ↑ Golumbic, Martin Charles ; Dewar, Robert BK ; Goss, Clinton F. (1980). "MICRO SPITBOL におけるマクロ置換 - 組み合わせ解析". Proceedings of the 11th Southeastern Conference on Combinatorics, Graph Theory and Computing, Congressus Numerantium, Utilitas Math., Winnipeg, Canada . 11th Southeastern Conference on Combinatorics, Graph Theory and Computing. Vol. 29. pp. 485– 495. ↑ Glazunov, NM (2012年11月25日). "科学研究の基礎". arXiv : 1212.1651 [ cs.OH ]. ↑ Guelton, Serge (2019年8月5日). 「Clangでコンパイルプロセスをカスタマイズする: 最適化オプション」 . Red Hat. ↑ Evans, Michael (1982年12月). 「Cobol環境のためのソフトウェアエンジニアリング」 . Communications of the ACM . 25 (12): 874–882 . doi : 10.1145/358728.358732 . 2013年8月10日 取得。 ↑ Sun, Chengnian 他 (2016年7月18日~20日) 「GCCとLLVMにおけるコンパイラバグの理解に向けて」 . 第25回国際ソフトウェアテスト・分析シンポジウム論文集 . Issta 2016. pp. 294–305 . doi : 10.1145/2931037.2931074 . ISBN 9781450343909 . S2CID 8339241 . ↑ Schilling, Jonathan L. (1993年8月). "コンパイラ最適化におけるフェイルセーフプログラミング". ACM SIGPLAN Notices . 28 (8): 39–42 . doi : 10.1145/163114.163118 . S2CID 2224606 .
外部リンク Agner Fog による最適化マニュアル– x86プロセッサアーキテクチャと低レベルコード最適化に関するドキュメント