【问题标题】:Why can't gcc vectorize this straight-line code?为什么 gcc 不能向量化这条直线代码?
【发布时间】:2014-09-01 03:35:36
【问题描述】:

我有以下代码,这似乎是 SLP 的完美候选者:

struct vector {
  double x, y, z;
} __attribute__((aligned(16)));

int
slp_test(struct vector *x0, struct vector *n)
{
  double t = -x0->z/n->z;
  double u = x0->x + t*n->x;
  double v = x0->y + t*n->y;
  return t >= 0.0 && u >= 0.0 && v >= 0.0 && u + v <= 1.0;
}

uv 的计算似乎很容易矢量化,x0n 应该足够好地对齐。但在 x86-64 -O3 上,gcc 4.9.0 会生成以下代码:

    movsd   .LC0(%rip), %xmm1
    movsd   16(%rdi), %xmm0
    movsd   (%rdi), %xmm2
    xorpd   %xmm1, %xmm0
    movsd   (%rsi), %xmm1
    pxor    %xmm3, %xmm3
    divsd   16(%rsi), %xmm0 ; t = x0->z/n->z
    mulsd   %xmm0, %xmm1    ; t*n->x
    addsd   %xmm1, %xmm2    ; u = x0->x + t*n->x
    movsd   8(%rsi), %xmm1
    mulsd   %xmm0, %xmm1    ; t*n->y
    ucomisd %xmm3, %xmm2
    addsd   8(%rdi), %xmm1  ; v = x0->y + t*n->y
    setae   %dl
    ucomisd %xmm3, %xmm1
    setae   %al
    testb   %al, %dl
    je      .L3
    ucomisd %xmm3, %xmm0
    jb      .L3
    addsd   %xmm2, %xmm1
    movsd   .LC2(%rip), %xmm0
    xorl    %eax, %eax
    ucomisd %xmm1, %xmm0
    setae   %al
    ret
.L3:
    xorl    %eax, %eax
    ret

gcc 怎么不使用mulpdaddpd 而不是mulsds 和addsds 这两个?我用-fopt-info-all-vec 试图找出原因,它抱怨对齐(@9​​87654321@):

slp-test.c:8:17: note: === vect_analyze_data_refs_alignment ===
slp-test.c:8:17: note: vect_compute_data_ref_alignment:
slp-test.c:8:17: note: misalign = 0 bytes of ref x0_3(D)->z
slp-test.c:8:17: note: vect_compute_data_ref_alignment:
slp-test.c:8:17: note: misalign = 0 bytes of ref n_6(D)->z
slp-test.c:8:17: note: vect_compute_data_ref_alignment:
slp-test.c:8:17: note: misalign = 0 bytes of ref x0_3(D)->x
slp-test.c:8:17: note: vect_compute_data_ref_alignment:
slp-test.c:8:17: note: misalign = 0 bytes of ref n_6(D)->x
slp-test.c:8:17: note: vect_compute_data_ref_alignment:
slp-test.c:8:17: note: misalign = 8 bytes of ref x0_3(D)->y
slp-test.c:8:17: note: vect_compute_data_ref_alignment:
slp-test.c:8:17: note: misalign = 8 bytes of ref n_6(D)->y
slp-test.c:8:17: note: === vect_analyze_slp ===
slp-test.c:8:17: note: Failed to SLP the basic block.
slp-test.c:8:17: note: not vectorized: failed to find SLP opportunities in basic block.

除非我误解了__attribute__((aligned(16))),否则它应该能够强制对齐这些访问。有什么想法吗?

【问题讨论】:

  • 嗯...我得到test.c:1:16: warning: attribute 'aligned' is ignored, place it after "struct" to apply attribute to type declaration [-Wignored-attributes]
  • 这是铿锵警告吗?我的 gcc 似乎不支持它。无论如何,将__attribute__((aligned(16))) 移动到正确的位置似乎并没有改变任何东西。
  • 矢量化它没有任何好处。 uv 之后需要单独访问。执行此操作所需的解包开销超过了向量化单个算术运算的好处。
  • 嗯...我对矢量化不是很了解,但是您确实只使用一对标量。没有用于打包更多标量的循环
  • @Mysticial u &gt;= 0.0 &amp;&amp; v &gt;= 0.0 比较也应该是可矢量化的,因此仅对并不总是执行的 u + v &lt;= 1.0 比较需要解包。

标签: c gcc vectorization


【解决方案1】:

此代码不会从矢量化中获得太多好处,请记住,cpus 能够在单个周期内执行多条指令。 例如,在 Nehalem 乘法/加法上,延迟为 4,倒数吞吐量为 1,因此在理想情况下,它应该能够在 4 个周期内计算 4 个这些指令。这里至少 2 应该是可能的。 这已经意味着即使向量寄存器已经完美归档,使用打包指令也不会获得任何收益。

编辑:我没有意识到可以一次性加载数据,所以设置成本可以忽略不计

为了填充它们,您可能已经需要一个高低 mov 指令,这将比少数打包指令稍后获得的成本更高。 (在 Nehalem 上,mov[hl]pd 的延迟约为 5,而 movsd 的延迟为 2)

比较也不能通过向量化获利,因为您需要将打包的比较解包回普通寄存器,这是一项非常昂贵的操作。 此外,编译器不知道分支的概率,它必须假设第一个比较总是会短路其余的,所以并行执行任何操作都是有害的。

编辑:使用 sse4 ptest 虽然它可能是有利可图的

这里的瓶颈也可能是不可矢量化的除法。 您可能最好尝试一次矢量化 2 个结构的操作,而不是尝试矢量化一个结构中的操作。

【讨论】:

  • 如果你花更多时间做矢量化算术,它仍然不会矢量化它。 (说 vector-mul by t,然后 vector-add x0->(x,y),thne vector-mul by t,然后 vector-add n->(x,y)。)Clang 确实将它向量化。
  • 查看我的最新评论,矢量化确实有助于提高性能。它们可以用movapd填充,比较可以用cmppdptest或类似的。当然 gcc 没有意识到这一点是合理的。
  • 有趣,我确实忽略了你可以做一次加载而忘记了 ptest,所以它可能可以节省你的周期。我有点惊讶你可以节省 25%。 fwiw ICC 也不会对其进行矢量化,即使允许使用 sse4,clang 也不会使用 ptest
  • 经过一些更仔细的基准测试之后,似乎我错了;对于true 情况,每个版本的代码(GCC、Clang、显式矢量化代码和我的手卷矢量化程序集)恰好需要 13 个周期,而对于 false 情况,大约需要 9 个周期。此外,如果truefalse 混合使用,gcc 的版本比其他版本快得多。
猜你喜欢
  • 2019-06-06
  • 2018-11-01
  • 2013-08-08
  • 2014-12-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多