Я действительно зависит от вашей платформы и компилятора, а также от того, встроена функция или нет.
При передаче по ссылке структура не копируется, в стеке сохраняется только ее адрес, а не содержимое. При передаче по значению содержимое копируется. На 64-битной платформе размер структуры такой же, как указатель на структуру (с учетом 64-битных указателей, что представляется более распространенной ситуацией). Таким образом, преимущества передачи по ссылке здесь не совсем ясны.
Однако есть еще одна вещь, которую следует учитывать. Ваша структура содержит значение с плавающей точкой. В архитектуре Intel они могут быть сохранены в FPU или в регистре SIMD перед вызовом функции. В этой ситуации, если функция принимает параметр по ссылке, тогда они должны быть переданы в память, а адрес этой памяти передан функции. Это может быть очень медленно. Если бы они были переданы по значению, копирование в память не потребовалось бы (быстрее). И одна платформа (PS3), компилятор не будет достаточно умен, чтобы удалить эти разливы, даже в случае встроенной функции.
На самом деле, как и на любой вопрос микрооптимизации, «хорошего ответа» не существует, все зависит от того, как вы используете функцию и чего хочет ваш компилятор / платформа. Лучше всего было бы убедиться (или использовать инструмент для анализа сборки), чтобы проверить, что лучше для вашей комбинации платформа / компилятор.
Я собираюсь закончить, цитируя Джеймина Кесслера из Q-Game s, который гораздо больше разбирается в этих темах, чем я когда-либо мог бы быть:
2) Если тип помещается в регистр, передайте его по значению. НЕ ПРОПУСКАЙТЕ ВЕКТОРНЫЕ ТИПЫ ПО ССЫЛКЕ, ОСОБЕННО СОСТАВЛЯЙТЕ ССЫЛКУ. Если функция в конечном итоге становится встроенной, GCC иногда попадает в память при обращении к ссылке. Я скажу еще раз: если тип, который вы используете, вписывается в регистры (float, int или vector), не передавайте его функции ни в чем, кроме значения. В случае ненормальных компиляторов, таких как Visual Studio для x86, он не может поддерживать выравнивание объектов в стеке, и поэтому объекты, имеющие директивы выравнивания, должны передаваться функциям по ссылке. Это может быть исправлено или в 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
состоит в соглашении о вызовах, используемом DoSomething1
и DoSomething2
для получения структуры Vector
. При компиляции с g++
(версия 4.2, архитектура x86_64) сгенерированный код:
.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)
push 1.0f
в стеке). И если они были результатом предыдущего вычисления, это потребовало бы их копирования из регистра %xmm0
SIMD. И это может быть очень медленным (это может привести к остановке конвейера до тех пор, пока значение не станет доступным, и если стек не выровнен должным образом, копирование также будет медленным).
Так что, если ваша функция не встроенная , предпочитайте передавать вместо копии вместо const-reference. Если функция действительно inline , посмотрите код, сгенерированный, прежде чем принять решение.