コードのシリアルパフォーマンスを改善するための優れた戦略は何ですか?


66

私は計算科学に携わっており、その結果、多くのコードの科学的スループットを向上させ、これらのコードの効率を理解しようとするのに、かなりの時間を費やしています。

私が取り組んでいるソフトウェアのパフォーマンス対可読性/再利用性/保守性のトレードオフを評価したと仮定しましょう、そして私はパフォーマンスのために行く時だと決めました。また、(フロップ/秒とメモリ帯域幅に関して)私の問題に対してより良いアルゴリズムがないことを知っていると仮定しましょう。私のコードベースは、C、C ++、Fortranなどの低レベル言語であると仮定することもできます。最後に、コードに並列性がないこと、または単一コアでのパフォーマンスのみに関心があると仮定します。

最初に試すべき最も重要なことは何ですか?どれだけのパフォーマンスが得られるかを知るにはどうすればよいですか?

回答:


66

まず、スキルマンダンが指摘したように、プロファイリングが不可欠です。LinuxでIntelのVTune Amplifierを個人的に使用しているのは、どこで何をするのに時間を費やしたかを非常にきめ細かな概要で説明してくれるからです。

アルゴリズムを変更しない場合(つまり、すべての最適化が廃止されるような大きな変更がない場合)、大きな違いを生む可能性のある一般的な実装の詳細を探すことをお勧めします。

  • メモリーの局所性:一緒に読み取ったり使用したりするデータも一緒に保存されますか?

  • メモリのアライメント:ダブルは実際に4バイトにアライメントされていますか?どのように梱包しましたstructsか?独創的であるためposix_memalignには、の代わりに使用しますmalloc

  • キャッシュの効率性:局所性は、キャッシュの効率性の問題のほとんどを処理しますが、読み取り/書き込みを頻繁に行ういくつかの小さなデータ構造がある場合、それらが整数倍またはキャッシュ行の一部(通常64バイト)であれば役立ちます。また、データがキャッシュラインのサイズに揃えられている場合にも役立ちます。これにより、データの読み込みに必要な読み取り回数を大幅に削減できます。

  • ベクトル化:いいえ、手動でコード化されたアセンブラーを使用しないでください。gccSSE / AltiVec / whatever命令に自動的に変換されるベクタータイプを提供します。

  • 命令レベルの並列処理:ベクトル化のろくでなしの息子。頻繁に繰り返される計算がうまくベクトル化されない場合は、入力値を累積して、一度に複数の値を計算してみてください。ループの展開のようなものです。ここで活用しているのは、CPUには通常、コアごとに複数の浮動小数点ユニットがあることです。

  • 算術精度:実行するすべての処理で本当に倍精度演算が必要ですか?たとえば、ニュートン反復で補正を計算している場合、通常、計算しているすべての数字は必要ありません。より詳細な議論については、このペーパーを参照してください。

これらのトリックのいくつかは、daxpy_cvec このスレッドで使用されます。とはいえ、Fortran(私の本では低レベル言語ではない)を使用している場合、これらの「トリック」のほとんどをほとんど制御することはできません。

すべての実稼働実行に使用するクラスターなど、専用のハードウェアで実行している場合は、使用されているCPUの詳細を参照することもできます。そのアーキテクチャのためにアセンブラで直接何かを書くべきではありませんが、見落としているかもしれない他の最適化を見つけるように促すかもしれません。機能について知ることは、それを活用できるコードを書くために必要な最初のステップです。

更新

私がこれを書いてからしばらく経ちましたが、それがとても人気のある答えになったことに気づいていませんでした。このため、重要な点を1つ追加します。

  • 地元のコンピューターサイエンティストに相談してください。アルゴリズムおよび/または計算をより効率的/エレガント/並列化することに専念する規律があれば、それはクールではないでしょうか。まあ、良いニュース、その規律が存在する:コンピューターサイエンス。おそらく、あなたの教育機関には専任の部署があります。これらの人と話してください。

多くの非コンピューター科学者に、これは、何ももたらさなかった上記の規律とのイライラする議論の記憶、または他の人々の逸話の記憶を取り戻すことでしょう。がっかりしないでください。学際的なコラボレーションはトリッキーなものであり、少し手間がかかりますが、その見返りは膨大です。

私の経験では、コンピューターサイエンティスト(CS)としての秘trickは、期待とコミュニケーションの両方を正しく得ることにあります。

