タイトルに示した通り
3 に答える
コンパイラ ベンダーは通常、参照をポインタとして実装します。ポインターは、多くの組み込み型と同じかそれより大きいサイズになる傾向があります。これらの組み込み型の場合、値渡しでも参照渡しでも、同じ量のデータが渡されます。ただし、関数では、実際のデータを取得するために、この内部ポインターを逆参照する必要があります。これにより、生成されたコードに命令が追加され、キャッシュにない可能性のある2 つのメモリ ロケーションも作成されます。違いはそれほど大きくありませんが、タイトなループで測定できます.
コンパイラ ベンダーは、const 参照 (および場合によっては非 const 参照も) が組み込み型で使用されている場合、それらを無視することを選択できます。これはすべて、関数とその呼び出し元を処理するときにコンパイラが利用できる情報に依存します。
int、char、short、float などの Pod タイプの場合、データのサイズは、実際のデータを参照するために渡されるアドレスと同じ (または小さい) です。参照されたアドレスの値を調べることは不要な手順であり、追加のコストがかかります。
たとえば、次の関数foo
を使用して、bar
void foo(char& c) {...}
void bar(char c) {...}
がfoo
呼び出されると、アドレスはプラットフォームに応じて 32 ビットまたは 64 ビットのいずれかの値で渡されます。c
withinを使用するfoo
と、渡されたアドレスに保持されているデータの値を検索するコストがかかります。
呼び出し時bar
に char のサイズの値が渡され、アドレス ルックアップのオーバーヘッドはありません。
実際には、C++ 実装は通常、内部でポインターを渡すことによって参照渡しを実装します (呼び出しがインライン化されていないと仮定します)。
したがって、小さな値を渡すよりもポインターを渡す方が高速ではないため、参照渡しを高速化するための巧妙なメカニズムはありません。また、関数内に入ると、値渡しも最適化の恩恵を受けることができます。例えば:
int foo(const int &a, int *b) {
int c = a;
*b = 2;
return c + a;
}
コンパイラが知っているすべての場合、は「エイリアシング」と呼ばれる をb
指します。a
値で渡さa
れた場合、この関数は と同等に最適化できます*b = 2; return 2*a;
。最新の CPU の命令パイプラインでは、これは「ロードを開始し、b の格納を開始し、a がロードされるのを待ち、2 倍し、b が格納されるのを待ち、戻る」というよりも、「ロードを開始し、b を格納を開始する」のようになります。 、a がロードされるのを待ち、b が格納されるのを待ち、ロードを開始し、a がロードされるのを待ち、a を c に追加し、戻る」と、エイリアシングの可能性がパフォーマンスに大きな影響を与える理由がわかり始めます。場合によっては、これが必ずしも大きな効果ではない場合もあります。
もちろん、エイリアシングは、可能な入力に対する関数の効果を変更する場合にのみ最適化を妨げます。しかし、関数の意図がエイリアシングが結果に影響を与えるべきではないという理由だけで、必ずしもコンパイラが影響しないと想定できることを意味するわけではありません。それはわからない。また、2 番目のポインター パラメーターが存在する必要はありません。オプティマイザーが「認識できない」コードを関数が呼び出すときはいつでも、参照が変更される可能性があると想定する必要があります。