5

floatまたはdoubleタイプのいずれかに基づいて代数計算を実行するテンプレートクラスの大規模なライブラリを維持しています。多くのクラスには、アクセサメソッド(ゲッターとセッター)および少量のコードを実行するその他の関数があります。したがって、このような関数は、コンパイラーが定義を見つけるときにインラインとして修飾する必要があります。対照的に、他のメンバー関数には高度なコードが含まれているため、インライン化するよりも呼び出す方が適切です。

関数定義の大部分はヘッダーにあり、実際にはヘッダーに含まれる.inlファイルにあります。floatしかし、関数定義がとの明示的なインスタンス化によって.cppファイルにうまく存在するクラスもたくさんありますdouble。これは、ライブラリの場合に行うのがかなり良いことです(ここで理由を説明します)。そして最後に、関数定義が.inlファイル(アクセサーメソッド)と.cppファイル(コンストラクター、デストラクタ、および大量の計算)にまたがって分割されているクラスがかなりあり、それらすべてを維持するのは非常に困難です。

一部の関数がインライン化されないようにする信頼できる方法を知っている場合にのみ、すべてのクラス実装を.inlファイルに入れます。または、inlineキーワードがコンパイラーに一部の関数をインライン化するように強く提案できる場合は、.cppファイルに入れます。ではない。ライブラリ内のすべての関数定義を.cppファイルに配置することを強くお勧めしますが、アクセサメソッドはライブラリ全体で広く使用されているため、参照されるときは常にインライン化され、呼び出されないようにする必要があります。

したがって、これに関連して、私の質問は次のとおりです。

  1. ここで最近学んだように、でマークされているかどうかに関係なく、コンパイラによって自動的にインラインとして修飾されるinlineという事実を考慮して、テンプレート関数の定義をマークすることは意味がありますか?inline

  2. そして最も重要なのは、テンプレートクラスのすべてのメンバー関数の定義を1つのファイルにまとめたいので、.inlまたは.cpp(.cppの場合は明示的なインスタンス化を使用)のいずれかです。コンパイラー(MSVCおよびGCC)に、どの関数をインライン化する必要があり、どの関数をインライン化するべきでないかを示唆するためにテンプレート関数でそのようなことが可能かどうか、どうすればこれを達成できるか、または本当に方法がない場合( )、最も最適な妥協点は何でしょうか?

----------

編集1:inlineキーワードは、関数をインライン化するためのコンパイラーへの単なる提案であることを私は知っていました。

EDIT2:私は本当に知っています。私はコンパイラーに提案をするのが好きです。

EDIT3:私はまだ知っています。それは問題が何であるかではありません。

----------

いくつかの新しい情報を考慮して、2番目の質問と密接に関連する3番目の質問もあります。

3.コンパイラが最近非常に賢く、インライン化する関数と呼び出す関数をより適切に選択でき、リンク時のコード生成とリンク時の最適化が可能である場合、.cppを効果的に調べることができます -インライン化または呼び出される運命を決定するためにリンク時に関数定義を配置します。おそらく、すべての定義をそれぞれの.cppファイルに移動するだけでよい解決策になるでしょうか。

----------

それで、結論は何ですか?

まず第一に、私はダニエル・トレビエンとジョナサン・ウェイクリーの構造化された十分に根拠のある答えに感謝します。両方に賛成したが、1つだけを選択する必要があった。しかし、与えられた答えはどれも私に受け入れられる解決策を提示しなかったので、選ばれた答えはたまたま私が最終決定を下すのに他の人よりもわずかに助けになったものでした。その詳細は興味のある人のために次に説明されます。

ええと、私は常にコードのパフォーマンスを維持と開発の便利さよりも評価してきたので、最も受け入れられる妥協案は、すべてのアクセサーメソッドとそれぞれのその他の軽量メンバー関数を移動することだと思います。テンプレートクラスをそれぞれのヘッダーに含まれる.inlファイルに入れinline、コンパイラに適切なヒント(またはインライン強制用のキーワード)を提供するためにこれらの関数をキーワードでマークし、残りの関数をそれぞれに移動します.cppファイル。

