为什么_mm_extract_ps返回int而不是float?
float从C中的XMM寄存器读取单个数据的正确方法是什么?
或者更确切地说,一种不同的方式是:与_mm_set_ps指令相反的是什么?
dou*_*536 18
没有一个答案似乎真正回答了这个问题,为什么它会回归int.
原因是,extractps指令实际上将向量的一个组件复制到通用寄存器.它返回一个int似乎很愚蠢,但这就是实际发生的事情 - 原始浮点值最终出现在一个通用寄存器中(它保存整数).
如果您的编译器配置为为所有浮点运算生成SSE,那么将值"提取"到寄存器的最接近的事情是将值移动到向量的低分量中,然后将其转换为标量浮点数.这应该导致向量的组件保留在SSE寄存器中:
/* returns the second component of the vector */
float foo(__m128 b)
{
return _mm_cvtss_f32(_mm_shuffle_ps(b, b, _MM_SHUFFLE(0, 0, 0, 2)));
}
Run Code Online (Sandbox Code Playgroud)
该_mm_cvtss_f32征是免费的,它不会产生指令,它不仅使编译器重新诠释XMM寄存器作为float这样就可以归还如此.
将_mm_shuffle_ps所需值获取到最低组件中.该_MM_SHUFFLE宏生成用于将所得的立即操作数shufps指令.
所述2在31 0寄存器(从开始第三32位分量,在存储器中的顺序),并将其放置:在示例获取从127位95:64浮子寄存器(开头的0分量,在存储器订购).
生成的代码很可能会自然地在寄存器中返回值,就像任何其他浮点值返回一样,没有低效写入内存并将其读回.
如果您生成的代码使用x87 FPU作为浮点(对于未经SSE优化的普通C代码),这可能会导致生成低效的代码 - 编译器可能会存储SSE向量的组件然后用于fld将其读回x87寄存器堆栈.通常,64位平台不使用x87(它们对所有浮点使用SSE,主要是标量指令,除非编译器正在向量化).
我应该补充一点,我总是使用C++,所以我不确定是否通过值或C中的指针传递__m128更有效.在C++中我会使用a const __m128 &,这种代码会在头文件中,所以编译器可以内联.
令人困惑的int _mm_extract_ps()是,不是float从向量中获取标量元素. 内在函数不公开指令的内存目标形式(这可能对此有用).这并不是内在函数无法直接表达指令有用的所有内容的唯一情况.:(
gcc和clang知道asm指令是如何工作的,并且在编译其他shuffle时会以这种方式使用它; 对_mm_extract_ps结果进行打字float通常会导致gcc(extractps eax, xmm0, 2/ mov [mem], eax)出现可怕的asm .
如果您认为将CPU的FP域中_mm_extract_ps的IEEE 754二进制32浮点模式提取到整数域(作为C标量int),而不是使用整数向量操作来操纵FP位模式,则该名称是有意义的. 根据我对gcc,clang和icc的测试(见下文),这是唯一的"便携式"用例,可以_mm_extract_ps在所有编译器中编译成好的asm.任何其他东西只是一个特定于编译器的黑客来获取你想要的asm.
相应的asm指令是EXTRACTPS r/m32, xmm, imm8.请注意,目标可以是内存或整数寄存器,但不是另一个XMM寄存器.它是FP的等价物PEXTRD r/m32, xmm, imm8(也在SSE4.1中),其中整数寄存器目的地形式更明显有用.EXTRACTPS不是相反的INSERTPS xmm1, xmm2/m32, imm8.
也许与PEXTRD的这种相似性使得内部实现更简单而不会损害提取到内存的用例(对于asm,而不是内在函数),或者英特尔的SSE4.1设计者认为它实际上比非作为非-destructive FP-domain copy-and-shuffle(x86在没有AVX的情况下严重缺乏).有FP-vector指令有一个XMM源和一个memory-or-xmm目的地,比如说MOVSS xmm2/m32, xmm,所以这种指令不是新的.有趣的事实:PEXTRD和EXTRACTPS的操作码只在最后一位有所不同.
在汇编中,标量float只是XMM寄存器的低位元素(或内存中的4个字节).对于像ADDSS这样的指令,XMM的上层元素甚至不必归零,而不会引发任何额外的FP异常.在调用XMM寄存器中传递/返回FP args的约定(例如所有通常的x86-64 ABI)时,float foo(float a)必须假设XMM0的上层元素在入口处保留垃圾,但在返回时可能在XMM0的高元素中留下垃圾.(更多信息)
正如@doug指出的那样,可以使用其他shuffle指令将向量的float元素放入xmm寄存器的底部. 这已经是SSE1/SSE2中一个主要解决的问题了,看来EXTRACTPS和INSERTPS并没有试图解决寄存器操作数问题.
当标量浮点数已经存在于寄存器中时,SSE4.1 INSERTPS xmm1, xmm2/m32, imm8是编译器实现的最佳方式之一,_mm_set_ss(function_arg)它们不能/不优化去除上层元素的归零.(除了clang之外的编译器大部分时间都是这样).该链接问题还进一步讨论了内在函数无法公开加载或存储指令版本,如EXTRACTPS,INSERTPS和PMOVZX,其内存操作数小于128b(因此即使没有AVX也不需要对齐).编写安全代码是不可能的,这些代码可以像在asm中那样有效地编译.
如果没有AVX 3操作数SHUFPS,x86不能像整数PSHUFD那样提供一种完全有效且通用的方式来复制和混洗FP向量. SHUFPS是一种不同的野兽,除非与src = dst一起使用.保留原始版本需要一个MOVAPS,它在IvyBridge之前花费了CPU上的uop和延迟,并且总是花费代码大小.在FP指令之间使用PSHUFD会导致延迟(旁路延迟).(有关一些技巧,请参阅此水平和答案,例如使用SSE3 MOVSHDUP).
SSE4.1 INSERTPS可以将一个元素提取到一个单独的寄存器中,但是即使更换了所有原始值,AFAIK仍然依赖于目标的先前值.像这样的错误依赖对于无序执行是不利的. xor- zeroing寄存器作为INSERTPS的目的地仍然是2 uop,并且在SSE4.1 CPU上具有比MOVAPS + SHUFPS更低的延迟,而没有用于零延迟MOVAPS的移动消除(仅Penryn,Nehalem,Sandybridge.如果你是Silvermont包括低功耗CPU).但是代码大小稍微差一点.
使用_mm_extract_ps然后将结果打字回到浮动(如当前接受的答案及其评论中所建议的)是一个坏主意.您的代码很容易在gcc或icc上编译成可怕的东西(如EXTRACTPS到内存,然后加载回XMM寄存器).Clang似乎对脑死亡行为免疫,并通过自己选择的随机指令(包括适当使用EXTRACTPS)进行常规的随机编译.
我在Godbolt编译器资源管理器上用gcc5.4 -O3 -msse4.1 -mtune=haswell,clang3.8.1和icc17 尝试了这些例子.我使用的是C模式,而不是C++,但GNU C++允许使用基于联合的类型惩罚作为ISO C++的扩展.类型惩罚的指针式转换违反了C99和C++中的严格别名,即使使用GNU扩展也是如此.
#include <immintrin.h>
// gcc:bad clang:good icc:good
void extr_unsafe_ptrcast(__m128 v, float *p) {
// violates strict aliasing
*(int*)p = _mm_extract_ps(v, 2);
}
gcc: # others extractps with a memory dest
extractps eax, xmm0, 2
mov DWORD PTR [rdi], eax
ret
// gcc:good clang:good icc:bad
void extr_pun(__m128 v, float *p) {
// union type punning is safe in C99 (and GNU C and GNU C++)
union floatpun { int i; float f; } fp;
fp.i = _mm_extract_ps(v, 2);
*p = fp.f; // compiles to an extractps straight to memory
}
icc:
vextractps eax, xmm0, 2
mov DWORD PTR [rdi], eax
ret
// gcc:good clang:good icc:horrible
void extr_gnu(__m128 v, float *p) {
// gcc uses extractps with a memory dest, icc does extr_store
*p = v[2];
}
gcc/clang:
extractps DWORD PTR [rdi], xmm0, 2
icc:
vmovups XMMWORD PTR [-24+rsp], xmm0
mov eax, DWORD PTR [-16+rsp] # reload from red-zone tmp buffer
mov DWORD PTR [rdi], eax
// gcc:good clang:good icc:poor
void extr_shuf(__m128 v, float *p) {
__m128 e2 = _mm_shuffle_ps(v,v, 2);
*p = _mm_cvtss_f32(e2); // gcc uses extractps
}
icc: (others: extractps right to memory)
vshufps xmm1, xmm0, xmm0, 2
vmovss DWORD PTR [rdi], xmm1
Run Code Online (Sandbox Code Playgroud)
当你想在xmm寄存器中得到最终结果时,由编译器来优化你的提取物并做一些完全不同的事情.Gcc和clang都取得了成功,但ICC却没有.
// gcc:good clang:good icc:bad
float ret_pun(__m128 v) {
union floatpun { int i; float f; } fp;
fp.i = _mm_extract_ps(v, 2);
return fp.f;
}
gcc:
unpckhps xmm0, xmm0
clang:
shufpd xmm0, xmm0, 1
icc17:
vextractps DWORD PTR [-8+rsp], xmm0, 2
vmovss xmm0, DWORD PTR [-8+rsp]
Run Code Online (Sandbox Code Playgroud)
请注意,icc也表现不佳extr_pun,所以它不喜欢基于联合的类型惩罚.
这里明显的赢家是"手动"进行随机播放_mm_shuffle_ps(v,v, 2)和使用_mm_cvtss_f32. 我们从寄存器和内存目标的每个编译器获得了最佳代码,但ICC未能将exTRACTPS用于memory-dest情况.使用AVX,SHUFPS +独立存储在Intel CPU上仍然只有2 uop,只是更大的代码大小,需要一个tmp寄存器.但是,如果没有AVX,它将花费MOVAPS来破坏原始载体:/
根据Agner Fog的指令表,除了Nehalem之外的所有Intel CPU都实现了具有多个uop的PEXTRD和EXTRACTPS的寄存器目标版本:通常只是一个shuffle uop +一个MOVD uop,用于将数据从向量域移动到gp-integer.Nehalem寄存器 - 目的地EXTRACTPS对于端口5是1 uop,具有1 + 2个周期延迟(1 +旁路延迟).
我不知道为什么他们设法将EXTRACTPS实现为单个uop而不是PEXTRD(2 uop,并以2 + 1周期延迟运行).Nehalem MOVD为1 uop(并可在任何ALU端口上运行),具有1 + 1周期延迟.(+1表示vec-int和通用整数寄存器之间的旁路延迟,我认为).
Nehalem关注矢量FP与整数域的关注; SnB系列CPU在域之间具有较小(有时为零)的旁路延迟延迟.
在Nehalem上,PEXTRD和EXTRACTPS的memory-dest版本都是2 uop.
在Broadwell以及之后,内存目的地EXTRACTPS和PEXTRD是2 uop,但是在Sandybridge上通过Haswell,内存目标EXTRACTPS是3 uops.内存目的地PEXTRD在除Sandybridge之外的所有东西上都是2 uops,它是3.这看起来很奇怪,而Agner Fog的表有时会出错,但它有可能.微融合不适用于某些微体系结构的某些指令.
如果任何一条指令对于任何重要的事情(例如内部循环内部)都非常有用,那么CPU设计人员就会构建执行单元,可以将整个事务作为一个uop(或者对于memory-dest来说可能是2).但这可能需要内部uop格式的更多位(Sandybridge简化).
有趣的事实:_mm_extract_epi32(vec, 0)编译(在大多数编译器上)movd eax, xmm0比更短更快pextrd eax, xmm0, 0.
有趣的是,它们在Nehalem上表现不同(它关注矢量FP与整数域很多,并且在Penryn(45nm Core2)中引入SSE4.1后很快就出现了).具有寄存器目的地的EXTRACTPS是1 uop,具有1 + 2个周期延迟(来自FP和整数域之间的旁路延迟的+2).PEXTRD是2 uops,并以2 + 1周期延迟运行.
从MSDN 文档中,我相信您可以将结果转换为浮点数。
请注意,从他们的示例中,0xc0a40000 值相当于 -5.125 (a.m128_f32[1])。
更新:我强烈推荐@doug65536和@PeterCordes(如下)的答案来代替我的答案,这显然会在许多编译器上生成性能不佳的代码。
| 归档时间: |
|
| 查看次数: |
4911 次 |
| 最近记录: |