期待-賢明なことに、CSはあなたの問題が面白いと思う場合にのみあなたを助けます。これは、あなたが書いたコードの一部を最適化/ベクトル化/並列化しようとすることをほとんど排除しますが、彼らは理解していない問題のために実際にはコメントしません。CSは通常、根底にある問題、たとえばそれを解決するために使用されるアルゴリズムにより関心があります。彼らにあなたの解決策を与えないで、彼らにあなたの問題を与えてください。

また、CSが「この問題はすでに解決されています」と言って、論文への参照を提供する準備をしてください。アドバイスの言葉:その論文を読んで、それが本当にあなたの問題に当てはまるなら、それが提案するどんなアルゴリズムでも実装してください。これは独善的なCSではなく、あなたを助けたCSです。気を悪くしないでください:問題が計算上おもしろくない場合、つまり、すでに解決されて解決策が最適であると示されている場合、彼らはそれを処理しません。

コミュニケーションに関しては、ほとんどのCSはあなたの分野の専門家ではないことを忘れないでください。方法理由ではなく、あなたがしていることに関して問題を説明してください 私たちは通常、なぜ、そしてどのように私たちが最善を尽くしているのかを本当に気にしません。

たとえば、私は現在、多くの計算宇宙学者と協力して、SPHMultipolesに基づいたシミュレーションコードのより良いバージョンを作成しています。暗黒物質と銀河のハローに関する話をやめ、計算の核心にドリルダウンするために約3回の会議が必要でした。それらの量を超えてから、上記のすべての隣人を再度実行し、他の計算でその量を適用します。次に、パーティクルまたは少なくともそれらのいくつかを移動し、すべてをもう一度実行します。前者は信じられないほど面白いかもしれませんが(!)、後者はアルゴリズムについて考え始めるために理解する必要があるものです。

しかし、私は主な点とは異なります。計算を高速化することに本当に興味があり、自分がコンピューター科学者ではない場合は、話をしてください。


4
プロファイリングツールが進むにつれて、valgrindを忘れることはありません。
GertVdE

1
最適化されているプログラムがF1レースカーのようで、すでに最適に近いPedroに同意します。私が実際に目にするプログラムは、科学的でなく、多くの場合、キャデラッククーペデビルに似ています。実際のパフォーマンスを得るには、大量の脂肪を削減することができます。その後、サイクルシェービングはその歩みを打ち始めます。
マイクダンラベイ

1
@MikeDunlavey:完全に同意します。アルゴリズム関連の問題に対処するために、回答に更新を追加しました。
ペドロ

1
@MikeDunlavey、私 CSフォークです:)
ペドロ

2
私はこれをUマサチューセッツ州ローウェルでの講演で実演しました。ライブデモで、730xの高速化のすべての段階を示しました。半ダースのうち、1人の教授がポイントを得たと思います。
マイクダンラベイ

38

科学ソフトウェアは、チューニングが必要なものを知る方法に関しては、他のソフトウェアとそれほど違いはありません。

私が使用する方法は、ランダムな一時停止です。私が見つけた高速化の一部を以下に示します。

時間の大部分がlogやなどの関数に費やされexpている場合、それらが呼び出されているポイントの関数として、それらの関数の引数が何であるかを見ることができます。多くの場合、同じ引数で繰り返し呼び出されます。もしそうなら、メモすることは大きなスピードアップ要因を生み出します。

BLASまたはLAPACK関数を使用している場合、配列のコピー、行列の乗算、コレスキ変換などのルーチンにかなりの時間が費やされることがあります。

  • 配列をコピーするルーチンは速度のためではなく、利便性のためにあります。それほど便利ではないが、より高速な方法があります。

  • 行列を乗算または反転する、またはコレスキ変換を行うルーチンには、上三角または下三角の「U」または「L」などのオプションを指定する文字引数が含まれる傾向があります。繰り返しになりますが、これらは便宜上のものです。私が見つけたのは、マトリックスがそれほど大きくないため、ルーチンは、オプションを解読するためだけに文字比較するサブルーチンを呼び出す時間の半分以上を費やしていたということです。最も高価な数学ルーチンの特別な目的のバージョンを書くと、大幅に高速化されました。