すべてのメンバー関数定義を.cppファイルに配置すると、MSVCのDaniel Trebbien(開発の古い段階)およびGCCのJonathan Wakely(開発の古い段階)によって確認されたように、リンク時間の最適化に関するいくつかの問題を解き放ちながら、軽量関数のインライン化が妨げられます。開発の現在の段階で)。また、すべての関数定義をヘッダー(または.inlファイル)に配置することは、各クラスの実装を.inlファイルと.cppファイルにソートし、この決定のボーナス副作用を組み合わせることの要約的な利点を上回りません。プリミティブアクセサメソッドのコードのみがライブラリのクライアントに表示され、よりジューシーなものがバイナリに隠されています(ただし、これが主な理由ではないことを確認しますが、このプラスはソフトウェアライブラリに精通している人には明らかでした)。inline関数のインラインステータスを促進するため(この特定のケースでは、キーワードを両方の場所に配置するか、1つだけに配置するかはまだわかりません)。

4

4 に答える 4

8

つまり、テンプレートコードをヘッダーファイルに入れます。オプティマイザーがインライン化について適切な決定を下せない場合は、コンパイラー固有forceinlineまたはキーワードを使用してください。noinline


テンプレートメンバーの定義をヘッダーファイルに入れることができ、またそうすべきです。これにより、コンパイラーは、実際のテンプレートパラメーターが何であるかを検出したときに、使用時に定義にアクセスできるようになり、暗黙的なインスタンス化を実行できるようになります。

テンプレート関数はすでに単一定義要件から除外されているため、inlineキーワードはテンプレートにほとんど影響を与えません(単一定義規則では、すべての定義が同じである必要があります)。関数をインライン化する必要があることは、コンパイラーへのヒントです。また、関数をインライン化しないようにコンパイラーへのヒントとして省略できます。したがって、そのように使用します。ただし、オプティマイザは他の要素(関数サイズ)を確認し、インライン化について独自の選択を行います。

一部のコンパイラには、オプティマイザの選択を上書きする、__attribute__(always_inline)などの特別なキーワードがあります。__declspec(noinline)

ただし、ほとんどの場合、コンパイラは「関数呼び出しとしてより意味のある複雑なコード」をインライン化しないほど賢いです。あなたはそれについて心配する必要はありません、ただオプティマイザーにそのことをさせてください。

インライン化のトレードオフはプラットフォーム固有であるため、ポータブルインライン化制御は有益ではありません。オプティマイザは、これらのプラットフォーム固有のトレードオフをすでに認識している必要があります。コンパイラの選択をオーバーライドする必要があると感じた場合は、プラットフォームごとにオーバーライドしてください。

于 2012-07-10T19:51:52.747 に答える
5

1.テンプレート関数の定義をインラインでマークすることは、私が最近学んだように、インラインでマークされているかどうかに関係なく、コンパイラーによって自動的にインラインとして修飾されるという事実を考慮して、意味がありますか?か否か?動作はコンパイラ固有ですか?

クラス定義で定義されたメンバー関数は常にインライン関数であるという事実を参照していると思います。これはC++標準に準拠しており、最初の発行以来次のようになっています。

9.3メンバー機能

..。

メンバー関数は、そのクラス定義で定義(8.4)できます。その場合、それはインラインメンバー関数(7.1.2)です。

したがって、次の例では、template <typename FloatT> my_class<FloatT>::my_function()は常にインライン関数です。

template <typename FloatT>
class my_class
{
public:
    void my_function() // `inline` member function
    {
        //...
    }
};

template <>
class my_class<double> // specialization for doubles
{
public:
    void my_function() // `inline` member function
    {
        //...
    }
};

ただし、の定義をの定義のmy_function()外に移動してtemplate <typename FloatT> my_class<FloatT>も、自動的にインライン関数にはなりません。

template <typename FloatT>
class my_class
{
public:
    void my_function();
};

template <typename FloatT>
void my_class<FloatT>::my_function() // non-`inline` member function
{
    //...
}

template <>
void my_class<double>::my_function() // non-`inline` member function
{
    //...
}

inline後者の例では、次の定義で指定子を使用することは理にかなっています(冗長ではありません) 。

template <typename FloatT>
inline void my_class<FloatT>::my_function() // `inline` member function
{
    //...
}

template <>
inline void my_class<double>::my_function() // `inline` member function
{
    //...
}

2.そして最も重要なのは、テンプレートクラスのすべてのメンバー関数の定義を1つのファイルにまとめたいので、.inlまたは.cpp(.cppの場合は明示的なインスタンス化を使用)、できればそれでもコンパイラー(MSVCおよびGCC)に、どの関数をインライン化する必要があり、どの関数をインライン化するべきでないかをヒントできること、テンプレート関数でそのようなことが可能かどうか、どうすればこれを達成できるか、または本当に方法がない場合(私は願っています)あります)、最も最適な妥協点は何でしょうか?

ご存知のように、コンパイラーは、inline指定子があるかどうかに関係なく、関数をインライン化することを選択できます。指定子はinline単なるヒントです。

