現在、効率的なC++コードの書き方を研究していますが、関数呼び出しに関しては、疑問が浮かびます。この擬似コード関数の比較:
not-void function-name () {
do-something
return value;
}
int main () {
...
arg = function-name();
...
}
この他の点では同一の擬似コード関数:
void function-name (not-void& arg) {
do-something
arg = value;
}
int main () {
...
function-name(arg);
...
}
どのバージョンがより効率的で、どの点で(時間、メモリなど)ですか?依存する場合、最初の方がより効率的であり、2番目の方が効率的であるのはいつですか?
編集:コンテキストでは、この質問はハードウェアプラットフォームに依存しない相違点に限定されており、ほとんどの部分はソフトウェアにも限定されています。マシンに依存しないパフォーマンスの違いはありますか?
編集:これがどのように重複しているかわかりません。もう1つの質問は、参照渡し(前のコード)と値渡し(以下)を比較することです。
not-void function-name (not-void arg)
これは私の質問と同じものではありません。私の焦点は、関数に引数を渡すより良い方法ではありません。私の焦点は、outを外部スコープから変数に渡すより良い方法です。
まず、オブジェクトを返すことは、参照渡しよりも常に読みやすく(そしてパフォーマンスが非常に似ている)ことを考慮に入れてください。 。最も低いコストを得る方法を知りたい場合は、何を返す必要があります:
単純なオブジェクトまたは基本的なオブジェクトを返す必要がある場合、両方のケースでパフォーマンスは似ています。
オブジェクトが非常に大きく複雑な場合、オブジェクトを返すにはコピーが必要になり、参照パラメーターとして使用するよりも遅くなる可能性がありますが、メモリ消費量は少なくなると思います。
とにかく、コンパイラーは両方のパフォーマンスを非常によくする多くの最適化を行うと考えなければなりません。 コピー領域 を参照してください。
まあ、コンパイルは簡単なビジネスではないことを理解する必要があります。コンパイラーがコードをコンパイルする際には、多くの考慮事項があります。
C++標準は標準のABI(抽象バイナリインターフェイス)を提供していないため、この質問に単純に答えることはできません。したがって、各コンパイラは好きなコードをコンパイルでき、コンパイルごとに異なる結果を得ることができます。
たとえば、一部のプロジェクトでは、C++はMicrosoft CLRのマネージ拡張(C++/CX)にコンパイルされます。既にヒープ上のオブジェクトへの参照がすべてあるため、違いはないと思います。
アンマネージコンパイルの場合、答えは簡単ではありません。たとえば、「XXXはYYYよりも速く実行されますか?」について考えると、いくつかの質問が思い浮かびます。
std::array
)、またはヒープ上の何かへのポインターがありますか? (例:std::vector
)?具体的な例を挙げると、MSVC++およびGCCでは、値によってstd::vector
を返すことは、r-value-optimizationのため、参照で渡すことであり、ビット(数ナノ秒)高速で移動よりベクトルを返します。たとえば、これはClangでは完全に異なる場合があります。
最終的には、ここで唯一の真の答えはプロファイリングです。
copy elision と呼ばれる最適化のため、ほとんどの場合、オブジェクトを返すことを使用する必要があります。
ただし、関数の使用方法によっては、オブジェクトを参照渡しする方が適切な場合があります。
見る std::getline
たとえば、std::string
参照による。この関数はループ条件として使用されることを意図しており、std::string
until EOFに達する。同じstd::string
は、std::string
すべてのループ反復で再利用され、実行する必要があるメモリ割り当ての数を大幅に削減します。
いくつかの答えはこれに触れましたが、編集に照らして強調したいと思います
コンテキストでは、この質問はハードウェアプラットフォームに依存しない違いに制限されており、ほとんどの部分はソフトウェアにも限定されています。マシンに依存しないパフォーマンスの違いはありますか?
これが質問の制限である場合、答えは答えがないということです。 c ++の仕様では、オブジェクトの戻り値または参照渡しのパフォーマンスを賢明に実装する方法は規定されておらず、両方がコードの観点から行うセマンティクスのみが規定されています。
したがって、コンパイラーは、プログラマーに知覚可能な違いをもたらさないと仮定して、一方を他方と同一のコードに最適化することは自由です。
これに照らして、状況に応じて最も直感的なものを使用するのが最善だと思います。関数が実際に何らかのタスクまたはクエリの結果としてオブジェクトを「返す」場合、それを返します。関数が外部コードが所有するオブジェクトに対して操作を実行している場合は、参照渡しします。
これに関するパフォーマンスを一般化することはできません。まず、直感的な操作を行い、ターゲットシステムとコンパイラーが最適化する方法を確認します。プロファイリング後に問題を発見した場合、必要に応じて変更します。
プラットフォームごとにABIが異なるため、100%一般化することはできませんが、ほとんどの実装に適用されるかなり一般的なステートメントを作成できると思います。
まず、プリミティブ型を検討しましょう。低レベルでは、参照によるパラメータの受け渡しはポインタを使用して実装されますが、プリミティブな戻り値は通常、文字通りレジスタに渡されます。そのため、戻り値の方がパフォーマンスが向上する可能性があります。一部のアーキテクチャでは、小さな構造に同じことが当てはまります。 1つまたは2つのレジスタに収まるほど小さい値をコピーするのは非常に安価です。
ここで、より大きいがまだシンプルな(デフォルトのコンストラクター、コピーコンストラクターなどがない)戻り値を検討してみましょう。通常、大きな戻り値は、関数に戻り値を配置する場所へのポインターを渡すことで処理されます。コピー省略により、関数から返される変数、戻りに使用される一時変数、および結果が配置される呼び出し元の変数を1つにマージできます。したがって、受け渡しの基本は、参照渡しと戻り値渡しの場合とほぼ同じです。
全体的にプリミティブ型については、戻り値がわずかに優れていると予想し、より大きくても単純な型については、コンパイラがコピーの省略に非常に悪い場合を除き、同じかそれ以上であると期待します。
デフォルトのコンストラクターを使用する型の場合、コンストラクターのコピーなどはより複雑になります。関数が複数回呼び出された場合、戻り値は毎回オブジェクトを強制的に再構築しますが、参照パラメーターはデータ構造を再構築せずに再利用できる場合があります。一方、参照パラメーターは、関数が呼び出される前に(おそらく不要な)構成を強制します。
この擬似コード関数:
not-void function-name () {
do-something
return value;
}
戻り値がそれ以上の変更を必要としない場合に、より適切に使用されます。渡されたパラメーターは、function-name
でのみ変更されます。これ以上の参照は必要ありません。
それ以外は同一の擬似コード関数:
void function-name (not-void& arg) {
do-something
arg = value;
}
同じ変数の値をモデレートする別のメソッドがあり、どちらかの呼び出しで変数に加えられた変更を保持する必要がある場合に便利です。
void another-function-name (not-void& arg) {
do-something
arg = value;
}
パフォーマンスの面では、コピーは一般的に高価ですが、小さなオブジェクトの場合は違いは無視できるかもしれません。また、コンパイラーは戻りコピーを移動に最適化し、参照の受け渡しと同等にすることもできます。
正当な理由がない限り、const
以外の参照を渡さないことをお勧めします。戻り値を使用します(例:tryGet()
ソートの関数)。
必要に応じて、他の人がすでに言っているように、自分で違いを測定できます。両方のバージョンでテストコードを数百万回実行し、違いを確認します。