後者を拡張できる場合:行列乗算ルーチンDGEMMはLSAMEを呼び出して、文字引数をデコードします。包括的パーセント時間(見る価値のある唯一の統計)を見ると、「良い」と見なされるプロファイラーは、80%などの合計時間の数パーセントを使用してDGEMMを示し、50%などの合計時間の数パーセントを使用してLSAMEを表示できます 前者を見ると、「それは十分に最適化されている必要があるので、それについて私ができることはあまりない」と言いたくなるでしょう。後者を見ると、「ハァッ、それは何なの?それはほんの小さなルーチンです。このプロファイラーは間違っているに違いありません!」と言いたくなるでしょう。

それは間違っていません、あなたが知る必要があることをあなたに言っていないだけです。ランダムに一時停止すると、DGEMMがスタックサンプルの80%にあり、LSAMEが50%にあることがわかります。(それを検出するのに多くのサンプルは必要ありません。通常は10で十分です。)さらに、これらのサンプルの多くで、DGEMMは2、3の異なるコード行からLSAMEを呼び出しています。

これで、なぜ両方のルーチンが包括的な時間を費やしているのかがわかりました。また、この時間を費やすためにコードのどこから呼び出されているかも知っています。だからこそ、私はランダムな一時停止を使用し、プロファイラーがどれほどよく作られていても、プロファイラーを慎重に見ます。彼らは、何が起こっているかを伝えることよりも、測定値を取得することに関心があります。

数学ライブラリルーチンがn番目の程度に最適化されていると仮定するのは簡単ですが、実際、それらは幅広い目的に使用できるように最適化されています。推測しやすいものではなく、実際に何起こっているのを確認する必要があります。

追加:最後の2つの質問に答えるために:

最初に試すべき最も重要なことは何ですか?

10〜20個のスタックサンプルを取り、それらを要約するだけでなく、それぞれがあなたに言っていることを理解してください。これを最初に、最後に、そしてその間に行います。(「試用」はありません、若いスカイウォーカー。)

どれだけのパフォーマンスが得られるかを知るにはどうすればよいですか?

xβ(s+1,(ns)+1)sn1/(1x)n=10s=5x
ここに画像の説明を入力してください
xx

以前に指摘したように、これ以上できなくなるまで手順全体を繰り返すことができ、複合化された高速化率は非常に大きくなる可能性があります。

(s+1)/(n+2)=3/22=13.6%。)次のグラフの下部の曲線はその分布です。

ここに画像の説明を入力してください

40個ものサンプル(一度に持っているものよりも多い)を取り、そのうちの2つだけで問題が発生したかどうかを検討してください。より高い曲線に示すように、その問題の推定コスト(モード)は5%です。

「誤検知」とは何ですか?問題を修正した場合、予想よりも非常に小さなゲインを実現するため、修正したことを後悔します。曲線は(問題が「小さい」場合)、ゲインそれを示すサンプルの割合よりも小さい可能性がありますが、平均して大きくなることを示しています。

はるかに深刻なリスクがあります-「偽陰性」。それは問題があるときですが、見つかりません。(これに貢献しているのは「確認バイアス」であり、証拠の欠如は、欠如の証拠として扱われる傾向があります。)

あなたはプロファイラ(良いもの)を取得することで、問題が実際にどの程度あまり正確な情報を犠牲にし、はるかに正確な測定(偽陽性のため、少ないチャンスを)取得されている(それを見つけると取得のため、少ないチャンス任意のゲイン)。これにより、達成可能な全体的な高速化が制限されます。

プロファイラーのユーザーには、実際に実際に得られるスピードアップ要因を報告することをお勧めします。


再作成する別のポイントがあります。偽陽性に関するペドロの質問。

彼は、高度に最適化されたコードで小さな問題に取りかかるのは難しいかもしれないと述べました。(私にとって、小さな問題は、合計時間の5%以下を占める問題です。)

5%を除いて完全に最適なプログラムを構築することは完全に可能であるため、この点はこの回答のように経験的にしか対処できません。経験的経験から一般化するには、次のようになります。

書かれているように、プログラムには通常、最適化の機会がいくつか含まれています。(これらを「問題」と呼ぶこともできますが、多くの場合、完全に優れたコードであり、単にかなりの改善が可能です。)この図は、ある程度の時間(たとえば100秒) ...見つかって修正すると、元の100の30%、21%などを節約できます。

ここに画像の説明を入力してください

問題Fのコストは元の時間の5%であるため、「小さな」問題であり、40以上のサンプルがないと見つけることは困難です。

