【问题标题】:Returning Vs. Pointer返回与。指针
【发布时间】:2020-11-08 15:47:22
【问题描述】:

这两种情况的性能会有多大差异?

int func(int a, int b) { return a + b; }

void func(int a, int b, int * c) { *c = a + b; }

现在,如果它是一个结构呢?

typedef struct { int a; int b; char c; } my;

my func(int a, int b, char c) { my x; x.a = a; x.b = b; x.c = c; return x; }

void func(int a, int b, int c, my * x) { x->a = a; x->b = b; x->c = c; }

我能想到的一件事是寄存器不能用于此目的,对吗?除此之外,我不知道这个函数在通过编译器后会变成什么样子。

哪个更高效、更快捷?

【问题讨论】:

  • 在所有情况下检查生成的汇编程序(打开编译器优化),并分析性能。
  • 最后两个案例与第一个案例的相关性在哪里。最后两个案例甚至都不太相似
  • 我没有足够的知识来确定生成的汇编代码有多好(即可能是一些具有更大延迟的指令),我也没有足够的知识来正确地对它们进行基准测试,所以我正在寻找答案那些在自己之前对此提出质疑并能够得出编译器行为方式的结论的人。
  • @Chase 它与我的标题相关,比较返回数据与使用指针。

标签: c++ c optimization micro-optimization


【解决方案1】:

如果函数可以内联,往往前2个没什么区别。

否则(因为没有链接时优化而没有内联)按值返回int 效率更高,因为它只是寄存器中的一个值,可以立即使用。此外,调用者不必传递尽可能多的参数,或者找到/腾出空间来指向。如果调用者确实想使用输出值,则必须重新加载它,从而在整个依赖链中引入延迟,从输入就绪到输出就绪。 (现代 x86 CPU 上的存储转发延迟约为 5 个周期,而在 x86-64 System V 上实现该功能的 lea eax, [rdi + rsi] 的延迟为 1 个周期。

例外情况可能是调用者不打算使用该值的极少数情况,只是希望它在某个地址的内存中。将该地址传递给被调用者(在寄存器中)以便可以在那里使用,这意味着调用者不必将该地址保留在任何可以在函数调用中继续存在的地方。


对于结构版本:

寄存器不能用于此目的,对吗?

不,对于某些调用约定,可以在寄存器中返回小结构。

x86-64 System V 将在 RDX:RAX 寄存器对中按值返回您的 my 结构,因为它小于 16 个字节并且都是整数。 (并且可以简单地复制。)在https://godbolt.org/z/x73cEh 上试试 -

# clang11.0 -O3 for x86-64 SysV
func_val:
        shl     rsi, 32
        mov     eax, edi
        or      rax, rsi             # (uint64_t)b<<32 | a;  the low 64 bits of the struct
    # c was already in EDX, the low half of RDX; clang leaves it there.
        ret
func_out:
        mov     dword ptr [rcx], edi
        mov     dword ptr [rcx + 4], esi        # just store the struct members 
        mov     byte ptr [rcx + 8], dl          # to memory pointed-to by 4th arg
        ret

GCC 不会像 clang 那样假定 char c 正确地符号扩展为 EDX (unofficial ABI feature)。 GCC 做了一个非常愚蠢的字节存储/双字重载,它创建了一个存储转发停顿,从内存而不是 EDX 的高字节中获取未初始化的垃圾。纯粹是一个错过的优化,但请参阅https://godbolt.org/z/WGcqKc。它还疯狂地使用 SSE2 将两个整数合并为一个 64 位值,然后再执行 movq rax, xmm0 或输出参数到内存中。

如果调用者使用这些值,您肯定希望结构版本内联,因此可以优化这种打包到返回值寄存器中。

How does function ACTUALLY return struct variable in C? 有一个更大结构的 ARM 示例:按值返回将隐藏指针传递给调用者的返回值对象。从那里,如果分配给逃逸分析无法证明是私有的东西,调用者可能需要从那里复制它。 (例如通过一些指针)。 What prevents the usage of a function argument as hidden pointer?

还相关:Why is tailcall optimization not performed for types of class MEMORY?

How do C compilers implement functions that return large structures? 指出代码生成可能在 C 和 C++ 之间有所不同。

如果不了解 asm 和您关心的调用约定,我不知道如何解释任何可以应用的一般经验法则。通常通过引用传递/返回 结构,但对于小型结构,它非常“取决于”。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-12-10
    • 2013-04-10
    • 1970-01-01
    • 1970-01-01
    • 2018-05-30
    • 2014-08-02
    • 2011-10-26
    • 2014-05-30
    相关资源
    最近更新 更多