53

まず、コンパイラにすべての関数の実装をインライン化させる方法を探していません。

inline誤った回答のレベルを減らすには、キーワードが実際に何を意味するのかを理解してください。これが良い説明です。インラインvs静的vs外部です。

だから私の質問、なぜすべての関数定義をマークしないのinlineですか?つまり、理想的には、唯一のコンパイル単位はですmain.cpp。または、ヘッダーファイルで定義できない関数(pimplイディオムなど)については、さらにいくつかの可能性があります。

この奇妙な要求の背後にある理論は、オプティマイザーに最大の情報を提供するというものです。もちろん、関数の実装をインライン化することもできますが、モジュールが1つしかないため、「クロスモジュール」最適化を行うこともできます。他に利点はありますか?

誰かが実際のアプリケーションでこれを試しましたか?パフォーマンスは向上しましたか?下降?!?

すべての関数定義をマークすることの欠点は何inlineですか?

  • コンパイルは遅くなる可能性があり、はるかに多くのメモリを消費します。
  • 反復ビルドが壊れているため、変更するたびにアプリケーション全体を再ビルドする必要があります。
  • リンク時間は天文学的なものかもしれません

これらの欠点はすべて、開発者にのみ影響します。実行時の欠点は何ですか?

4

11 に答える 11

26

あなたは本当に#includeすべてを意味しましたか?これにより、モジュールは1つだけになり、オプティマイザーはプログラム全体を一度に確認できます。

実際、MicrosoftのVisual C ++は、/GL(プログラム全体の最適化)スイッチを使用すると、まさにこれを実行します。リンカーが実行され、すべてのコードにアクセスできるようになるまで、実際には何もコンパイルされません。他のコンパイラにも同様のオプションがあります。

于 2010-10-22T18:35:23.443 に答える
16

sqliteはこのアイデアを使用しています。開発中は、従来のソース構造を使用します。しかし、実際に使用する場合は、1つの巨大なcファイル(112k行)があります。彼らは最大の最適化のためにこれを行います。約5〜10%のパフォーマンス向上を主張する

http://www.sqlite.org/amalgamation.html

于 2010-10-22T19:11:40.597 に答える
10

私たち(および他のいくつかのゲーム会社)は、他のすべてを編集する1つのuber-.CPPを作成することでそれを試しまし#includeた。これは既知の手法です。私たちの場合、それはランタイムにあまり影響を与えていないように見えましたが、あなたが言及したコンパイル時の不利な点は完全に壊滅的であることが判明しました。変更を加えるたびに30分コンパイルすると、効果的に反復することができなくなります。(これは、アプリが12以上の異なるライブラリに分割されている場合です。)

デバッグ中に複数の.objを使用し、release-optビルドでのみuber-CPPを使用するように別の構成を作成しようとしましたが、コンパイラが単にメモリを使い果たしてしまうという問題が発生しました。十分に大きなアプリの場合、ツールは数百万行のcppファイルをコンパイルするだけでは不十分です。

LTCGも試してみましたが、リンクフェーズ中にクラッシュしなかったまれなケースでは、小さいながらも優れたランタイムブーストが提供されました。

于 2010-10-22T22:43:46.807 に答える
9

面白い質問です!リストされているすべての欠点が開発者に固有であることは確かに正しいです。ただし、不利な立場にある開発者が高品質の製品を生産する可能性ははるかに低いことをお勧めします。実行時の不利な点はないかもしれませんが、各コンパイルが完了するまでに数時間(または数日)かかる場合、開発者が小さな変更を加えることにどれほど消極的であるか想像してみてください。

私はこれを「時期尚早の最適化」の角度から見ます。複数のファイルのモジュラーコードはプログラマーの生活を楽にするので、この方法で物事を行うことには明らかな利点があります。特定のアプリケーションの実行が遅すぎることが判明し、すべてをインライン化することで測定された改善が見られる場合にのみ、開発者に不便をかけることを検討します。それでも、開発の大部分が(測定できるように)行われた後であり、おそらく本番ビルドに対してのみ行われるでしょう。