ただし、最初の10個のサンプルで問題Aを簡単に見つけることができます。**これが修正されると、プログラムは70秒しかかかりません。100/ 70 = 1.43xの高速化です。これはプログラムを高速化するだけでなく、残りの問題が占める割合をその比率で拡大します。たとえば、問題Bは元々21秒で合計21%でしたが、Aを削除すると、Bは70秒のうち21秒、つまり30%になります。したがって、プロセス全体が繰り返されるときを見つけやすくなります。

プロセスが5回繰り返されると、実行時間は16.8秒になり、そのうち問題Fは5%ではなく30%であるため、10個のサンプルで簡単に見つけることができます。

それがポイントです。経験的に、プログラムにはサイズの分布を持つ一連の問題が含まれており、問題を見つけて修正すると、残りの問題を簡単に見つけることができます。これを達成するために、問題をスキップすることはできません。問題がある場合は、時間がかかり、全体の高速化が制限され、残りの問題が拡大されないためです。 そのため、隠れている問題を見つけることが非常に重要です。

問題A〜Fが見つかって修正された場合、スピードアップは100 / 11.8 = 8.5xです。それらのいずれかが欠落している場合、たとえばDの場合、スピードアップは100 /(11.8 + 10.3)= 4.5xのみです。 それが偽陰性の代価です。

そのため、プロファイラーが「ここに重大な問題はないようだ」(つまり、優れたコーダー、これは実際に最適なコードです)と言うとき、それは正しいかもしれませんし、そうでないかもしれません。(偽陰性。)高速化のために、別のプロファイリング方法を試して問題があることを発見しない限り、修正すべき問題があるかどうかはわかりません。私の経験では、プロファイリング方法は、多数のサンプルを要約する必要はなく、少数のサンプルが必要です。各サンプルは、最適化の機会を認識するのに十分に理解されています。

2/0.3=6.671 - pbinom(1, numberOfSamples, sizeOfProblem)1 - pbinom(1, 20, 0.3) = 0.9923627

xβ(s+1,(ns)+1)nsy1/(1x)xyy1BetaPrimeディストリビューション。私はこの振る舞いに到達する200万のサンプルでそれをシミュレートしました:

         distribution of speedup
               ratio y

 s, n    5%-ile  95%-ile  mean
 2, 2    1.58    59.30   32.36
 2, 3    1.33    10.25    4.00
 2, 4    1.23     5.28    2.50
 2, 5    1.18     3.69    2.00
 2,10    1.09     1.89    1.37
 2,20    1.04     1.37    1.17
 2,40    1.02     1.17    1.08

 3, 3    1.90    78.34   42.94
 3, 4    1.52    13.10    5.00
 3, 5    1.37     6.53    3.00
 3,10    1.16     2.29    1.57
 3,20    1.07     1.49    1.24
 3,40    1.04     1.22    1.11

 4, 4    2.22    98.02   52.36
 4, 5    1.72    15.95    6.00
 4,10    1.25     2.86    1.83
 4,20    1.11     1.62    1.31
 4,40    1.05     1.26    1.14

 5, 5    2.54   117.27   64.29
 5,10    1.37     3.69    2.20
 5,20    1.15     1.78    1.40
 5,40    1.07     1.31    1.17

(n+1)/(ns)s=ny

これは、5、4、3、および2つのサンプルのうち2つのヒットに対する、高速化係数の分布とその平均のプロットです。たとえば、3つのサンプルが取得され、そのうち2つが問題でヒットし、その問題を除去できる場合、平均的な高速化係数は4倍になります。2つのヒットが2つのサンプルのみで見られる場合、平均的なスピードアップは未定義です-概念的には、無限ループを持つプログラムがゼロ以外の確率で存在するためです!

ここに画像の説明を入力してください


1
うーん…プロファイラのコールグラフや、VTuneが提供する「ボトムアップ」タイプの要約を見て、この情報を正確に取得しませんか?
ペドロ

2
@Pedro:場合のみ。スタックサンプル(および関連する変数)では、時間の増分が費やされている理由全体がエンコードされます。なぜ使われているのかわからない限り、それを取り除くことはできません。一部の問題は限られた情報で見つかりますが、すべてではありません。それらの一部のみを取得し、すべてを取得しなかった場合、取得できない問題により、さらなる高速化が妨げられます。チェックここここ
マイクダンラベイ

