内存操作:在现代CPU / GPU中设置每第n位(C / C ++)

时间:2013-06-02 22:57:04

标签: c bitmap sse

据我所知,“现代”CPU具有非常令人印象深刻的操作二进制数据的例程,例如通过相同的操作流式传输许多数据。

Ad hoc我找不到一个lib来使用那些CPU或GPU硬件制作简单的指令(在GB内存中设置每5位),只需要经典的| << &技巧。

但是设置每个第5或第721位必须与在宽度为5或宽度为721的黑白图片中绘制垂直黑线相同,我希望有一个快速的方法。

所以我的问题:在主流x86_64 Intel / AMD CPU或GPU上有没有提示如何以快速有效的方式使用位?开源将是一个副作用。

1 个答案:

答案 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

我试图想办法增加在包装时设置进位标志的位内字节位置,这样你就可以adcptr+= stride + carry。或者只是得到0或1来添加。

较短的步幅

如果你的步幅等于128b,事情是微不足道的。只需读取/修改并使用常量掩码存储到POR

如果你的步幅较小,那么事情会变得有趣。向量寄存器没有按位旋转指令。可以通过巧妙的方式在xmm寄存器中移位多个设置位。