回归比。指针
Returning Vs. Pointer
这两种情况下的性能会有多大差异?
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; }
我能想到的一件事是寄存器不能用于此目的,对吗?除此之外,我不知道这个函数在经过编译器后会变成什么样子。
哪个更高效、更快捷?
如果函数可以内联,通常前2个没有区别。
否则(没有内联,因为没有 link-time 优化)return 按值 int
效率更高,因为它只是寄存器中的一个值可以马上使用。此外,调用者不必传递那么多参数,或 find/make space 来指向。如果调用者确实想要使用输出值,则必须重新加载它,从而在从输入就绪到输出就绪的整个依赖链中引入延迟。 (Store-forwarding 延迟在现代 x86 CPU 上约为 5 个周期,而 lea eax, [rdi + rsi]
为 x86-64 System V 实现该功能的延迟为 1 个周期。
例外情况可能发生在调用者不打算使用该值,只是希望它位于内存中某个地址的极少数情况下。将该地址传递给被调用者(在寄存器中)以便在那里可以使用它意味着调用者不必将该地址保留在函数调用期间仍然存在的任何地方。
对于结构版本:
a register cannot be used for this purpose, correct?
不,对于某些调用约定,可以在寄存器中return编辑小结构。
x86-64 System V 将 return 您的 my
按 RDX:RAX 寄存器对中的值进行结构化,因为它小于 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 是正确的 sign-extended (). GCC does a really dumb byte store / dword reload that creates a store-forwarding stall, to get uninitialized garbage from memory instead of from high bytes of EDX. Purely a missed optimization, but see it in https://godbolt.org/z/WGcqKc。它还疯狂地使用 SSE2 将两个整数合并为一个 64-执行 movq rax, xmm0
之前的位值,或 output-arg.
的内存
如果调用者使用这些值,您肯定希望结构版本内联,因此可以优化打包到 return-value 寄存器中。
How does function ACTUALLY return struct variable in C? has an ARM example for a larger struct: return by value passes a hidden pointer to the caller's return-value object. From there, it may need to be copied by the caller if assigning to something that escape analysis can't prove is private. (e.g. through some pointer).
还相关:
How do C compilers implement functions that return large structures? 指出 code-gen 可能在 C 和 C++ 之间有所不同。
我不知道如何解释在不了解 asm 和您关心的调用约定的情况下可以应用的任何一般经验法则。通常 pass/return large structs by reference,但对于小型结构,它非常“取决于”。
这两种情况下的性能会有多大差异?
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; }
我能想到的一件事是寄存器不能用于此目的,对吗?除此之外,我不知道这个函数在经过编译器后会变成什么样子。
哪个更高效、更快捷?
如果函数可以内联,通常前2个没有区别。
否则(没有内联,因为没有 link-time 优化)return 按值 int
效率更高,因为它只是寄存器中的一个值可以马上使用。此外,调用者不必传递那么多参数,或 find/make space 来指向。如果调用者确实想要使用输出值,则必须重新加载它,从而在从输入就绪到输出就绪的整个依赖链中引入延迟。 (Store-forwarding 延迟在现代 x86 CPU 上约为 5 个周期,而 lea eax, [rdi + rsi]
为 x86-64 System V 实现该功能的延迟为 1 个周期。
例外情况可能发生在调用者不打算使用该值,只是希望它位于内存中某个地址的极少数情况下。将该地址传递给被调用者(在寄存器中)以便在那里可以使用它意味着调用者不必将该地址保留在函数调用期间仍然存在的任何地方。
对于结构版本:
a register cannot be used for this purpose, correct?
不,对于某些调用约定,可以在寄存器中return编辑小结构。
x86-64 System V 将 return 您的 my
按 RDX:RAX 寄存器对中的值进行结构化,因为它小于 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 是正确的 sign-extended (movq rax, xmm0
之前的位值,或 output-arg.
如果调用者使用这些值,您肯定希望结构版本内联,因此可以优化打包到 return-value 寄存器中。
How does function ACTUALLY return struct variable in C? has an ARM example for a larger struct: return by value passes a hidden pointer to the caller's return-value object. From there, it may need to be copied by the caller if assigning to something that escape analysis can't prove is private. (e.g. through some pointer).
还相关:
How do C compilers implement functions that return large structures? 指出 code-gen 可能在 C 和 C++ 之间有所不同。
我不知道如何解释在不了解 asm 和您关心的调用约定的情况下可以应用的任何一般经验法则。通常 pass/return large structs by reference,但对于小型结构,它非常“取决于”。