【发布时间】:2020-01-15 15:14:46
【问题描述】:
【问题讨论】:
【问题讨论】:
由于您没有指定,我假设意图是使用 SSE 内在函数。我还假设至少 SSE2 可用,因为这基本上是 x86-64 设置的基线……
执行上述操作的一种非常直接的方法是通过_mm_move_epi64() 简单地复制输入的低 64 位并将高 64 位归零
__m128 lo2(__m128 x)
{
return _mm_castsi128_ps(_mm_move_epi64(_mm_castps_si128(x)));
}
并使用_mm_move_sd()复制输入的高64位并从零开始复制低64位
__m128 hi2(__m128 x)
{
return _mm_castpd_ps(_mm_move_sd(_mm_castps_pd(x), _mm_setzero_pd()));
}
工作示例here
【讨论】:
lo2 中使用_mm_move_epi64(x),在hi2 中使用_mm_move_sd(x, _mm_setzero_pd())?
_mm_blend_ps 和 _mm_setzero_ps;与movsd 相比,blendps 可以在 Intel CPU 中更多的执行端口上运行。出于某些原因,英特尔 CPU 将 movsd reg,reg 解码为仅在端口 5 上运行,就像随机播放一样。 agner.org/optimize 和 uops.info。 (或者当然andps/andps也可以释放端口5的压力,但代价是常数)。但是是的,+1、movq 和 movsd 内部函数应该可以高效编译。
_mm_movelh_ps() 和 _mm_movehl_ps() 怎么样,结合全零寄存器,完全符合您的要求?
#include <iostream>
#include <x86intrin.h>
void print_vec(__m128 a) {
alignas(16) float res[4];
_mm_store_ps(res, a);
std::cout << res[0] << '\t' << res[1] << '\t' << res[2] << '\t' << res[3]
<< '\n';
}
int main() {
__m128 vec = _mm_set_ps(4.0f, 3.0f, 2.0f, 1.0f);
__m128 lo = _mm_movelh_ps(vec, _mm_setzero_ps());
__m128 hi = _mm_movehl_ps(vec, _mm_setzero_ps());
std::cout << "Orig:\t";
print_vec(vec);
std::cout << "Lower:\t";
print_vec(lo);
std::cout << "Upper:\t";
print_vec(hi);
return 0;
}
编译并运行它会产生:
Orig: 1 2 3 4
Lower: 1 2 0 0
Upper: 0 0 3 4
【讨论】:
movq xmm0, xmm1 零扩展在 3 个向量 ALU 端口中的任何一个上运行的低半部分)。此外,movq 具有复制到新寄存器的优势,但您的方式需要额外的movaps 以避免破坏vec。 (因为稍后需要将另一半归零)。
movhlps 将低半部分归零与 movsd 将低半部分归零一样有效(来自 @Michael Kenzel 的回答)。并节省 1 字节的代码大小,因为它是 SSE1 ps 而不是 SSE2 pd。
movq xmm,xmm 在 Intel 上是 0.33c 延迟,可以追溯到 Core2,而在 Zen uops.info/html-instr/MOVQ_0F7E_XMM_XMM.html 上是 0.25
movq。 (在 Nehalem 上,如果延迟遇到瓶颈,来自 FP->int->FP 的旁路延迟可能会使 movq 成为一个糟糕的选择。没有其他 CPU 的旁路延迟高达 2 个周期。但在这种情况下,对于特定于 Nehalem您希望 SSE4.1 insertps 复制和归零而不需要归零寄存器的版本。