おそらく、あなたはあなたの方法を悪いプロファイリングと比較しています...また、総実行時間への寄与とは無関係に、各ルーチンのプロファイルを調べて、同じ効果で改善を探すこともできます。あなたのアプローチで私が心配しているのは、コード内の「ホットスポット」がますます小さくなるにつれて、追跡されるフォールスポジティブの数が増えることです。
ペドロ

@Pedro:複数のサンプルで修正できるものが見つかるまで、サンプルを取り続けます。ベータdistrは、気にすればどれだけ節約できるかを示しますが、表示よりも高速化が遅くなることを恐れている場合は、より多くの可能性があるチャンスを捨てていることに注意してください(そして、それは右スキューです) )。プロファイラーを要約する場合のより大きな危険は、偽陰性です。問題が発生する可能性がありますが、プロファイラーがどこにあるのかについて非常に不明確な場合、直観がそれを嗅ぎ分けることを望んでいます。
マイクダンラベイ

@Pedro:私が知っている唯一の弱点は、スナップショットの時間を見ると、リクエスターが隠れている非同期イベントや非同期プロトコルを単に処理している場合など、その時間が費やされている理由を把握できないことです。より「通常の」コードについては、「良い」プロファイラーを見せてください。問題を抱えているか、単に見つけることができない問題を見せます(あなたはあなたの誤りのある賢さに頼ります)。一般に、このような問題を構築する方法は、提供されている目的をローカルで解読できないようにすることです。そして、そのような問題はソフトウェアにたくさんあります。
マイクダンラベイ

23

コンパイラの詳細な知識が必要なだけでなく、ターゲットアーキテクチャオペレーティングシステムの詳細な知識も必要です

パフォーマンスに影響するものは何ですか?

パフォーマンスの最後の1オンスごとに絞り込みたい場合、ターゲットアーキテクチャを変更するたびに、コードを調整して再最適化する必要があります。同じCPUの次のリビジョンでは、1つのCPUで最適化されたものが次善の可能性があります。

これの優れた例は、CPUキャッシュです。高速で小さなキャッシュを備えたCPUから、わずかに低速でわずかに大きなキャッシュを備えたCPUにプログラムを移動すると、プロファイリングが大幅に変更される可能性があります。

ターゲットアーキテクチャが変更されなくても、オペレーティングシステムの低レベルの変更もパフォーマンスに影響する可能性があります。SpectreおよびMeltdown緩和パッチは、一部のワークロードで大きな影響を与えたため、最適化の再評価を強制する可能性があります。

どうすればコードを最適化できますか?

高度に最適化されたコードを開発するときは、モジュールを維持し、同じアルゴリズムの異なるバージョンを簡単に交換できるようにする必要があります。場合によっては、利用可能なリソースとサイズ/複雑さに応じて、実行時に使用する特定のバージョンを選択することもできます処理されるデータ。

モジュール方式はまた、彼らはすべて同じように動作していることを確認することができ、あなたの最適化および最適化されていないすべてのバージョンで同じテストスイートを使用することができることを意味し、中に素早く各1をプロファイリングなどの-のためのような比較。「認識を超えて最適化された」計算集約的なコードを他の人に文書化して教える方法に対する回答で、もう少し詳しく説明します。

参考文献

また、私は非常にウルリック・ドレパーの優れた論文を見てとることをお勧めしますすべてのプログラマは、メモリについて知っておくべきそのタイトルへのオマージュであるが、デビッド・ゴールドバーグさんも同様に素晴らしいすべてのコンピュータ科学者は、浮動小数点演算について知っておくべきこと

すべてのを忘れないでください最適化は、将来になる可能性がある抗最適化をするので、最小限に抑えることが、可能なコードのにおいを考慮すべきです。私の答えは、コーディング時にマイクロ最適化が重要なのですか? これは、個人的な経験から具体的な例を示しています。


8

あなたは質問をあまりにも狭く表現していると思います。私の考えでは、データ構造とアルゴリズムを変更するだけで、数100行を超えるコードのパフォーマンスが大幅に向上するという仮定の下で生活するのが便利な態度です。この主張。


3
原則的に同意しましたが、アルゴリズム/データ構造のパフォーマンスと基礎となるハードウェアの詳細との相互作用を過小評価してはなりません。たとえば、バランスの取れたバイナリツリーは、データの検索/保存に最適ですが、グローバルメモリのレイテンシによっては、ハッシュテーブルの方が優れている場合があります。
ペドロ

