如何最大限度地减少使用标量 SIMD 内部函数的 SIMD 注册表的双重负载开销

how to minimize overhead loading double into simd regsiters working with scalar SIMD intrinsics

本文关键字:SIMD 内部函数 注册表 开销 负载 标量 最大限度      更新时间:2023-10-16

使用 gcc 7.2 在 godbolt.org 我可以看到以下代码在汇编程序中进行了非常优化的翻译。我看到 1 个负载、1 个添加和 1 个存储。

#include <immintrin.h>
__attribute__((alwaysinline)) double foo(double x, double y)
{
    return x+y;
}
void usefoo(double x, double *y, double *z)
{
    *z = foo(x, *y);
}

这导致:

usefoo(double, double*, double*):
   addsd xmm0, QWORD PTR [rdi]
   movsd QWORD PTR [rsi], xmm0
   ret

但是,如果我尝试使用下面的代码使用内部函数和模板来实现相同的目标,我可以看到增加了一些开销。特别是,指令的重点是什么:movq xmm0, xmm0

#include <immintrin.h>
__attribute__((alwaysinline)) double foo(double x, double y)
{
    return _mm_cvtsd_f64(_mm_add_sd(__m128d{x}, __m128d{y}));
}
void usefoo(double x, double *y, double *z)
{
    *z = foo(x, *y);
}

这导致:

usefoo(double, double*, double*):
  movq xmm1, QWORD PTR [rdi]
  movq xmm0, xmm0
  addsd xmm0, xmm1
  movlpd QWORD PTR [rsi], xmm0
  ret

如何使用标量内部函数实现与编译器以其他方式生成的代码等效的代码?

如果你想知道我为什么要这样做,请考虑用<=替换+:如果我写x<y编译器将结果转换为布尔值,而内部函数会将其保留为双位掩码。因此,对于我的用例,编写x<y不是一种选择。然而,使用+很简单,可以说明这个问题。

"无关"movq是清除__m128d中的第二个元素,正如列表初始化__m128d{x}所要求的那样。

当源操作数是 XMM 寄存器时,移动低四字;当目标操作数是 XMM 寄存器时,四字存储到寄存器的低四字,高四字清除到所有 0。

请记住,当提供的初始值设定项少于成员数量时,所有剩余成员都将进行值初始化(为零(。

我希望进行更高级别的优化,以确保从未使用第二个元素,并删除无关的指令。 另一方面,即使未使用,在加法操作期间也不允许捕获第二个值,显式清除它可能是确保它不会捕获的最安全方法。