如何解决检查uint8_t [8]是否包含任何非0并以一个内存负载访问非零插槽
基本上我有一个带有定义的结构
#define BATCH_SIZE 8
#define BATCH_SIZE_LOG 3
//#define BATCH_MASK 0x7070707070707070
// for the sake of understanding the ASM turn this into a no-op
#define BATCH_MASK (~(0UL))
struct batcher {
uint8_t indexes[8];
uint64_t vals[8 * BATCH_SIZE];
uint32_t __attribute__((noinline))
push(const uint64_t i,const uint64_t v) {
if(__builtin_expect(indexes[i] < (BATCH_SIZE - 1),1)) {
vals[8 * i + indexes[i]++] = v;
return 0;
}
return 1;
}
uint32_t __attribute__((noinline))
claim(const uint64_t i) {
if(__builtin_expect(indexes[i] == (BATCH_SIZE - 1),1)) {
indexes[i] = 8;
return 0;
}
return 1;
}
uint32_t
can_pop() const {
if(*((uint64_t *)(&indexes[0])) & BATCH_MASK) {
return 1;
}
return 0;
}
uint64_t __attribute__((noinline))
pop() {
if(__builtin_expect(can_pop(),1)) {
const uint32_t idx = _tzcnt_u64(*((uint64_t *)(&indexes[0])) & BATCH_MASK) >> BATCH_SIZE;
return vals[8 * idx + --indexes[idx]];
}
return 0;
}
};
我很好奇的是,pop
是否可以仅用indexes
中的1个内存加载来实现(所以总共2个,indexes
中有1个,vals
中有1个)
第一个内存负载是将所有indexes
解释为uint64_t
,以便我可以检查它是否为非0,如果是,则使用这些索引之一。
我一直在查看程序集输出here
具有pop
的实现
batcher::pop():
movq (%rdi),%rax // first load from indexes
testq %rax,%rax
jne .L11
ret
.L11:
xorl %edx,%edx
movzbl (%rdi,%rdx),%eax // second load from indexes
decl %eax
movb %al,(%rdi,%rdx)
movzbl %al,%eax
movq 8(%rdi,%rax,8),%rax
ret
编译器的实现方式是从%(rdi)
到%rax
的一种方式,以解释为uint64_t
(测试是否有非0索引),并且在第二次加载时进行解释。条件通过加载计算出的uint8_t
索引。
我想知道是否有一种方法可以在没有两个负载的情况下在组装中实现pop
(我将要做的事情)。我知道我可以通过对第一次加载的结果进行移位/屏蔽来完成相同的逻辑。我特别想知道的是,是否有一种方法可以让我索引第一次加载所产生的uint64_t
,就像它在uint8_t[8]
数组中一样。
我的猜测是,这不可能是因为寄存器没有内存地址,所以这样做并没有任何意义,但是我可能会丢失一些专门用于隔离{{ 1}}或通过某种方式重构uint64_t
的程序集实现以启用此功能。
注意:我仅限于Intel Skylake上可用的指令集。
如果有人有任何想法,我将不胜感激。谢谢!
解决方法
可能是tzcnt
,将其四舍五入到8位的倍数,然后右移(对于BMI2 shrx
,这是一个uop)。然后,非零字节位于寄存器的底部,您可以在其中movzbl
将其零扩展到任何其他寄存器(not the same one,that would defeat mov-elimination)
tzcnt %rax,%rcx # input in RAX
and $-8,%ecx # 0xff...f8
shrx %rcx,%rax,%rdx # rdx = rax >> cl
movzbl %dl,%eax # zero latency between separate registers
(如果可能为全零,则test / jz
,如果您需要检测这种情况,或者只是让移位发生。将qword移位64将使值保持不变,因此结果将为0
。 )
您可以使用_tzcnt_u64
之类的内部函数来完成此操作;为此使用内联汇编没有明显的好处。您可以使用GNU C进行未对齐的严格混叠安全qword加载typedef uint64_t aliasing_u64 __attribute__((aligned(1),may_alias))
。
只有8个字节,对移动掩码结果上的pcmpeqb
/ pmovmskb
/ tzcnt
而言,找到字节位置通常是过大的。 (然后整数movzbl
使用字节偏移量从内存中加载该字节)。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。