コンパイラの最適化後のプログラム速度には、値による戻り値と永続オブジェクトへの参照による戻り値のどちらが優れていますか?
/// Generate a 'foo' value directly as a return type.
template< typename T >
inline T gen_foo();
/// Get a 'foo' reference of a persistent object.
template< typename T >
inline T const& get_foo();
T
プリミティブ、ポインター、メンバー ポインター、またはユーザー定義の小さな PO,D. のようなデータになります。
私の知る限り、値渡しですが、参照渡しの可能性があります。
値渡し:
- 1 つを返す
T
と、オブジェクトが小さくなり、呼び出し元の変数にすばやくコピーできます。 - オプティマイザは、(N)RVO と copy-elision を使用して、返されたコピーを削除できます。
- オプティマイザーは、生成コードまたは生成された値を呼び出し元のコードにインライン化できます。
- プログラムは、キャッシュされているかどうかにかかわらず、RAM にアクセスする必要はありません。
- 1 つを返す
参照渡し:
- オプティマイザは、永続的な値を完全に評価し、その使用を文字通りの等価物に置き換える場合があります。これが発生するかどうかは、残りの分析に影響します。
- 永続値が完全に評価され、リテラルとして置換される場合:
- 返す値はありません。
- オプティマイザーはリテラルを簡単にインライン化できます。
- プログラムは、キャッシュされているかどうかにかかわらず、RAM にアクセスする必要はありません。
- 永続的な値を完全に評価および置換できない場合:
- 1 つの参照を返すことは小さなオブジェクトであり、呼び出し元の変数へのコピーが高速です。
- オプティマイザーは、(N)RVO とコピー省略を使用して、コピーを返さないようにすることができます。
- オプティマイザーは、生成コードまたは生成された値を呼び出し元のコードにインライン化できません。
- プログラムは RAM にアクセスする必要がありますが、これは L1/L2/etc にある可能性があります。キャッシュ。
バックグラウンド:
一部のプラットフォームでは、値で返すといくつかの浮動小数点例外がトリガーされますが、パラメーター参照で埋めるとトリガーされないため、これを考慮する必要があります。(これは当然のことです。この質問は、この点について議論するためのものではありません。) したがって、私が欲しかった API と、使用を検討せざるを得ない API は次のとおりです。
/// Generate a 'foo' value directly as a return type.
template< typename T >
inline T gen_foo();
/// Fill in a 'foo' passed in by reference.
template< typename T >
inline void fill_foo( T& r_foo );
私は 'fill' API を嫌います (定義を初期化から分離し、一時的な作成を妨げるため) 代わりに、次のような参照渡しバージョンに変換できます。
/// Forward-declare 'Initialized_Foo'.
template< typename T > struct Initialized_Foo;
/// Get a 'foo' reference; this returns a persistent reference to a static object.
template< typename T >
inline T const& get_foo()
{
#if 0
// BAD: This calls 'fill_foo' *every* time, and breaks const-correctness.
thread_local static const T foo;
fill_foo( const_cast< T& >( foo ) );
return foo;
#else
// GOOD: This calls 'fill_foo' only *once*, and honours const-correctness.
thread_local static const Initialized_Foo< T > initialized_foo;
return initialized_foo.data;
#endif
}
/// A 'foo' initializer to call 'fill_foo' at construction time.
template< typename T >
struct Initialized_Foo
{
T data;
Initialized_Foo()
{
fill_foo( data );
}
};