1
同意した。アルゴリズムとデータ構造は、O(10)からO(100)の改善を提供できます。ただし、いくつかの計算に制限のある問題(分子動力学計算、天体物理学、リアルタイム画像およびビデオ処理、金融など)では、高度に調整されたクリティカルループは、アプリケーション全体の実行時間を3倍から10倍速くすることができます。
フクルス

かなりのサイズの「本番」コードで、順序の悪いネストされたループを見てきました。それ以外はあなたが正しいと思う。
-dmckee

8

最初にすべきことは、コードのプロファイルを作成することです。最適化を開始する前に、プログラムのどの部分が遅くなっているのかを調べたいと思います。さもなければ、実行時間の多くを消費していなかったコードの部分を最適化してしまう可能性があります。

Linux

gprofは非常に優れていますが、各行ではなく各関数がどれだけの時間を費やしているかを示すだけです。

Apple OS X

あなたはサメを試してみたいかもしれません。Apple Developerサイトの[ダウンロード]> [開発ツール]> [CHUD 4.6.2](古いバージョンはこちら)から入手できます。CHUDには、BigTopフロントエンド、PMCインデックス検索ツール、Saturn関数レベルのプロファイラー、その他多くのコマンドなど、他のプロファイリングツールも含まれています。Sharkにはコマンドラインバージョンが付属します。


+1プロフィール?はい、ある意味では...推測するよりもはるかに優れていますが、ここでは特にgprofや他の多くのプロファイラーに当てはまる問題のリストを示します。
マイクダンラベイ

SharkはOS Xの古いコマンドですか?詳細はこちら。Mountain Lionでは、Instrumentsを使用する必要がありますか?
hhh

@hhh:Mac用のGUIプロファイラーでしたが、もうメンテナンスされていないようです。この答えを書いてから、アップルマシンでプログラミングしたことがないので、あまり助けられません。
ダン

1
Apple DeveloperサイトのDownloads> Developer Tools> CHUD 4.6.2から入手できます。古いバージョンここでは、「メーカーに問い合わせてください」、バグについての考え方:残念ながら、このインストールは成功していない-それは物事をプロファイリングのすべての種類が含まれています。シャークは、ライオンの後、明らかにXcodeから削除され、後でMacUpdateの無料ツールになった後、Apple Devサイトに戻されました。
hhh

@hhh:あなたは私よりもこれに答える資格があるようです。回答を編集して更新するか、独自に作成してください。
ダン

7

どれだけのパフォーマンスを得ることができるかについては、コードのプロファイリングの結果を取得し、「p」の時間の一部を占める部分を特定するとしましょう。その部分のパフォーマンスを「s」の係数だけ改善する場合、全体的なスピードアップは1 /((1-p)+ p / s)になります。したがって、1 /(1-p)の係数で速度を最大限に高めることができます。うまくいけば、あなたは高いpの領域を持っています!これは、シリアル最適化のアムダールの法則に相当します。


5

コードの最適化は慎重に行う必要があります。また、既にコードをデバッグ済みであると仮定しましょう。特定の優先順位に従うと、多くの時間を節約できます。つまり:

  1. 可能な限り、高度に最適化された(または専門的に最適化された)ライブラリを使用します。いくつかの例には、FFTW、OpenBlas、Intel MKL、NAGライブラリなどが含まれます。非常に才能のある人(GotoBLASの開発者など)でない限り、おそらく専門家に勝るものはありません。

  2. プロファイラーを使用して(次のリストのかなりの数がこのスレッドで既に名前が付けられています-Intel Tune、valgrind、gprof、gcovなど)、コードのどの部分が最も時間がかかっているかを調べます。めったに呼び出されないコードの部分を最適化する時間を無駄にすることはありません。

  3. プロファイラーの結果から、最も時間がかかったコードの部分を確認します。アルゴリズムの性質を判断します-CPUバインドですか、メモリバインドですか?それぞれ最適化手法の異なるセットが必要です。多くのキャッシュミスが発生している場合、メモリがボトルネックになっている可能性があります。CPUは、メモリが使用可能になるのを待つクロックサイクルを浪費しています。ループがシステムのL1 / L2 / L3キャッシュに収まるかどうかを検討してください。ループに「if」ステートメントがある場合、プロファイラーが分岐の予測ミスについて何か言っているかどうかを確認しますか?システムの分岐予測ミスのペナルティとは何ですか?ところで、インテルの最適化リファレンスマニュアル[1]から分岐予測データを取得できます。Intelのマニュアルに記載されているように、ブランチの予測ミスのペナルティはプロセッサ固有です。

  4. 最後に、プロファイラーによって特定された問題に対処します。ここでは、いくつかの手法についてすでに説明しました。最適化に関する優れた信頼性の高い包括的なリソースも多数用意されています。2つだけ挙げると、Intel Optimization Reference Manual [1]とAgner Fogによる5つの最適化マニュアル[2]です。コンパイラーが既に実行している場合、実行する必要のないことがいくつかあることに注意してください。たとえば、ループのアンロール、メモリーの位置合わせなどです。コンパイラーの資料を注意深く読んでください。

