我知道有一个重复的指令(例如repnz
)。
我有一个情况,我有一个(8位)数组[7, 2, 3, 4, ..., 7, 0, 0, 0 ...]
(我在末尾有64个零字节)。我想加载第一个值(7)并与其余的值进行AND,直到有一个零:
7 & 2 == 2
2 & 3 == 2
2 & 4 == 0
所以我想在这里停下来,并有一个指针或索引3(数组[3] == 4)。我还需要结果变为零之前的值。
有没有一个rep指令或者SIMD指令我可以用来找到它?有没有什么聪明的C代码我可以写?目前我使用一个简单的while循环,我以导致结果变为零的索引和'previousResult'结束,这样我就可以使用结果为零之前的值(在上面的例子中为2)
1条答案
按热度按时间sy5wg1nm1#
如果长时间运行很常见,你可以使用与前缀和相同的模式来执行一些移位和AND,除了使用AND而不是加法。这与标量循环相比具有额外的设置和清理开销,但可以很好地扩展更大的大小并避免分支错误预测。
用
_mm_and_si128
替换_mm_add_epi8
。在前缀AND-scan中用pcmpeqb
查找第一个零,然后是通常的pmovmskb
/bsf
(或tzcnt
),如果前16个字节中有一个零。相关问答:
AVX-512有一些有趣的指令。
vpternlogd
可以执行3输入AND,但只能垂直执行,不能在一个向量内执行,因此您必须进行 Shuffle 设置。也许在前缀AND扫描中会有用。其他AVX-512 SIMD指令可能没有用。
vp2intersectd
(仅限https://felixcloutier.com/x86/vp2intersectd:vp2intersectq- Tiger Lake)查找元素之间的两两相等,而不是按位相交。同样,vpconflictd
只查找整数的精确匹配。像VGF2P8AFFINEQB
这样的GFNI指令可以做一些整洁的事情,但是如果正确的常量可以让你跨字节进行按位AND,IDK可能不会。我没有确切的数字,但长度1-5是非常受欢迎的,8和12是不受欢迎的,但并不罕见。我怀疑做一个SIMD加载+一些shuffles +得到索引0可能会比一个1字节的循环,当数据的长度是5或更少,这是所有的时间。所以我想我卡住了,或者必须找到一种新的方式来表示数据。
是的,对于短长度,SIMD可能会更慢,只要分支在短版本中被正确预测。也许剥离前3或4次迭代并使用一些
cmov
可以减少分支预测错误。(对于吞吐量,如果这个线程与超线程共享一个核心,分支未命中并不是那么糟糕,尽管它在被检测到之前仍然浪费了一些前端带宽。)也许一些整数SWAR可以创建一些ILP(指令级并行性),比如首先用
test al, dl/jnz
检查x & a[1]
,同时一次在64位上执行a & (a>>8)
?然后是and al, dl
或dl holds a[1]&a[2]
的东西。展开两个,这样每次迭代都不执行一个分支可能是好的。除了编译器可以做的之外,通常不值得手工优化asm,但是x86部分寄存器在较宽移位的低字节上执行字节大小的AND可能很难让编译器发出。只要我们使用一个值零扩展到
unsigned
或uint64_t
,AND在较高的字节中将为零,所以最坏的情况是,愚蠢的编译器将浪费一些雷克斯前缀,而它本可以使用32位操作数大小。(在C中,memcpy
到uint64_t
用于未对齐的别名安全加载)。为了好玩,在https://uica.uops.info/提醒我在Intel CPU上移位与分支竞争端口0和6之前,我尝试编写了一些asm。因此,对于关键路径延迟来说,资源冲突与更长的依赖链一样是一个严重的问题,并且很坚韧让它在每次迭代中执行8个周期以上,或者在Ice Lake上检查每个字节约1个周期。这是一个简单的内存源
and al, [rdi]
/jz
左移
eax
/rax
以对齐x
(RAX中的一个字节,不一定是最低的)(a[i+1..8]
)和RSI(a[i+1..8] & a[i+2..9]
)用2个班次交换1个班次(在末尾加上1个额外的移位以使RAX中的字节向下回到底部。)它还增加了关键路径延迟,与移位独立负载结果相比,移位和AND两者作为RAX的循环承载依赖性链的一部分。也许我们可以用64位AND并行做其他工作,比如对最后2对字节进行水平AND?但是将0xFF放入RAX,然后提取较高的字节作为早期输出将需要额外的指令,可以跳过最后4个字节吗?
(未经测试,可能不是最佳的,特别是在英特尔;太多的班次和分支竞争端口0和6。
只要输入数组中有一个保证的零,就不需要外循环条件;我们肯定会在那里停止,如果不是更早的话,所以不需要循环计数器,只需要指针更新来跟踪进度。
uiCA says Ice Lake / Rocket Lake可以以每次迭代7个周期(检查8个字节)的速度运行它,仅仅比每个周期一个AND快一点。
在冰湖上是相当挑剔的,至少在uiCA对管道的模拟中是这样(假设完美的分支预测等)在某些点使用一些
shl rax, 16
而不是shr rcx,16
/shr rsi,16
的版本预计在Ice Lake上运行约8 c/ iter。(使用雷克斯前缀使两条指令更长),避免了Skylake上的JCC勘误表,在ICL上减慢到8.85c,但在Skylake上加速到约10 c。(uiCA,清理未调整以从RAX中获取正确的字节)。但这种做法很可能不好;也许更好的工作在2或4字节的块,所以更多的加载和更少的移位。也许甚至2字节加载到一个寄存器,然后
test al,cl
/and cl, ch
/and al, cl
或其他东西。阅读CH在Intel上有一个周期额外的延迟,但不消耗吞吐量。