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?
假设您只有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)
注意:_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就可以轻松搞定每一半的其他三个元素了。(或者movshdup与movhlps具有较短的编码:没有直接的字节)。
7 shuffle uops 与 1 存储和 7 加载 uops 相比值得考虑,但如果您无论如何都要为函数调用溢出向量,则不是。
您在 Windows 上,其中 xmm6-15 是呼叫保留的(只有 low128;ymm6-15 的上半部分是呼叫破坏的)。这是开始的另一个原因vextractf128。
在 SysV ABI 中,所有 xmm / ymm / zmm 寄存器都被调用破坏,因此每个print()函数都需要溢出/重新加载。唯一明智的做法是存储到内存并print使用原始向量调用(即打印低元素,因为它将忽略寄存器的其余部分)。然后movss xmm0, [rsp+4]调用print第二个元素,依此类推。
将所有 8 个浮点数很好地解压缩为 8 个向量 reg 对您没有好处,因为无论如何在第一个函数调用之前它们都必须单独溢出!
| 归档时间: |
|
| 查看次数: |
4835 次 |
| 最近记录: |