假设我有一个指向内存的指针rsi
,我想将指向的 12 字节值加载到xmm0
. 我不在乎高 32 位会发生什么。有什么有效的方法来做到这一点?
(附带的问题:我想出的最好的方法涉及movlpd
“移动低压缩双精度浮点值”指令。有没有什么方法可以使该指令特定于浮点值?我不明白什么它是以这种方式记录的;当然它也应该适用于整数。)
如果 16 字节加载不会跨入另一个页面并出现错误,则使用movups
. 高 4 字节将是内存中的任何垃圾。导致您不关心的 4B 缓存未命中可能是一个问题,缓存行拆分也可能是一个问题。
否则使用movq
/ pinsrd
(SSE4.1),或其他方式进行两次加载 + 随机播放。 movq
+pinsrd
将是英特尔 SnB 系列 CPU 上的 3 个融合域微指令,因为pinsrd
不能进行微融合。(并且它的 ALU uop 需要 shuffle 端口(p5))。
另一种可能性: AVX VMASKMOVPS xmm1, xmm2, m128
。
根据与每个数据元素关联的掩码位(第一个 src 操作数的 MSB),有条件地将打包数据元素从第二个源操作数移动到目标操作数的相应数据元素中。
...如果该内存位置的相应掩码位为 0,则不会因引用任何内存位置而发生故障。
英特尔 Haswell:3 个融合域微指令(一个加载和两个洗牌 (p5))。4c 延迟,每 2c 吞吐量一个。
比较起来可能不是很好,尤其是。如果周围的代码必须洗牌。
您非常罕见的条件分支movups
在保证不会出错的任何时候使用,它也是快速路径上的 3 个融合域微指令,其中一个可以在端口 6 上运行(根本不与向量 ALU 竞争)。LEA 也不在关键路径上。
movlpd
可以安全地用于任何数据。对于代表浮点 NaN 或类似的数据,它永远不会出错或变慢。您只需使用 insn ref 手册中列出的说明以及非空的“SIMD 浮点异常”部分来担心这一点。例如addps
,可以生成“溢出、下溢、无效、精确、非正常”异常,但显示shufps
“无”。
Peter Cordes 的回答让我想起了页面,最后我只是检查一下我们是否有可能出错:
// We'd like to perform only a single load from memory, but there's no 96-bit
// load instruction and it's not necessarily safe to load the full 128 bits
// since this may read beyond the end of the buffer.
//
// However, observe that memory protection applies with granularity of at
// most 4 KiB (the smallest page size). If the full 16 bytes lies within a
// single 4 KiB page, then we're fine. If the 12 bytes we are to read
// straddles a page boundary, then we're also fine (because the next four
// bytes must lie in the second page, which we're already reading). The only
// time we're not guaranteed to be okay to read 16 bytes is if the 12 bytes
// we want to read lie near the end of one page, and some or all of the
// following four bytes lie within the next page.
//
// In other words, the only time there's a risk is when the pointer mod 4096
// is in the range [4081, 4085). This is <0.1% of addresses. Check for this
// and handle it specially.
//
// We perform the check by adding 15 and then checking for the range [0, 3).
lea rax, [rsi+15]
test eax, 0xffc
jz slow_read
// Hooray, we can load from memory just once.
movdqu xmm0, XMMWORD PTR [rsi]
done_reading:
[...]
slow_read:
movq xmm1, QWORD PTR [rsi]
pinsrd xmm1, DWORD PTR [rsi+8], 2
jmp done_reading
movss xmm0, [rdx+8] //; +8*8Bits = 64 Bits
pshufd xmm0, xmm0, 0x00 //; spreading it in every part
movlps xmm0, [rdx] //; overwriting the lower with 64 Bits
它在我的情况下适用于Float,不确定它是否适合您。