参照:

[1] Intel 64およびIA-32アーキテクチャ最適化リファレンスマニュアル:http : //www.intel.sg/content/dam/doc/manual/64-ia-32-architectures-optimization-manual.pdf

[2] Agner Fog、「ソフトウェア最適化リソース」:http : //www.agner.org/optimize/

  • 「C ++でのソフトウェアの最適化:Windows、Linux、およびMacプラットフォーム用の最適化ガイド」
  • 「アセンブリ言語でのサブルーチンの最適化:x86プラットフォームの最適化ガイド」
  • 「Intel、AMD、およびVIA CPUのマイクロアーキテクチャ:アセンブリプログラマおよびコンパイラメーカー向けの最適化ガイド」
  • 「命令テーブル:Intel、AMD、およびVIA CPUの命令レイテンシ、スループット、およびマイクロオペレーションの内訳のリスト」
  • 「異なるC ++コンパイラとオペレーティングシステムの呼び出し規約」

3

私はここの他の多くのコンピュータ科学者ではありません(間違っている可能性があります:))。しかし、最近では、標準ライブラリを使用している限り、シリアルパフォーマンスに時間をかけすぎても意味がありません。コードをよりスケーラブルにするために、追加の時間/努力を費やすほうが価値があるかもしれません。

いずれにせよ、パフォーマンスがどのように改善されたか(まだ構造化されていないFEの問題について)の2つの例(まだ読んでいない場合)があります。

シリアル:要約および関連するテキストの後半を参照してください。

パラレル:特に4.2節の初期化フェーズ。


3

これはおそらく、答えというよりもメタ答えです...

コンパイラーを熟知する必要があります。これを最も効率的に取得するには、マニュアルを読んでオプションを試してください。

@Pedroのディスペンスに関する優れたアドバイスの多くは、プログラムではなくコンパイルを調整することで実装できます。


最後の点には同意しません。コンパイラーが何ができるかを知ることは一つのことですが、コンパイラーが実際にそれで何かを行えるようにコードを書くことはまったく別の問題です。データを並べ替えるコンパイラフラグはありません。必要に応じて精度を低くしたり、分岐がほとんどないかまったくないように最も内側のループを書き直したりします。コンパイラを知ることは良いことですが、それはあなたがより良いコードを書くのを助けるだけであり、それ自体があなたのコードを良くすることはありません。
ペドロ

1

(Linuxで)プログラムをプロファイリングする簡単な方法はperfstatモードで使用することです。最も簡単な方法は、次のように実行することです

perf stat ./my_program args ...

そして、それはあなたに有用なパフォーマンス統計の束を与えます:

Performance counter stats for './simd_test1':

     3884.559489 task-clock                #    1.000 CPUs utilized
              18 context-switches          #    0.005 K/sec
               0 cpu-migrations            #    0.000 K/sec
             383 page-faults               #    0.099 K/sec
  10,911,904,779 cycles                    #    2.809 GHz
 <not supported> stalled-cycles-frontend
 <not supported> stalled-cycles-backend
  14,346,983,161 instructions              #    1.31  insns per cycle
   2,143,017,630 branches                  #  551.676 M/sec
          28,892 branch-misses             #    0.00% of all branches

     3.885986246 seconds time elapsed

場合によっては、Dキャッシュのロードとミスもリストされます。キャッシュミスが多い場合、プログラムはメモリを集中的に使用しており、キャッシュを適切に処理していません。最近では、CPUはメモリ帯域幅よりも高速になっています。通常、問題は常にメモリアクセスです。

perf record ./my_program; perf reportプロファイルを作成する簡単な方法を試すこともできます。詳細については、manページをご覧ください。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.