比条件+指针增量快吗?

Acc*_*tor 6 c optimization performance memcpy blit

这是我简单的blitting功能:

static void blit8(unsigned char* dest, unsigned char* src)
{
    byte i;
    for (i = 0; i < 8; ++i) {
        if (*src != 0) {
            *dest = *src;
        }
        ++dest;
        ++src;
    }
}
Run Code Online (Sandbox Code Playgroud)

我已经在-O3,blit8并被内联.restrict(gcc)在这里没有效果.也没有以任何不同的方式递增指针,或使用另一个数字作为透明度,或另一种类型i...我甚至尝试传递一个1字节的位掩码并检查而不是解除引用src.增加i16 的限制似乎提供了一个非常小的加速(~4-6%),但我正在使用8字节而不是16字节的块.

我的瓶颈?实际上没有任何线索,我不认为这是缓存行,因为我的错过率很低(?)和64(我的缓存行大小)在改变事物时没有特别的意义.但是我不认为它的内存速度memcpy也是如此(因为速度更快,稍微更多).

cg_annotate这说blit8(没有内联):

Ir    I1mr   ILmr            Dr      D1mr   DLmr          Dw       D1mw    DLmw  file:function
3,747,585,536      62      1 1,252,173,824 2,097,653      0 674,067,968          0       0  ppu.c:blit8.constprop.0
Run Code Online (Sandbox Code Playgroud)

常规cachegrind输出(带内联):

I   refs:      6,446,979,546
I1  misses:          184,752
LLi misses:           22,549
I1  miss rate:          0.00%
LLi miss rate:          0.00%

D   refs:      2,150,502,425  (1,497,875,135 rd   + 652,627,290 wr)
D1  misses:       17,121,968  (    2,761,307 rd   +  14,360,661 wr)
LLd misses:          253,685  (       70,802 rd   +     182,883 wr)
D1  miss rate:           0.8% (          0.2%     +         2.2%  )
LLd miss rate:           0.0% (          0.0%     +         0.0%  )

LL refs:          17,306,720  (    2,946,059 rd   +  14,360,661 wr)
LL misses:           276,234  (       93,351 rd   +     182,883 wr)
LL miss rate:            0.0% (          0.0%     +         0.0%  )
Run Code Online (Sandbox Code Playgroud)

0.8%D1未命中率?对我来说听起来很低落.

对我来说最有趣的是,删除0-check(功能相同memcpy)可以提供<1%的加速,即使:

memcpy比约快25%.我希望尽可能接近原始速度memcpy,同时保持颜色0透明.

问题是,据我所知,没有向量指令支持条件语句,但我需要保存dest在那里src0.有什么[fast]可以表现得像OR字节级别吗?

我之前读过有一个扩展或者其他东西告诉CPU不要缓存一些数据,但我再也找不到了.我的想法是不直接读取src,只是从中写入dest,并确保它不被缓存.然后只需从位掩码中读取以检查透明度.我只是不知道如何实际做到这一点.这甚至可能更快吗?我也不知道,所以我为什么要问这个问题.

我更喜欢有关如何使用C更快速的提示,也许是一些gcc扩展,但如果x86汇编是唯一的方法,那就这样吧.帮助我理解我的实际瓶颈(因为我对我的结果感到困惑)也会有所帮助.

Ant*_*nty 2

您没有提到是否使用 GCC,但我们假设是。如果涉及循环内的条件,GCC 会很挑剔 - 这就是您的示例无法矢量化的原因。

所以这段代码:

void blit8(unsigned char* dest, unsigned char* src)
{
    char i;
    for (i = 0; i < 8; ++i) {
        if (*src != 0) {
            *dest = *src;
        }
        ++dest;
        ++src;
    }
}
Run Code Online (Sandbox Code Playgroud)

最终为:

blit8:
        movzx   eax, BYTE PTR [rsi]
        test    al, al
        je      .L5
        mov     BYTE PTR [rdi], al
.L5:
        movzx   eax, BYTE PTR [rsi+1]
        test    al, al
        je      .L6
        mov     BYTE PTR [rdi+1], al
.L6:
        movzx   eax, BYTE PTR [rsi+2]
        test    al, al
        je      .L7
        mov     BYTE PTR [rdi+2], al
.L7:
        movzx   eax, BYTE PTR [rsi+3]
        test    al, al
        je      .L8
        mov     BYTE PTR [rdi+3], al
.L8:
        movzx   eax, BYTE PTR [rsi+4]
        test    al, al
        je      .L9
        mov     BYTE PTR [rdi+4], al
.L9:
        movzx   eax, BYTE PTR [rsi+5]
        test    al, al
        je      .L10
        mov     BYTE PTR [rdi+5], al
.L10:
        movzx   eax, BYTE PTR [rsi+6]
        test    al, al
        je      .L11
        mov     BYTE PTR [rdi+6], al
.L11:
        movzx   eax, BYTE PTR [rsi+7]
        test    al, al
        je      .L37
        mov     BYTE PTR [rdi+7], al
.L37:
        ret
Run Code Online (Sandbox Code Playgroud)

它由编译器展开,但仍然适用于单字节。

但在这种情况下,有一个技巧经常起作用 - 使用三元运算符代替 if(cond)。这将解决一个问题。但还有另一个问题 - GCC 拒绝矢量化短小块 - 在本例中为 8 字节。因此,让我们使用另一个技巧 - 在更大的块上进行计算,但忽略其中的一部分。

这是我的例子:

void blit8(unsigned char* dest, unsigned char* src)
{
    int i;
    unsigned char temp_dest[16];
    unsigned char temp_src[16];

    for (i = 0; i < 8; ++i) temp_dest[i] = dest[i];
    for (i = 0; i < 8; ++i) temp_src[i] = src[i];

    for (i = 0; i < 16; ++i) 
    {
        temp_dest[i] = (temp_src[i] != 0) ? temp_src[i] : temp_dest[i];
    }

    for (i = 0; i < 8; ++i) dest[i] = temp_dest[i];
}
Run Code Online (Sandbox Code Playgroud)

以及相应的组件:

blit8:
        mov     rax, QWORD PTR [rdi]
        vpxor   xmm0, xmm0, xmm0
        mov     QWORD PTR [rsp-40], rax
        mov     rax, QWORD PTR [rsi]
        mov     QWORD PTR [rsp-24], rax
        vmovdqa xmm1, XMMWORD PTR [rsp-24]
        vpcmpeqb        xmm0, xmm0, XMMWORD PTR [rsp-24]
        vpblendvb       xmm0, xmm1, XMMWORD PTR [rsp-40], xmm0
        vmovq   QWORD PTR [rdi], xmm0
        ret
Run Code Online (Sandbox Code Playgroud)

注意:我没有对它进行基准测试 - 它只是证明可以通过使用正确的编码规则和技巧来生成 SIMD 代码;)