SSE asm具有SQRTPS命令。
SQRTPS命令有两个版本:
SQRTPS xmm1, xmm2
SQRTPS xmm1, m128
gcc / clang / vs(所有)编译器具有辅助函数_mm_sqrt_ps
。
但_mm_sqrt_ps
只适用于预加载的xmm(使用_mm_set_ps / _mm_load_ps)。
从Visual Studio中,例如: http://msdn.microsoft.com/en-us/library/vstudio/8z67bwwk%28v=vs.100%29.aspx
我的期望:
__attribute__((aligned(16))) float data[4];
__attribute__((aligned(16))) float result[4];
asm{
sqrtps xmm0, data // DIRECTLY FROM MEMORY
movaps result, xmm0
}
我拥有的(在C中):
__attribute__((aligned(16))) float data[4];
__attribute__((aligned(16))) float result[4];
auto xmm = _mm_load_ps(&data) // or _mm_set_ps
xmm = _mm_sqrt_ps(xmm);
_mm_store_ps(&result[0], xmm);
(在asm中):
movaps xmm1, data
sqrtps xmm0, xmm1 // FROM REGISTER
movaps result, xmm0
换句话说,我想看到这样的事情:
__attribute__((aligned(16))) float data[4];
__attribute__((aligned(16))) float result[4];
auto xmm = _mm_sqrt_ps(data); // DIRECTLY FROM MEMORY, no need to load (because there is such instruction)
_mm_store_ps(&result[0], xmm);
答案 0 :(得分:2)
快速研究:我制作了以下文件,名为mysqrt.cpp
:
#include <pmmintrin.h>
extern "C" __m128 MySqrt(__m128* a) {
return _mm_sqrt_ps(a[1]);
}
尝试gcc,即g++4.8 -msse3 -O3 -S mysqrt.cpp && cat mysqrt.s
:
_MySqrt:
LFB526:
sqrtps 16(%rdi), %xmm0
ret
Clang(clang++3.6 -msse3 -O3 -S mysqrt.cpp && cat mysqrt.s
):
_MySqrt: ## @MySqrt
.cfi_startproc
## BB#0: ## %entry
pushq %rbp
Ltmp0:
.cfi_def_cfa_offset 16
Ltmp1:
.cfi_offset %rbp, -16
movq %rsp, %rbp
Ltmp2:
.cfi_def_cfa_register %rbp
sqrtps 16(%rdi), %xmm0
popq %rbp
retq
不了解VS,但至少gcc和clang似乎都会产生sqrtps
的内存版本。
更新功能使用示例:
#include <iostream>
#include <pmmintrin.h>
extern "C" __m128 MySqrt(__m128* a);
int main() {
__m128 x[2];
x[1] = _mm_set_ps1(4);
__m128 y = MySqrt(x);
std::cout << y[0] << std::endl;
}
// output:
2
更新2:关于您的代码,您应该这样做:
auto xmm = _mm_sqrt_ps(*reinterpret_cast<__m128*>(data));
当然风险自负,您应该保证data
包含有效的__m128
并且已经正确对齐。
答案 1 :(得分:1)
我认为你误解了原语_mm_sqrt_ps(__m128)
提供的界面。这里的参数类型可以是存储器或寄存器中的变量保持。扩展类型__m128
的行为与任何普通的内置类型相同,例如double
,并且不绑定到xmm寄存器,但也可以存储在内存中。
编辑除非您使用asm
,否则编译器会确定变量是否以及何时加载到寄存器中或保留在内存中。因此,在以下代码段中
__m128 foo(const __m128 x, const __m128*y, std::size_t n)
{
__m128 result = _mm_set_ps(1.0);
while(n--)
result = _mm_mul_ps(result,_mm_add_ps(x,_mm_sqrt_ps(*y++)));
return result;
}
由编译器决定哪些变量存储在寄存器中。我认为编译器会将x
和result
放入xmm寄存器,但直接从内存中获取*y
。
答案 2 :(得分:0)
你的问题的答案是你无法控制这一点,至少对于对齐的负载,与内在函数。由编译器决定它是否使用SQRTPS xmm1,xmm2或SQRTPS xmm1,m128。如果你想100%确定,那么你必须在汇编中写它。在我看来,这是内在函数的缺陷之一(至少目前正在实施)。
有些代码可以帮助解释这一点。
我们可以使用GCC(64位-O3)使用对齐和未对齐的加载生成两个版本
float x[4], y[4]
__m128 x4 = _mm_loadu_ps(x);
__m128 y4 = _mm_sqrt_ps(x4);
_mm_storeu_ps(y,y4);
这给出了(使用英特尔语法)
movups xmm0, XMMWORD PTR [rdx]
sqrtps xmm0, xmm0
但是,如果我们进行对齐加载,我们会得到另一种形式
float x[4], y[4]
__m128 x4 = _mm_load_ps(x);
__m128 y4 = _mm_sqrt_ps(x4);
_mm_storeu_ps(y,y4);
这将load和square root组合成一条指令
sqrtps xmm0, XMMWORD PTR [rax]
大多数人会说“信任编译器”。我不同意。如果您正在使用内在函数,那么应该假设您知道自己在做什么而不是编译器。下面是一个示例difference-in-performance-between-msvc-and-gcc-for-highly-optimized-matrix-multp,其中GCC选择了一个表单,MSVC选择了另一个表单(用于乘法而不是sqrt),它在性能上有所不同。
所以再一次,如果你使用对齐的加载,你只能祈祷编译器做你想要的。然后可能在下一个版本的编译器上做了不同的事情......