PGOには、次のタイプの最適化が含まれる場合があります( ソース ):
- インライン化 -たとえば、関数Aが関数Bを頻繁に呼び出し、関数Bが十分に小さい場合、関数BはAに埋め込まれます。これは、プログラム起動の実際の統計に基づいて行われます。
- 仮想呼び出しの投機 -仮想呼び出し、または関数を介した呼び出しの場合、ポインターは特定の関数を指すことが多く、特定の関数の条件付き直接呼び出し(条件が満たされたときにトリガーされる)に置き換えることができ、さらに関数を組み込み(インライン)にすることもできます
- レジスタ割り当て -収集されたデータに基づいたレジスタ割り当ての最適化。
- 基本的なブロック最適化 -この最適化により、共通して呼び出されるコードブロックを共通のメモリページに配置できるため、使用されるページ数とメモリオーバーランが最小限に抑えられます。
- サイズ/速度の最適化 -プログラムが時間の大部分を費やす機能は、実行速度について最適化できます。
- 関数レイアウト -呼び出しグラフに基づいて、同じ実行チェーンに属する関数は同じセクションに配置されます。
- 条件分岐の最適化 -分岐および切り替えステートメントの最適化。 テストの実行に基づいて、PGOはswitchステートメントのどの条件が他の条件よりも頻繁に満たされるかを判断するのに役立ちます。 これらの値は、switch式から削除できます。 if / elseでも同じことが言えます。コンパイラは、より頻繁に呼び出されるブランチに基づいてブランチを配置できます。
- デッドコード分離 -テスト実行中に呼び出されなかったコードを特別なセクションに移動して、頻繁に使用されるメモリページに入らないようにすることができます。
- EHコード分離 -例外が特定の条件下でトリガーされたと判断できる場合、例外的なケースで実行される例外処理コードを別のセクションに転送できます。
- メモリー組み込み関数-(正しく翻訳するのが難しいと思う、オリジナルを引用する)組み込み関数の拡張は、組み込み関数が頻繁に呼び出されるかどうかを判断できれば、より適切に決定できます。 組み込み関数は、移動またはコピーのブロックサイズに基づいて最適化することもできます。
GCCコンパイラーを使用するときにPGOを実行する最も簡単な方法について説明します。 GCCでのPGOのサポートは、2つのフラグ-fprofile-generateおよび-fprofile-useによって提供されます。 一般的なコンパイルスキームは次のようになります。
- すべての最適化フラグと-fprofile-generateフラグを使用してプログラムをコンパイルします。 このフラグは、コンパイラーとリンカーの両方に設定する必要があります。 たとえば、次のように:
g ++ -O3 -march =ネイティブ-mtune =ネイティブ-fprofile-generate -Wall -c -fmessage-length = 0 -MMD -MP -MF "src / pgo-1.d" -MT "src / pgo-1.d "-O" src / pgo-1.o "" ../src/pgo-1.cpp "
g ++ -fprofile-generate -o "pgo-1" ./src/pgo-1.o
- コンパイルが成功した後、最も典型的なユースケースでプログラムのテスト実行を実行する必要があります。 すべてが正しく実行されると、テスト実行の結果として、 gcda拡張子を持つ統計ファイルが表示されます。
- すべての最適化フラグと-fprofile-useフラグを使用してプログラムをコンパイルします。 このフラグは、コンパイラーとリンカーの両方に設定する必要があります。 たとえば、次のように:
g ++ -O3 -march =ネイティブ-mtune =ネイティブ-fprofile-use -Wall -c -fmessage-length = 0 -MMD -MP -MF "src / pgo-1.d" -MT "src / pgo-1.d "-O" src / pgo-1.o "" ../src/pgo-1.cpp "
g ++ -fprofile-use -o "pgo-1" ./src/pgo-1.o
この場合、gccは手順2で作成した統計ファイルを使用するか、ファイルが見つからなかったことを報告します。
これは簡単なスキームです。 ただし、最適化を考えずに使用する価値がない場合。 常に何かを最適化する前に、特定の最適化フラグの有用性を効果的に評価できるテスト環境を最初に作成する必要があります。
簡単な例でPGOの有効性を検討してください。 学習したプログラムのコード:
#include <iostream>
#include <アルゴリズム>
#include <stdlib.h>
const size_t MB = 1024 * 1024 ;
size_t MOD = 0 ;
unsigned char uniqueNumber ( ) {
static unsigned char number = 0 ;
++番号% MODを返し ます 。
}
int main ( int argc、 char ** argv ) {
if ( argc < 3 ) {
1を 返し ます。
}
size_t BLOCK_SIZE = atoi ( argv [ 1 ] ) * MB ;
MOD = atoi ( argv [ 2 ] ) ;
unsigned char * garbage = ( unsigned char * ) malloc ( BLOCK_SIZE ) ;
std :: generate_n (ガベージ、BLOCK_SIZE、uniqueNumber ) ;
std :: sort ( garbage、garbage + BLOCK_SIZE ) ;
無料 (ごみ) ;
0を 返し ます 。
}
プログラムは、渡された最初のパラメーターに応じて、数メガバイトの符号なしchar配列を作成します。 次に、渡された2番目のパラメーターに応じて、繰り返し文字のシーケンスでそれを埋めます。 その後、結果の配列をソートします。 例:
./prog 32 3-スキームに従って埋められたサイズ32MBの配列を作成します:{1、2、1、2、...}。 その後、ソートします。
./prog 16 7-16メガバイトのサイズの配列を作成します。{1、2、3、4、5、6、1、2、3、4、5、6、...}のスキームに従って埋められます。 その後、ソートします。
したがって、さまざまなパラメーターを設定することにより、この配列を並べ替えるときに条件付き遷移をトリガーする頻度と、処理されるデータのサイズに影響を与えることができます。 これにより、前述の条件付きブランチ最適化をテストできます。 トリッキーなスクリプトを作成した後、さまざまなパラメーターを使用して1792のテストを実行し、それらを以下のグラフにまとめました。 配列サイズは変化しました:{2、4、8、16、32、128、256}、および除数{1..256}。
生産性向上率(累積)
このグラフでは、関心が互いに重なっています。 y軸に指定された絶対値ではなく、特定の色で塗りつぶされた領域を見る必要があります。 面積が大きいほど、生産性の向上は大きくなります。 グラフは、配列サイズのさまざまな値に対するパフォーマンスの向上を明確に示しています。
生産性向上率(単純)
y軸は、最適化されていないプログラムに対するパフォーマンス向上の割合を示しています。 x軸は使用されている仕切りを示しています。
絶対性能
以下のy軸のグラフは、プログラムの実行時間を秒単位で示し、x軸で使用される除数を示しています。 伝説では、-PGOは最適化なし、+最適化ありPGOを意味します。
結論
この特定のプログラムでは、PGOベースの最適化が非常に有用であり、5〜25%の安定したパフォーマンスの向上を提供します。
ソースコード、テストスクリプト、グラフ作成スクリプトを含むファイルをダウンロードできます 。
アーカイブの構造は次のとおりです。
pgo-1 / fprofile-generate--fprofile-generateフラグを使用してビルドスクリプトを作成します
pgo-1 / fprofile-use--fprofile-useフラグを使用してビルドスクリプトを作成する
pgo-1 /リリース-通常バージョンをビルドするためのスクリプトを作成
pgo-1 / src-ソースコード
pgo-1 / graph.pl-チャート生成スクリプト(super_logファイルを読み取ります)
pgo-1 / run.sh-テストサイクルを開始するスクリプト
graph.plは、run.shコマンドの出力に対応する形式のログファイルで機能します。
たとえば、次のように実行できます。
./run.sh> log 2>&1
テールログ| grep -A2 PGO
UPD。 テストプロジェクトは、フラグ-O3 -march = native -mtune = native with PGO、および-O3 -march = native -mtune = native with PGOを使用してビルドされました。 すべてのグラフは、-O3 -march = native -mtune = nativeと比較して成長を示しています。
ミラー用品