于 2010-10-22T18:36:47.113 に答える
7

これは半関連性がありますが、Visual C ++には、モジュール間のインライン化を含む、モジュール間の最適化を実行する機能があることに注意してください。詳細については、 http://msdn.microsoft.com/en-us/library/0zza0de8%28VS.80%29.aspxを参照してください。

元の質問に答えを追加するために、オプティマイザーが十分にスマートであると仮定すると、実行時にマイナス面はないと思います(したがって、Visual Studioの最適化オプションとして追加されたのはなぜですか)。あなたが言及するすべての問題を引き起こすことなく、それを自動的に行うのに十分賢いコンパイラを使用するだけです。:)

于 2010-10-22T18:33:01.123 に答える
4

メリットはほとんどありません。 最新のプラットフォームに適したコンパイラでは、inline影響を与える関数はごくわずかです。これはコンパイラーへの単なるヒントであり、最近のコンパイラーはこの決定を自分で行うのがかなり得意であり、関数呼び出しのオーバーヘッドはかなり小さくなっています(多くの場合、インライン化の主な利点は呼び出しのオーバーヘッドを減らすことではなく、開くことですさらなる最適化)。

コンパイル時間 ただし、インラインでもセマンティクスが変更されるため、#includeすべてを1つの巨大なコンパイル単位にする必要があります。これにより、通常、コンパイル時間が大幅に増加します。これは、大規模なプロジェクトでは致命的です。


現在のデスクトッププラットフォームとその高性能コンパイラから離れると、コードサイズは大きく変わります。この場合、あまり賢くないコンパイラによって生成されるコードサイズの増加が問題になります。これにより、コードが大幅に遅くなります。組み込みプラットフォームでは、通常、コードサイズが最初の制限です。

それでも、一部のプロジェクトは「すべてをインライン化」することで利益を得ることができます。少なくともコンパイラがやみくもに従わない場合は、リンク時間の最適化と同じ効果が得られますinline

于 2010-10-23T23:42:40.370 に答える
3

これは、プログラム全体の最適化とリンク時コード生成(LTCG)の背後にある哲学です。最適化の機会は、グローバルな知識があれば最高です。

実用的な観点からは、これは一種の苦痛です。これは、変更を加えるたびに、ソースツリー全体を再コンパイルする必要があるためです。一般的に言えば、任意の変更を行う必要があるよりも、最適化されたビルドが必要になる頻度は少なくなります。

私はMetrowerksの時代にこれを試しましたが(「Unity」スタイルのビルドでセットアップするのは非常に簡単です)、コンパイルは完了しませんでした。私はそれが彼らが予期していなかった方法でツールチェーンに負担をかける可能性が高いワークフロー設定であることを指摘するためだけに言及します。

于 2010-10-22T18:40:13.040 に答える
3

すでに行われている場合もあります。これは、ユニティビルドの考え方と非常によく似ており、長所と短所は、説明した内容とは異なります。

  • コンパイラが最適化する可能性が高くなります
  • リンク時間は基本的になくなります(すべてが単一の翻訳単位にある場合、実際にはリンクするものはありません)
  • コンパイル時間は、まあ、いずれにせよ、行きます。あなたが言ったように、インクリメンタルビルドは不可能になります。一方、完全なビルドは、そうでない場合よりも高速になります(コードのすべての行が1回だけコンパイルされるため。通常のビルドでは、ヘッダー内のコードは、ヘッダーが含まれるすべての変換ユニットでコンパイルされることになります。 )。

ただし、ヘッダーのみのコードがすでにたくさんある場合(たとえば、Boostをたくさん使用する場合)、ビルド時間と実行可能パフォーマンスの両方の観点から、非常に価値のある最適化になる可能性があります。

いつものように、パフォーマンスが関係する場合、それは異なります。それは悪い考えではありませんが、普遍的に適用できるわけでもありません。

