C ++: передать векторную структуру по ссылке или по значению? - PullRequest
9 голосов
/ 12 марта 2011

Это моя векторная структура: struct Vector{ float x, y; };

  • Должен ли я передать ее в функции по значению или как const Vector&?

Ответы [ 4 ]

10 голосов
/ 12 марта 2011

Если вы передадите по значению, функция получит копию , которую она может изменить локально, не влияя на вызывающего.

Если вы передадите по const-reference, функция получит толькоссылка только для чтения.Копирование не требуется, но вызываемая функция не может модифицировать его локально.

Учитывая размер структуры, издержки на копирование будут очень малы.Так что выбирайте, что лучше / проще для вызываемой функции.

7 голосов
/ 12 марта 2011

Для такой небольшой структуры любая из них может быть наиболее эффективной, в зависимости от вашей платформы и компилятора.

(Имя может немного смущать других программистов, так как vector в C ++ обычно означает «динамический массив». Как насчет Vector2D?)

3 голосов
/ 12 марта 2011

Я действительно зависит от вашей платформы и компилятора, а также от того, встроена функция или нет.

При передаче по ссылке структура не копируется, в стеке сохраняется только ее адрес, а не содержимое. При передаче по значению содержимое копируется. На 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 , посмотрите код, сгенерированный, прежде чем принять решение.

0 голосов
/ 12 марта 2011

В качестве справки.Это более эффективно, чем сделать копию структуры и передать ее (т.е. передать по значению).

Единственное исключение - если ваша платформа может разместить всю структуру в регистр.

...