1

据我了解,“现代” CPU 具有相当令人印象深刻的处理二进制数据的例程,例如通过同一操作流式传输许多数据。

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

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

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

4

1 回答 1

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 % 8while循环 8 位寄存器ptr += stride/8 + carry。实际上,按寄存器计数旋转比通常的 ALU 操作(在最近的 Intel 上)要慢一些,但可变计数移位也是如此。

; 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 寄存器中移动多个设置位。

于 2015-08-02T01:33:59.203 回答