バルドタイムに関しては、基本的に2つの方法で最適化できます。

  • 翻訳ユニットの数を最小限に抑える(ヘッダーがより少ない場所に含まれるようにする)、または
  • ヘッダー内のコードの量を最小限に抑えます(複数の翻訳単位にヘッダーを含めるコストが削減されるように)

Cコードは通常、2番目のオプションを取りますが、ほとんど極端です。前方宣言とマクロ以外はほとんど何もヘッダーに保持されません。C ++は多くの場合、可能な限り最悪の合計ビルド時間を得る中央付近にあります(ただし、PCHおよび/またはインクリメンタルビルドは、再び時間を短縮する可能性があります)が、反対方向に進むと、変換ユニットの数を最小限に抑えることができますビルド時間の合計は本当に不思議です。

于 2010-10-24T00:02:17.423 に答える
2

ここでの前提は、コンパイラーが関数間で最適化できないことです。これは特定のコンパイラの制限であり、一般的な問題ではありません。これを特定の問題の一般的な解決策として使用するのは悪いことかもしれません。コンパイラは、他の場所でコンパイルされている(キャッシュが原因でパフォーマンスが低下している)同じメモリアドレスで再利用可能な関数(キャッシュを使用するようになる)でプログラムを肥大化させる可能性があります。

大きな関数は一般的に最適化にコストがかかります。ローカル変数のオーバーヘッドと関数内のコードの量の間にはバランスがあります。関数内の変数(渡される、ローカル、グローバルの両方)の数をプラットフォームの使い捨て変数の数の範囲内に保つと、ほとんどすべてがレジスターにとどまり、RAMやスタックに追い出される必要がなくなります。フレームは必要ありません(ターゲットによって異なります)ので、関数呼び出しのオーバーヘッドが大幅に削減されます。実際のアプリケーションでは常に実行するのは困難ですが、ローカル変数が多数ある少数の大きな関数の代わりに、コードはramとの間で変数を含むレジスタを削除およびロードするのにかなりの時間を費やします(目標)。

機能ごとだけでなく、プログラム全体で最適化できるllvmを試してください。リリース27はgccのオプティマイザーに追いついており、少なくとも1つか2つのテストでは、徹底的なパフォーマンステストを行いませんでした。そして28が出ているので私はそれがより良いと思います。いくつかのファイルでも、チューニングノブの組み合わせの数が多すぎて混乱することはありません。プログラム全体を1つのファイルにまとめるまで、まったく最適化しないのが最善だと思います。次に、最適化を実行して、オプティマイザーにプログラム全体を使用させます。基本的には、インライン化で実行しようとしていることですが、手荷物はありません。

于 2010-10-22T21:56:15.690 に答える
1

と仮定foo()し、bar()両方がいくつかを呼び出しますhelper()。すべてが1つのコンパイル単位にある場合、コンパイラーはhelper()、命令の合計サイズを減らすために、インライン化しないことを選択する場合があります。これによりfoo()、にインライン化されていない関数呼び出しが行われhelper()ます。

foo()コンパイラーは、実行時間がナノ秒改善されると、期待どおりに1日あたり100ドルが収益に追加されることを認識していません。foo()パフォーマンスの向上や低下が収益に影響を与えないことはわかりません。

プログラマーとしてのあなただけがこれらのことを知っています(もちろん注意深いプロファイリングと分析の後)。インライン化しないという決定はbar()、コンパイラーにあなたが知っていることを伝える方法です。

于 2016-01-05T02:52:32.640 に答える
0

インライン化の問題は、高性能関数をキャッシュに収めたいということです。関数呼び出しのオーバーヘッドがパフォーマンスに大きな打撃を与えると思うかもしれませんが、多くのアーキテクチャでは、キャッシュミスにより、カップルがプッシュして水から飛び出します。たとえば、メインの高性能パスからめったに呼び出される必要のない大きな(おそらく深い)関数がある場合、メインの高性能ループがL1icacheに収まらないところまで大きくなる可能性があります。これにより、コードが遅くなり、たまに関数を呼び出すよりもはるかに遅くなります。

于 2010-10-22T18:37:21.263 に答える