使用英特尔编译器内在函数,给定一个128位寄存器,打包8个16位元素,如何从寄存器中(廉价地)访问任意元素,以便随后使用_mm_cvtepi8_epi64
(符号扩展为2)位于寄存器低16位的位元素到两个64位元素?)
我会解释为什么我要问:
0x0
和0xffff ffff ffff ffff
打包两个四字(64位)。注意:输入缓冲区的值0x0
和0xff
可以更改为最有用的值,前提是在总和之前屏蔽效果仍然存在。
从我的问题可以明显看出,我目前的计划如下,在输入缓冲区中流式传输:
谢谢, 阿萨夫
答案 0 :(得分:3)
而不是问题本身的切线,更多地填写评论中的一些信息,因为评论部分本身太小而无法容纳这个( sic!):
至少gcc可以处理以下代码:
#include <smmintrin.h>
extern int fumble(__m128i x);
int main(int argc, char **argv)
{
__m128i foo;
__m128i* bar = (__m128i*)argv;
foo = _mm_cvtepi8_epi64(*bar);
return fumble(foo);
}
将其转换为以下程序集:
Disassembly of section .text.startup: 0000000000000000 : 0: 66 0f 38 22 06 pmovsxbq (%rsi),%xmm0 5: e9 XX XX XX XX jmpq .....
这意味着内在函数不需要以内存参数形式出现 - 编译器透明地处理取消引用mem参数并尽可能使用相应的mem-operand指令。 ICC也这样做。我没有Windows机器/ Visual C ++来测试MSVC是否也这样做,但我希望它能够实现。
答案 1 :(得分:3)
每个字节都是整个double的掩码,所以PMOVSXBQ
完全符合我们的需要:从m16
指针加载两个字节,并将它们符号扩展为xmm寄存器的两个64位(qword)半部分。
# UNTESTED CODE
# (loop setup stuff)
# RSI: double pointer
# RDI: mask pointer
# RCX: loop conter = mask byte-count
add rdi, rcx
lea rsi, [rsi + rcx*8] ; sizeof(double) = 8
neg rcx ; point to the end and count up
XORPS xmm0, xmm0 ; clear accumulator
; for real use: use multiple accumulators
; to hide ADDPD latency
ALIGN 16
.loop:
PMOVSXBQ XMM1, [RDI + RCX]
ANDPD XMM1, [RSI + RCX * 8]
ADDPD XMM0, XMM1
add RCX, 2 ; 2 bytes / doubles per iter
jl .loop
MOVHLPS XMM1, XMM0 ; combine the two parallel sums
ADDPD XMM0, XMM1
ret
对于实际使用,请使用多个累加器。另请参阅Micro fusion and addressing modes re:索引寻址模式。
用内在函数写这个应该很容易。正如其他人所指出的那样,只需使用解除引用的指针作为内在函数的args。
要回答问题的其他部分,请关于如何移动数据以排列PMOVSX
:
在Sandybridge和之后,使用RAM中的PMOVSXBQ可能很好。在每个周期不能处理两个负载的早期CPU上,一次加载16B掩码数据,并一次用PSRLDQ xmm1, 2
将其移位2个字节,将2个字节的掩码数据放入低2寄存器的字节。或者也许PUNPCKHQDQ
或PSHUFD
通过将高64移动到另一个reg的低64来获得两个依赖链。您必须检查哪个端口使用哪个端口(shift与shuffle / extract),并查看与PMOVSX
和ADDPD
之间的冲突较少。
punpck
和pshufd
都在SnB上使用p1 / p5,pmovsx
也是如此。 addpd
只能在p1上运行。 andpd
只能在p5上运行。嗯,也许PAND
会更好,因为它可以在p0(和p1 / p5)上运行。否则循环中的任何内容都不会使用执行端口0.如果将数据从整数移动到fp域会有延迟惩罚,如果我们使用PMOVSX
则不可避免,因为这将获得掩码int域中的数据。最好使用更多的累加器来使循环长于最长的依赖链。但要保持在28uops左右以适应循环缓冲区,以确保每个周期可以发出4个uop。
更多关于优化整个事情: 不需要对齐循环,因为在nehalem以后它将适合循环缓冲区。
您应该将循环展开2或4,因为前Haswell Intel CPU没有足够的执行单元来在一个周期内处理所有4个(融合的)微操作。 (3个向量和一个融合add
/ jl
。两个负载与它们所属的向量uops融合。)Sandybridge以后可以在每个周期执行两个加载,因此每个周期一次迭代是可行的,除了循环开销。
哦,ADDPD
的延迟为3个周期。因此,您需要展开并使用多个累加器,以避免循环携带的依赖关系链成为瓶颈。可能会以4为单位展开,然后在最后总结4个累加器。即使使用内在函数,您也必须在源代码中执行此操作,因为这会改变FP数学运算的顺序,因此编译器在展开时可能不愿意这样做。
因此,每个展开的4个循环将花费4个时钟周期,加上1个uop用于循环开销。在Nehalem,你有一个很小的循环缓存但没有uop缓存,展开可能意味着你必须开始关心解码器吞吐量。然而,在预沙桥上,每个时钟一个负载可能会成为瓶颈。
对于解码器吞吐量,您可以使用ANDPS
而不是ANDPD
,这需要少一个字节进行编码。 IDK,如果这会有所帮助。
将此扩展为256b ymm
寄存器需要AVX2才能实现最直接的实现(对于VPMOVSXBQ ymm
)。您可以通过执行两个VPMOVSXBQ xmm
并将它们与VINSERTF128
或其他内容组合来获得AVX的加速。
答案 2 :(得分:2)