【问题标题】:data alignment in structure and avx optimization结构中的数据对齐和 avx 优化
【发布时间】:2016-11-16 09:21:28
【问题描述】:

我正在尝试找出此代码的最佳(也许是 avx?)优化

typedef struct {
  float x;
  float y;
} vector;

vector add(vector u, vector v){
  return (vector){u.x+v.x, u.y+v.y};
}

运行 gcc -S code.c 会给出相当长的汇编代码

    .file   "code.c"
    .text
    .globl  add
    .type   add, @function
add:
.LFB0:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    movq    %rdi, -8(%rbp)
    movss   16(%rbp), %xmm1
    movss   48(%rbp), %xmm0
    addss   %xmm0, %xmm1
    movss   32(%rbp), %xmm2
    movss   64(%rbp), %xmm0
    addss   %xmm2, %xmm0
    movq    -8(%rbp), %rax
    movss   %xmm1, (%rax)
    movq    -8(%rbp), %rax
    movss   %xmm0, 16(%rax)
    movq    -8(%rbp), %rax
    popq    %rbp
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE0:
    .size   add, .-add
    .ident  "GCC: (Ubuntu 5.4.0-6ubuntu1~16.04.4) 5.4.0 20160609"
    .section    .note.GNU-stack,"",@progbits

虽然我期望如此简单的任务很少有说明。有人可以帮我优化这种代码,同时保持浮点类型吗?

谢谢。

【问题讨论】:

  • 实际上这是“非常少的指令” - 您可以通过使您的函数 inline 来消除一些开销。同样clang-O3 似乎比gcc 做得更好:godbolt.org/g/QRNOsL
  • @Fabio:与内联函数相比,宏没有真正的优势,也有一些缺点。
  • 开始使用 AoS 布局通常是个坏主意
  • @PaulR:clang 的输出不是“安全的”:/ 如果输入寄存器的高半部分有垃圾(ABI 确实允许),它可能会引发 FP 异常。它需要将高半部分归零才能使用addps。如果上半部分结果不正常,它也可能会减慢速度,即使您不关心 FP 异常,这也很重要,除非您启用了 DAZ 和 FTZ。 (但没有-ffast-math,编译不能假设其中任何一个。)
  • @starmole:首先,这是C题。其次,当它不内联时,通过 const-reference 传递这个结构会更糟。 x86-64 SysV ABI 按值传递这个结构到 XMM 寄存器中,所以它全部设置为 SIMD 添加。传入内存可能有助于愚蠢的编译器,但 gcc 通过指针传递它仍然做得不好(与在这种情况下做得很好的 clang (godbolt.org/g/YbwF6t) 相比;使用 MOVQ 加载解决了它无法进行零扩展的问题。它应该不过可能会使用 MOVSD,以防某些 CPU 关心 FP 与 int 负载。)

标签: c floating-point avx


【解决方案1】:

gcc 在使用Vector Instructions through Built-in Functions 时可以生成更好的代码:

typedef float v2f __attribute__((vector_size(8)));

v2f add(v2f u, v2f v) {
  return u + v;
}

生产:

add(float __vector(2), float __vector(2)):
    movlps  %xmm0, -32(%rsp)
    movlps  %xmm1, -40(%rsp)
    movss   -32(%rsp), %xmm0
    addss   -40(%rsp), %xmm0
    movss   %xmm0, -56(%rsp)
    movss   -28(%rsp), %xmm0
    addss   -36(%rsp), %xmm0
    movss   %xmm0, -52(%rsp)
    movlps  -56(%rsp), %xmm0
    ret

这仍然是低效的,因为它执行元素相加。


xmm registers are 128-bit wide,为了充分利用它们,代码需要在 128 位单元上运行。

在 3D 图形中,坐标通常是 4 元素浮点向量 {x, y, z, w},这使得 xmm 寄存器非常适合。例如:

typedef float v4f __attribute__((vector_size(16)));

v4f add(v4f u, v4f v) {
    return u + v;
}

这将为函数add 生成以下程序集:

add(float __vector(4), float __vector(4)):
    addps   %xmm1, %xmm0
    ret

【讨论】:

  • 对于我想要的应用程序,我只需要 2D 向量和浮点类型就足够了。但是,您的示例可以理解(并且可能克服)该问题。非常感谢你! F
  • 我想知道在哪里可以找到低级指令参考。例如,只需一条指令即可计算 u*v 的总和,并计算平方根,例如 a=sqrt(u[0]*v[0]+u[1]*v[1] +...)?
  • 什么废话?那个asm是垃圾。很难相信它实际上来自 gcc 5.4 -O3。两个加法都是用标量单独完成的。 4 个单独的存储到堆栈,以及一个存储转发停顿,用于在单独的 MOVSS 存储之后使用 MOVLPS 加载结果。 (而且 MOVLPS 对目标寄存器有错误的依赖性;MOVSD 会更快。)
  • 最佳值类似于movq %xmm0, %xmm0 / movq %xmm1, %xmm1 或类似于零高半部分,然后是addps %xmm1, %xmm0 / ret。你可以用内在函数编写类似的东西。
  • 它不能忽略它们,它必须将它们归零,因为 ABI 允许它们在输入时成为垃圾,从而导致虚假的 FP 异常或非规范化减速。但是在将它们归零之后,是的,它可以做 clang 不安全的事情(参见问题上的 cmets)。
猜你喜欢
  • 2016-12-28
  • 1970-01-01
  • 2011-11-30
  • 2010-11-04
  • 2023-03-23
  • 2013-06-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多