これは私のベクトル構造体です:struct Vector{ float x, y; };
- 値またはとして関数に渡す必要があります
const Vector&
か?
これは私のベクトル構造体です:struct Vector{ float x, y; };
const Vector&
か?値渡しの場合、関数は、呼び出し元に影響を与えずにローカルで変更できるコピーを取得します。
const-reference で渡す場合、関数は読み取り専用参照のみを取得します。コピーは必要ありませんが、呼び出された関数はそれをローカルで変更できません。
構造体のサイズを考えると、コピーのオーバーヘッドは非常に小さくなります。したがって、呼び出された関数に最適/最も簡単なものを選択してください。
このような小さな構造の場合、プラットフォームとコンパイラによっては、どちらかが最も効率的である可能性があります。
(C++ のvectorは通常 "動的配列" を意味するため、他のプログラマーには名前が少し紛らわしいかもしれません。どうVector2D
ですか?)
プラットフォームとコンパイラ、および関数がインラインかどうかに大きく依存します。
参照渡しの場合、構造体はコピーされず、そのアドレスのみがスタックに格納され、コンテンツは格納されません。値渡しの場合、内容がコピーされます。64 ビット プラットフォームでは、構造体のサイズは構造体へのポインターと同じです (より一般的な状況と思われる 64 ビット ポインターを想定しています)。そのため、参照渡しのメリットは、ここではあまり明確ではありません。
ただし、考慮すべき点がもう 1 つあります。構造体に float 値が含まれています。Intel アーキテクチャでは、関数の呼び出し前に FPU または SIMD レジスタに格納できます。そのような状況で、関数が参照によってパラメーターを取得する場合は、パラメーターをメモリにスピルし、このメモリへのアドレスを関数に渡す必要があります。これは非常に遅くなる可能性があります。それらが値で渡された場合、メモリへのコピーは必要ありません (より高速です)。また、一部のプラットフォーム (PS3) では、インライン関数の場合でも、コンパイラはこれらのスピルを削除するほどスマートではありません。
実際、マイクロ最適化のすべての質問と同様に、「良い答え」はありません。それはすべて、関数をどのように使用するか、およびコンパイラ/プラットフォームが何を望んでいるかに依存します。最善の方法は、測定 (またはツールを使用してアセンブリを分析) して、プラットフォームとコンパイラの組み合わせに最適なものを確認することです。
最後に、Q-Game の Jaymin Kessler の言葉を引用して締めくくりたいと思います。
2) 型がレジスタに収まる場合は、値で渡します。参照によってベクトル型を渡さないでください。特に、const 参照です。関数が最終的にインライン化された場合、GCC は参照にヒットしたときにメモリに移動することがあります。もう一度言います。使用している型がレジスタ (float、int、または vector) に収まる場合は、値以外で関数に渡さないでください。Visual Studio for x86 のような正常でないコンパイラの場合、スタック上のオブジェクトの位置合わせを維持できないため、align ディレクティブを持つオブジェクトは、参照によって関数に渡す必要があります。これは、修正されるか、Xbox 360 である可能性があります。マルチプラットフォームの場合、最小公分母に対応する必要がないように、typedef を渡すパラメーターを作成することをお勧めします。
次のコードを検討してください。
struct Vector { float x, y; };
extern Vector DoSomething1(Vector v);
extern Vector DoSomething2(const Vector& v);
void Test1()
{
Vector v0 = { 1., 2. };
Vector v1 = DoSomething1(v0);
}
void Test2()
{
Vector v0 = { 1., 2. };
Vector v1 = DoSomething2(v0);
}
コードの観点からすると、 と の唯一の違いは、Test1
とが構造体を受け取るためにTest2
使用する呼び出し規約です。(バージョン 4.2、アーキテクチャ x86_64) でコンパイルすると、生成されるコードは次のようになります。DoSomething1
DoSomething2
Vector
g++
.globl __Z5Test1v
__Z5Test1v:
LFB2:
movabsq $4611686019492741120, %rax
movd %rax, %xmm0
jmp __Z12DoSomething16Vector
LFE2:
.globl __Z5Test2v
__Z5Test2v:
LFB3:
subq $24, %rsp
LCFI0:
movl $0x3f800000, (%rsp)
movl $0x40000000, 4(%rsp)
movq %rsp, %rdi
call __Z12DoSomething2RK6Vector
addq $24, %rsp
ret
LFE3:
Test1
の場合、値はメモリからロードされると SIMD レジスタを介して渡されることがわかり%xmm0
ます (したがって、前の計算の結果がすでにレジスタにある場合は、それらをロードする必要はありません。メモリー)。一方、 の場合Test2
、値はスタックに渡されます (スタックにmovl $0x3f800000, (%rsp)
プッシュ1.0f
)。そして、それらが前の計算の結果である場合、%xmm0
SIMD レジスタからそれらをコピーする必要があります。そして、それは非常に遅くなる可能性があります (値が利用可能になるまでパイプラインが停止する可能性があり、スタックが適切に整列されていない場合、コピーも遅くなります)。
したがって、関数がinlineでない場合は、const-reference ではなくコピーで渡すことをお勧めします。関数が実際にinlineである場合は、決定する前に生成されたコードを確認してください。
参考までに。これは、構造体のコピーを作成してそれを渡す (値渡し) よりも効率的です。
唯一の例外は、プラットフォームが構造全体をレジスターに収めることができる場合です。