按引用和按值传递时的 gcc 程序集
Posted
技术标签:
【中文标题】按引用和按值传递时的 gcc 程序集【英文标题】:gcc assembly when passing by reference and by value 【发布时间】:2015-10-17 15:29:29 【问题描述】:我有一个计算乘积的简单函数 两个双数组:
#include <stdlib.h>
#include <emmintrin.h>
struct S
double *x;
double *y;
double *z;
;
void f(S& s, size_t n)
for (int i = 0; i < n; i += 2)
__m128d xs = _mm_load_pd(&s.x[i]);
__m128d ys = _mm_load_pd(&s.y[i]);
_mm_store_pd(&s.z[i], _mm_mul_pd(xs, ys) );
return;
int main(void)
S s;
size_t size = 4;
posix_memalign((void **)&s.x, 16, sizeof(double) * size);
posix_memalign((void **)&s.y, 16, sizeof(double) * size);
posix_memalign((void **)&s.z, 16, sizeof(double) * size);
f(s, size);
return 0;
请注意,函数 f 的第一个参数是通过引用传入的。 让我们看一下 f() 的结果汇编(我删除了一些不相关的 件,插入 cmets 并放置一些标签):
$ g++ -O3 -S asmtest.cpp
.globl _Z1fR1Sm
_Z1fR1Sm:
xorl %eax, %eax
testq %rsi, %rsi
je .L1
.L5:
movq (%rdi), %r8 # array x (1)
movq 8(%rdi), %rcx # array y (2)
movq 16(%rdi), %rdx # array z (3)
movapd (%r8,%rax,8), %xmm0 # load x[0]
mulpd (%rcx,%rax,8), %xmm0 # multiply x[0]*y[0]
movaps %xmm0, (%rdx,%rax,8) # store to y
addq $2, %rax # and loop
cmpq %rax, %rsi
ja .L5
注意数组 x、y、z 的地址被加载到通用 每次迭代的寄存器,参见语句 (1),(2),(3)。为什么 gcc 不动 这些指令在循环之外?
现在制作结构的本地副本(不是深层副本):
void __attribute__((noinline)) f(S& args, size_t n)
S s = args;
for (int i = 0; i < n; i += 2)
__m128d xs = _mm_load_pd(&s.x[i]);
__m128d ys = _mm_load_pd(&s.y[i]);
_mm_store_pd(&s.z[i], _mm_mul_pd(xs, ys) );
return;
组装:
_Z1fR1Sm:
.LFB525:
.cfi_startproc
xorl %eax, %eax
testq %rsi, %rsi
movq (%rdi), %r8 # (1)
movq 8(%rdi), %rcx # (2)
movq 16(%rdi), %rdx # (3)
je .L1
.L5:
movapd (%r8,%rax,8), %xmm0
mulpd (%rcx,%rax,8), %xmm0
movaps %xmm0, (%rdx,%rax,8)
addq $2, %rax
cmpq %rax, %rsi
ja .L5
.L1:
rep ret
请注意,与前面的代码不同, 负载 (1)、(2)、(3) 现在位于循环之外。
我希望能解释一下为什么这两个程序集 代码不同。内存别名在这里是否相关? 谢谢。
$ gcc --version gcc (Debian 5.2.1-21) 5.2.1 20151003
【问题讨论】:
你的 gcc 版本是... @KarolyHorvath 显然至少从 4.4.7 到 5.2.0。icc
也可以这样做,但 clang
不会。
$ gcc --version gcc (Debian 5.2.1-21) 5.2.1 20151003。我附加到原帖。
【参考方案1】:
是的,gcc 会在循环的每次迭代中重新加载 s.x
和 s.y
,因为 gcc 不知道 &s.z[i]
是否为某些 i
别名部分 S
对象的一部分通过引用传递给 f(S&, size_t)
.
使用 gcc 5.2.0,将__restrict__
应用于S::z
并将s
引用参数应用于f()
,即:
struct S
double *x;
double *y;
double *__restrict__ z;
;
void f(S&__restrict__ s, size_t n)
for (int i = 0; i < n; i += 2)
__m128d xs = _mm_load_pd(&s.x[i]);
__m128d ys = _mm_load_pd(&s.y[i]);
_mm_store_pd(&s.z[i], _mm_mul_pd(xs, ys));
return;
.. 导致 gcc 生成:
__Z1fR1Sm:
LFB518:
testq %rsi, %rsi
je L1
movq (%rdi), %r8
xorl %eax, %eax
movq 8(%rdi), %rcx
movq 16(%rdi), %rdx
.align 4,0x90
L4:
movapd (%r8,%rax,8), %xmm0
mulpd (%rcx,%rax,8), %xmm0
movaps %xmm0, (%rdx,%rax,8)
addq $2, %rax
cmpq %rax, %rsi
ja L4
L1:
ret
使用 Apple Clang 700.1.76,只需要 s
引用上的 __restrict__
:
__Z1fR1Sm: ## @_Z1fR1Sm
.cfi_startproc
## BB#0:
pushq %rbp
Ltmp0:
.cfi_def_cfa_offset 16
Ltmp1:
.cfi_offset %rbp, -16
movq %rsp, %rbp
Ltmp2:
.cfi_def_cfa_register %rbp
testq %rsi, %rsi
je LBB0_3
## BB#1: ## %.lr.ph
movq (%rdi), %rax
movq 8(%rdi), %rcx
movq 16(%rdi), %rdx
xorl %edi, %edi
.align 4, 0x90
LBB0_2: ## =>This Inner Loop Header: Depth=1
movapd (%rax,%rdi,8), %xmm0
mulpd (%rcx,%rdi,8), %xmm0
movapd %xmm0, (%rdx,%rdi,8)
addq $2, %rdi
cmpq %rsi, %rdi
jb LBB0_2
LBB0_3: ## %._crit_edge
popq %rbp
retq
.cfi_endproc
【讨论】:
奇怪的是,-fstrict-aliasing
没有帮助。我可能误解了该选项的作用。
我很确定你是对的,令人困惑的部分是三个额外的负载是从 %rdi 加载地址而不是实际的数组数据。
@DanielTrebbien 谢谢,我理解那部分(我在原始帖子中将其在 cmets 中写入程序集)。问题是要了解为什么涉及混叠;当我们写 s.z[i]=s.x[i]*s.y[i] 时,s.z 可以与 s.x 或 s.y 别名,如果我们稍后在代码中重用 s.z[i] 将需要额外的负载,但就像你说的那样正在重新加载地址。输入指针(指向结构的指针)没有任何别名,即我们没有 f(S&s1, S&s2) 并且不应该重新加载 s1, s2 指向的内容。我可能需要多考虑一下。
@Jester -fstrict-aliasing 我相信告诉 gcc 如果取消引用不同类型的指针,它们将不会引用相同的内存。以上是关于按引用和按值传递时的 gcc 程序集的主要内容,如果未能解决你的问题,请参考以下文章