インライン化を強制したり、インライン化を防止したりする標準的な方法はありません。ただし、ほとんどのC ++コンパイラは、それを実現するための構文拡張をサポートしています。MSVCは、__forceinlineインライン化を強制#pragma auto_inline(off)し、それを防ぐためのキーワードをサポートしています。G ++は、インライン化を強制および防止するためのサポートalways_inlineと属性をそれぞれサポートします。noinlineコンパイラが要求どおりに関数をインライン化できない場合に診断を有効にする方法など、詳細については、コンパイラのドキュメントを参照する必要があります。

これらのコンパイラ拡張機能を使用する場合は、関数がインライン化されているかどうかをコンパイラにヒントできるはずです。

一般に、すべての「単純な」メンバー関数定義を1つのファイル(通常はヘッダー)にまとめることをお勧めします。つまり、メンバー関数が定義に必要なs#includeのセットよりも多くのsを必要としない場合は、#includeクラス/テンプレート。たとえば、メンバー関数の定義でが必要#include <algorithm>になる場合がありますが、定義するためにクラス定義<algorithm>を含める必要はほとんどありません。コンパイラは、使用しない関数定義をスキップできますが、#includesの数が多いとコンパイル時間が著しく長くなる可能性があり、これらの非「単純な」関数をインライン化する必要はほとんどありません。


3.コンパイラが最近非常に賢く、インライン化する関数と呼び出す関数をより適切に選択でき、リンク時のコード生成とリンク時の最適化が可能である場合、.cppを効果的に調べることができます。 -インライン化または呼び出される運命を決定するためにリンク時に関数定義を配置します。おそらく、すべての定義をそれぞれの.cppファイルに移動するだけでよい解決策になるでしょうか。

