C ++:通过引用或值传递Vector结构?

时间:2011-03-12 17:02:05

标签: c++ calling-convention

这是我的Vector结构:struct Vector{ float x, y; };

  • 我应该按值或const Vector&将其传递给函数吗?

4 个答案:

答案 0 :(得分:10)

如果您按值传递,该函数将获得一个副本,它可以在本地修改而不会影响调用者。

如果通过const-reference传递,该函数将只获得一个只读引用。不涉及复制,但被调用的函数无法在本地修改它。

考虑到结构的大小,复制开销将非常小。因此,选择被调用函数的最佳/最简单的方法。

答案 1 :(得分:7)

对于如此小的结构,根据您的平台和编译器,任何一个都可能是最有效的。

(该名称可能对其他程序员来说有点混乱,因为C ++中的 vector 通常意味着“动态数组”。如何关于Vector2D?)

答案 2 :(得分:3)

我真的取决于你的平台和编译器,以及函数是否内联。

通过引用传递时,不复制结构,只将其地址存储在堆栈中,而不是内容。按值传递时,将复制内容。在64位平台上,struct的大小与指向struct的指针相同(假设64位指针似乎是更常见的情况)。因此,通过引用传递的收益在这里并不十分清楚。

然而,还有另外一件事需要考虑。您的结构包含浮点值。在英特尔架构上,它们可以在调用函数之前存储在FPU或SIMD寄存器中。在这种情况下,如果函数通过引用获取参数,则必须将它们溢出到内存,并将此内存的地址传递给函数。这可能非常慢。如果它们是按值传递的,则不需要复制到内存(更快)。还有一个平台(PS3),即使在内联函数的情况下,编译器也不会足够聪明地删除那些溢出。

事实上,就像微优化的每个问题一样,没有“好的答案”,这完全取决于你对函数的用法,以及你的编译器/平台想要的东西。最好的方法是使用mesure(或使用工具来分析程序集)来检查什么是最适合您的平台/编译器组合。

我将引用Jaymin Kessler from Q-Game来完成那些对这些主题更精通的{/}}:

  

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);
}

从代码的角度来看,Test1Test2之间的唯一区别是DoSomething1DoSomething2用于接收{{1}的调用约定结构。使用Vector(版本4.2,体系结构x86_64)编译时,生成的代码为:

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 push movl $0x3f800000, (%rsp))。如果它们是先前计算的结果,则需要从1.0f SIMD寄存器复制它们。而且这可能非常慢(它可能会阻止管道直到值可用,如果堆栈没有正确对齐,副本也会很慢)。

因此,如果您的函数不是内联,则更喜欢通过副本而不是const-reference传递。如果该功能确实是内嵌,请在考虑之前观察生成的代码。

答案 3 :(得分:0)

作为参考。这比制作结构的副本并传递它(即通过值传递)更有效。

唯一的例外是您的平台可以将整个结构放入寄存器中。