比条件+指针增加更快?

时间:2018-10-04 20:26:10

标签: c performance optimization memcpy blit

这是我简单的提示功能:

#include <iostream>
#include <cstdlib>
#include <ctime>

using namespace std;

int check ( int a , int n[3] )
{
    for ( int i = 0 ; i < 3 ; i++ )
    {
        if ( a == n[i] )
        {
            return 1 ;
        }
    }
    return 0 ;
}

int main ()
{
    //PRE-PROGRAM DECLARATIONS
    int n[3] , a[3] , input , repition ;
    repition = 0 ;
    srand(time(NULL)) ;
    n[0] = rand() % 10 ;
    n[1] = rand() % 10 ;
    n[2] = rand() % 10 ;

    //TESTING STUFF GOES HERE
    //cout << n[0] << n[1] << n[2] << endl ;

    //OUTPUT, INPUT, and PROCESSING
    cout << "Enter your Guess Here : " ;
    cin >> input ;
    a[0] = input / 100 ;
    a[1] = ( input % 100 ) / 10 ;
    a[2] = ( input % 10 ) ;


    //CONDITIONS
    if ( a[0] == n[0] && a[1] == n[1] && a[2] == n[2] )
    {
        repition = 4 ;
    }
    else
    {
        for ( int i = 0 ; i < 3 ; i++ )
        {
            repition = repition + check( n[i] , a ) ;
        }
    }

    //OUTPUT
    switch (repition)
    {
        case 4:
            cout << "All digits correct - Exact Order!\n" ;
            break ;
        case 3:
            cout << "All digits correct - Different Order!\n" ;
            break ;
        case 2:
            cout << "Two digits correct!\n" ;
            break ;
        case 1:
            cout << "Only One digit Correct!\n" ;
            break ;
        case 0:
            cout << "Sorry Try Again!\n" ;
            break ;
        default :
            cout << "Something is terrible!\n" ;
    }
    return 0;
}

我已经在static void blit8(unsigned char* dest, unsigned char* src) { byte i; for (i = 0; i < 8; ++i) { if (*src != 0) { *dest = *src; } ++dest; ++src; } } ,并且-O3已被内联。 blit8(gcc)在这里无效。也没有以任何不同的方式递增指针,也没有使用其他数字作为透明度,也没有使用restrict的其他类型……我什至尝试传递1字节的位掩码并检查它,而不是取消引用i。将src的限制提高到16,似乎可以提供非常较小的加速(〜4-6%),但是我使用的是8字节而不是16字节块。

我的瓶颈?实际上不知道,我不认为这是高速缓存行,因为我的未命中率很低(?),i(高速缓存行大小)在更改周围事物时没有特殊意义。但是我也不认为这是内存速度(因为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 输出(带有内联):

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% ) D1未命中率?对我来说听起来很低。

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

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

据我所知,问题是没有向量指令支持条件,但我需要保留0,其中destsrc。有什么[快速]可以像0一样,但是在字节级别上起作用?

我在有扩展名或告诉CPU不要缓存某些数据之前阅读过文件,但我找不到它。我的想法是不直接从OR中读取,而仅从其中写入src,并确保不对其进行缓存。然后只需从位掩码中读取以检查透明度。 我只是不知道该怎么做。那还有可能更不用说快速吗?我也不知道,所以为什么要问这个问题。

我希望有一个技巧,介绍如何仅使用C或某些gcc扩展就可以更快地完成开发,但是如果x86汇编是唯一的方法,那就这样吧。帮助我理解我的实际瓶颈(因为我对结果感到困惑)也会有所帮助。

2 个答案:

答案 0 :(得分: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;
    }
}

最终显示为:

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

它已由编译器展开,但仍可用于单个字节。

但是在这种情况下,有一种技巧很常见-代替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];
}

和相应的程序集:

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

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

答案 1 :(得分:1)

如果您的编译器/体系结构支持vector extensions(例如clang和gcc),则可以使用以下方法:

//This may compile to awful code on x86_64 b/c mmx is slow (its fine on arm64)
void blit8(void* dest, void* src){
typedef __UINT8_TYPE__ u8x8  __attribute__ ((__vector_size__ (8), __may_alias__));
    u8x8 *dp = dest, d = *dp, *sp = src, s = *sp, cmp;
    cmp = s == (u8x8){0};
    d &= cmp;
    *dp = s|d;
}

//This may compile to better code on x86_64 - worse on arm64
void blit8v(void* dest, void* src){
typedef __UINT8_TYPE__ u8x16  __attribute__ ((__vector_size__ (16), __may_alias__));
typedef __UINT64_TYPE__ u64, u64x2  __attribute__ ((__vector_size__ (16), __may_alias__));
    u8x16 *dp = dest, d = *dp, *sp = src, s = *sp, cmp;
    cmp = s == (u8x16){0};
    d &= cmp;
    d |= s;
    *(u64*)dest = ((u64x2)d)[0];
}

//This one is fine on both arm and x86, but 16 bytes vs. 8
void blit16(void* dest, void* src){
typedef __UINT8_TYPE__ u8x16  __attribute__ ((__vector_size__ (16), __may_alias__));
    u8x16 *dp = dest, *sp = src, d = *dp, s = *sp, cmp;
    cmp = s == (u8x16){0};
    *dp = s|(d & cmp);
}

直接编译为:

blit8:
        ldr     d1, [x1]
        ldr     d2, [x0]
        cmeq    v0.8b, v1.8b, #0
        and     v0.8b, v0.8b, v2.8b
        orr     v0.8b, v0.8b, v1.8b
        str     d0, [x0]
        ret
blit16:
        ldr     q1, [x1]
        ldr     q2, [x0]
        cmeq    v0.16b, v1.16b, #0
        and     v0.16b, v0.16b, v2.16b
        orr     v0.16b, v0.16b, v1.16b
        str     q0, [x0]
        ret

在x86_64上:

blit8v:                                 # @blit8v
        movdqa  xmm0, xmmword ptr [rsi]
        pxor    xmm1, xmm1
        pcmpeqb xmm1, xmm0
        pand    xmm1, xmmword ptr [rdi]
        por     xmm1, xmm0
        movq    qword ptr [rdi], xmm1
        ret
blit16:                                 # @blit16
        movdqa  xmm0, xmmword ptr [rsi]
        pxor    xmm1, xmm1
        pcmpeqb xmm1, xmm0
        pand    xmm1, xmmword ptr [rdi]
        por     xmm1, xmm0
        movdqa  xmmword ptr [rdi], xmm1
        ret