据我所知,“现代”CPU具有非常令人印象深刻的操作二进制数据的例程,例如通过相同的操作流式传输许多数据。
Ad hoc我找不到一个lib来使用那些CPU或GPU硬件制作简单的指令(在GB内存中设置每5位),只需要经典的| << &
技巧。
但是设置每个第5或第721位必须与在宽度为5或宽度为721的黑白图片中绘制垂直黑线相同,我希望有一个快速的方法。
所以我的问题:在主流x86_64 Intel / AMD CPU或GPU上有没有提示如何以快速有效的方式使用位?开源将是一个副作用。
答案 0 :(得分:0)
首先,为大量内存执行此操作会因缓存未命中而受到限制。当前的CPU可以为每个加载/存储执行相当多的指令,并且仍然可以最大化内存带宽。如果我们谈论已经存在于L1缓存中的几k内存,问题会更加有趣。
如果您每隔721位设置一次,那么矢量内容就无法帮助您。你的步幅是90.125字节,比AVX512矢量大。因此,最佳解决方案是在适当的地址执行单个字节OR
。写循环以跟踪字节中的位位置和字节位置是非常重要的。如果它是编译时常量步幅,则按8展开会使其变得容易。 (每8秒增加一个字节OR
。)
; pointer in rdi
; loop counter in ecx
.loop:
or byte ptr [rdi+90*0], 1<<0
or byte ptr [rdi+90*1], 1<<1
or byte ptr [rdi+90*2], 1<<2
or byte ptr [rdi+90*3], 1<<3
or byte ptr [rdi+90*4], 1<<4
or byte ptr [rdi+90*5], 1<<5
or byte ptr [rdi+90*6], 1<<6
or byte ptr [rdi+90*7], 1<<7
add rdi, 90*8 + 1
sub ecx, 8
jg .loop
; handle the last up to 7 iterations
对于不是编译时常量的步幅,您可以在执行stride % 8
时按ptr += stride/8 + carry
旋转8位寄存器。实际上,通过寄存器计数旋转比通常的ALU操作(在最近的英特尔上)慢一点,但可变计数移位也是如此。
; ecx = unsigned int stride. rdi=char *dest
mov ebx, ecx
and ecx, 7 ; ecx = stride%8
shr ebx, 3 ; ebx = stride/8
mov al, 1
.loop:
or byte ptr [rdi], al
rol al, cl
add rdi, rbx
; efficiently figure out when we need to add an extra 1 to rdi
; lost interest at this point, feel free to edit or post another answer finishing this code.
dec edx
jg .loop
我试图想办法增加在包装时设置进位标志的位内字节位置,这样你就可以adc
做ptr+= stride + carry
。或者只是得到0或1来添加。
如果你的步幅等于128b,事情是微不足道的。只需读取/修改并使用常量掩码存储到POR
。
如果你的步幅较小,那么事情会变得有趣。向量寄存器没有按位旋转指令。可以通过巧妙的方式在xmm寄存器中移位多个设置位。