すべての関数定義をCPPファイルに配置すると、ほとんどすべての関数のインライン化をLTOに依存することになります。これは、次の理由で希望するものではない場合があります。

  1. 少なくともMSVCのLTCGでは、強制的にインライン化する機能を放棄します(inline、__ inline、__ forceinlineを参照
  2. CPPファイルが共有ライブラリにリンクされている場合、共有ライブラリにリンクしているプログラムは、ライブラリ関数のLTOインライン化の恩恵を受けません。これは、コンパイラ中間言語(IL)(LTOへの入力)が破棄され、DLLまたはSOで使用できないためです。
  3. Under The Hood:Link-time Code Generationがまだ正しい場合、「静的ライブラリの関数の呼び出しは最適化できません」。
  4. リンカはすべてのインライン化を実行しますが、これはコンパイラがコンパイル時にインライン化を実行するよりもはるかに遅い場合があります。
  5. コンパイラのLTO実装には、特定の関数をインライン化しない原因となるバグがある可能性があります。
  6. LTOを使用すると、ライブラリを使用するプロジェクトに特定の制限が課される場合があります。たとえば、Under The Hood:Link-time Code Generationによると、「プリコンパイル済みヘッダーとLTCGには互換性がありません」。/ LTCG (リンク時コード生成) MSDNページには、「/LTCGは/INCREMENTALでの使用には無効です」などの他の注記があります。

インライン化される可能性のある関数定義をヘッダーファイルに保持する場合は、コンパイラーのインライン化とLTOの両方を使用できます。一方、すべての関数定義をCPPファイルに移動すると、コンパイラのインライン化が変換ユニット内にのみ制限されます。

于 2012-07-13T12:51:04.870 に答える
3
  1. どこでそれを学んだかはわかりませんが、テンプレートは「インラインでマークされているかどうかに関係なく、コンパイラによって自動的にインラインとして認定されません」。テンプレートとインライン関数はどちらも「あいまいなリンケージ」と呼ばれるものがあります。つまり、それらの定義はエラーなしで複数のオブジェクトに存在でき、リンカは定義の1つを使用し、他の定義を破棄します。ただし、テンプレートとインライン関数の両方にあいまいなリンクがあるという事実は、テンプレートが自動的にインラインになることを意味するわけではありません。ライオンとトラはどちらも大きな猫ですが、それはライオンがトラであるという意味ではありません。

  2. 使用しているすべてのインスタンス化を事前に知っていない限り、明示的なインスタンス化を常に使用できるとは限りません。たとえば、他の人が使用するテンプレートライブラリを作成している場合、すべての明示的なインスタンス化を提供することはできないため、でテンプレートを定義する必要.hがあります。 (または.inl)コードのユーザーが作成できるファイル#include。すべてのインスタンス化を事前に知っている場合、.cppファイルで明示的なインスタンス化を使用すると、コンパイル時間が短縮されるという利点があります。コンパイラは、明示的なインスタンス化を含むファイルでテンプレートを1回だけインスタンス化し、テンプレートを使用するすべてのファイルではインスタンス化しないためです。しかし、それはインライン化とは何の関係もありません。関数をインライン化するには、その定義が必要です。それを呼び出すコードから見えるので、ファイルで関数テンプレート(またはクラステンプレートのメンバー関数)のみを定義する場合.cpp、そのファイル以外の場所にインライン化することはできません。.cppそれらをファイルで定義し、それらをとして修飾すると、キーワードが表示されないinline他のファイルからそれらを呼び出そうとすると問題が発生する可能性がありますinline(関数が1つの翻訳単位でインラインで宣言されている場合、すべてでインラインで宣言されている必要があります)それが表示される翻訳単位、[dcl.fct.spec] / 4。)
    その価値については、通常、.inlファイルを使用する必要はありません。テンプレートを直接定義するだけです。.hファイル。処理するファイルが1つ少なくなります。すべてが1つの場所にあり、それだけで機能します。テンプレートを使用するすべてのファイルは、定義を確認し、必要に応じてそれらをインライン化することを選択できます。その場合でも、明示的なインスタンス化を使用して、インライン化の機会を犠牲にすることなく、コンパイル時間を改善し、オブジェクトファイルのサイズを減らすことができます。

  3. テンプレートコードが属するヘッダーでテンプレートコードを定義するよりも、なぜそれが優れているのでしょうか。正確に何を達成しようとしていますか?ファイルが少ない場合は、テンプレートコードをヘッダーに配置します。これは常に機能し、コンパイラはLTOを必要とせずにすべてをインライン化することを選択でき、クラステンプレートごとに1つのファイルしかありません(明示的なインスタンス化を使用してコンパイル時間を改善できます) 。すべてのコードをファイルに移動しようとしている場合.cpp(これは、集中しすぎていると思います)、先に進んで実行してください。それは悪い考えであり、おそらく問題を引き起こすと思います(リンク時の最適化には、私が試した唯一のコンパイラで問題があり、コンパイルが速くなることはありません)が、それが必要な場合は、あなたのボートを浮かぶものは何でも。

あなたの質問はここでの誤解を中心に展開しているようです:

一部の関数がインライン化されないようにする信頼できる方法を知っている場合にのみ、すべてのクラス実装を.inlファイルに入れます。

すべてのテンプレート定義がヘッダーファイルにある場合、「一部の関数がインライン化されるのを防ぐための信頼できる方法」は必要ありません...上で述べたように、テンプレートがinlineヘッダーにあるという理由だけで自動的にテンプレートが作成されるわけではありません。 reが大きすぎてインライン化できない場合、コンパイラーはそれらをインライン化しません。最初の問題は解決しました。第二に:

または、inlineキーワードがコンパイラに一部の関数をインライン化するように強く提案できる場合は.cppファイルにありますが、もちろん、特にinlineでマークされた関数が.cppファイルにある場合はそうではありません。

inline上で述べたように、ファイルでマークされた関数は.cpp、ヘッダーでインラインでマークされていない限り、形式が正しくなく、他の.cppファイルで使用されることはありません。したがって、これを行うと、作業が困難になり、リンカーエラーが発生する可能性があります。なぜわざわざ。

繰り返しますが、すべての記号は、テンプレート定義をヘッダーに配置することを示しています。明示的なインスタンス化を引き続き使用できるため(std::stringリンク先の投稿で説明されているように、GCCの場合と同様)、両方の長所を活用できます。それが達成しない唯一のことは、テンプレートのユーザーから実装を隠すことですが、それがテンプレートの観点から実装できる非テンプレート関数APIを提供する場合、それはとにかくあなたの目的ではないようです。単一の.cppファイル。

于 2012-07-12T23:28:51.307 に答える
1

これは完全な答えではありません。

私は、clangとllvmが非常に包括的なリンク時間の最適化を実行できることを読みました。これには、リンク時間のインライン化が含まれます。これを有効にするには、clang++を使用するときに最適化レベル-O4でコンパイルします。オブジェクトファイルは、マシンコードではなくllvmバイトコードになります。これがこれを可能にするものです。したがって、この機能を使用すると、必要に応じてインライン化されることを認識した上で、すべての定義をcppファイルに入れることができます。

ところで、関数本体の長さだけが、インライン化されるかどうかを決定するものではありません。1つの場所からのみ呼び出される長い関数は、その場所に簡単にインライン化できます。

于 2012-07-14T07:12:21.103 に答える