如何从AVX寄存器中获取数据?

MSa*_*ers 11 c++ avx visual-c++ fma

使用MSVC 2013和AVX 1,我在寄存器中有8个浮点数:

__m256 foo = mm256_fmadd_ps(a,b,c);
Run Code Online (Sandbox Code Playgroud)

现在我要打电话inline void print(float) {...}给所有8个花车.看起来英特尔 AVX内置技术会让这个变得相当复杂:

print(_castu32_f32(_mm256_extract_epi32(foo, 0)));
print(_castu32_f32(_mm256_extract_epi32(foo, 1)));
print(_castu32_f32(_mm256_extract_epi32(foo, 2)));
// ...
Run Code Online (Sandbox Code Playgroud)

但MSVC甚至没有这两种内在函数.当然,我可以将值写回内存并从那里加载,但我怀疑在汇编级别没有必要溢出寄存器.

奖金问:我当然喜欢写作

for(int i = 0; i !=8; ++i) 
    print(_castu32_f32(_mm256_extract_epi32(foo, i)))
Run Code Online (Sandbox Code Playgroud)

但MSVC并不了解许多内在函数需要循环展开.如何在8x32浮点数上写一个循环__m256 foo

Pau*_*l R 6

假设您只有AVX(即没有AVX2),则可以执行以下操作:

float extract_float(const __m128 v, const int i)
{
    float x;
    _MM_EXTRACT_FLOAT(x, v, i);
    return x;
}

void print(const __m128 v)
{
    print(extract_float(v, 0));
    print(extract_float(v, 1));
    print(extract_float(v, 2));
    print(extract_float(v, 3));
}

void print(const __m256 v)
{
    print(_mm256_extractf128_ps(v, 0));
    print(_mm256_extractf128_ps(v, 1));
}
Run Code Online (Sandbox Code Playgroud)

但是我认为我可能只使用工会:

union U256f {
    __m256 v;
    float a[8];
};

void print(const __m256 v)
{
    const U256f u = { v };

    for (int i = 0; i < 8; ++i)
        print(u.a[i]);
}
Run Code Online (Sandbox Code Playgroud)


Pet*_*des 5

注意:_mm256_fmadd_ps不是 AVX1 的一部分。FMA3 有其自己的功能位,并且仅在 Intel 上与 Haswell 一起推出。AMD 推出了带打桩机的 FMA3(AVX1+FMA4+FMA3,没有 AVX2)。


在 asm 级别,如果要将 8 个 32 位元素放入整数寄存器,实际上存储到堆栈然后进行标量加载会更快。 pextrd是关于 SnB 系列和推土机系列的 2-uop 指令。(以及不支持 AVX 的 Nehalem 和 Silvermont)。

vextractf128+ 2x movd+ 6xpextrd不可怕的唯一 CPU是 AMD Jaguar。(便宜pextrd,只有一个装载端口。)(见Agner Fog 的 insn 表

宽对齐的存储可以转发到重叠的窄负载。(当然,您可以使用movd来获取低元素,因此您可以混合使用负载端口和 ALU 端口 uops)。


当然,您似乎是float通过使用整数提取来提取s,然后将其转换回浮点数。 那看起来很可怕。

您实际需要的是每个float在其自己的 xmm 寄存器的低元素中。 vextractf128显然是开始的方式,将元素 4 带到新的 xmm reg 的底部。那么6x AVXshufps就可以轻松搞定每一半的其他三个元素了。(或者movshdupmovhlps具有较短的编码:没有直接的字节)。

7 shuffle uops 与 1 存储和 7 加载 uops 相比值得考虑,但如果您无论如何都要为函数调用溢出向量,则不是。


ABI注意事项:

您在 Windows 上,其中 xmm6-15 是呼叫保留的(只有 low128;ymm6-15 的上半部分是呼叫破坏的)。这是开始的另一个原因vextractf128

在 SysV ABI 中,所有 xmm / ymm / zmm 寄存器都被调用破坏,因此每个print()函数都需要溢出/重新加载。唯一明智的做法是存储到内存并print使用原始向量调用(即打印低元素,因为它将忽略寄存器的其余部分)。然后movss xmm0, [rsp+4]调用print第二个元素,依此类推。

将所有 8 个浮点数很好地解压缩为 8 个向量 reg 对您没有好处,因为无论如何在第一个函数调用之前它们都必须单独溢出!