查看完整版本: 关于程序优化的一点心得(arm, Cygwin )

Roc 2006-4-22 14:03

关于程序优化的一点心得(arm, Cygwin )

关于程序优化的一点心得(arm,  Cygwin )
我从两年前开始编写GDI程序,研究过miniGUI, microwindows, X-windows等多种
GDI,发现其实GDI也就几大块的内容,关键就是驱动、窗口管理、裁减管理、抗闪
烁;解决了这么几块,整个GDI也就初具雏形了。就这两年的经验,分次讲讲我的
体会:这次就讲讲我对于写LCD驱动的一点体会。LCD驱动无非是画点、画线、
Bitblt等问题,想来大家写这个都应该不是很困难的,关键是速度。这就牵涉到程
序优化的问题。下面简单举两个例子:
example 1:
char srcbuffer[1000];
char dstbuffer[1000];
void directcopy(char *srcaddr, char *dstaddr, int width)
{
// note i cut cut
while(width--){
  dstaddr[width] = srcaddr[width];
}
}
}我想对于一行数据的拷贝,大家都会这么写。想想这应该已经很快了,也没什么
可以优化的,其实错了,就这段代码,可以优化的地方很多。
example 2:
#define DUFFS_LOOP8(pixel_copy_increment, width)
{
int n = width/8;
switch (width & 7)
{
       default: do { pixel_copy_increment;
     case 7:   pixel_copy_increment;
     case 6:  pixel_copy_increment;
     case 5:  pixel_copy_increment;
     case 4:  pixel_copy_increment;
     case 3:  pixel_copy_increment;
     case 2:  pixel_copy_increment;
     case 1:  pixel_copy_increment;
     case 0:  ;
   } while ( n-- > 0 );
}
}
void optimizecopy(char *srcaddr, char *dstaddr)
{
DUFFS_LOOP8(*dstaddr++ = *srcaddr++, 1000);
}

int _main()
{
directcopy(srcbuffer, dstbuffer, 1000);

optimizecopy(srcbuffer, dstbuffer, 1000);
}

这个时候肯定有人会说:靠,不就是一段代码嘛?有没有必要搞得这么复杂,他其
实错了,这样一改写后,会节省很多效率,反汇编这两段程序,你就会发现
example1每一次赋值都要做一次判断,而example2每赋8次值才做一次判断,这很
影响效率特别是在大批量数据复制的时候。这些判断语句的消耗是惊人的。
00008000 <directcopy>:
    8000: e1a0c00d  mov r12, sp
    8004: e92dd800  stmdb sp!, {r11, r12, lr, pc}
    8008: e24cb004  sub r11, r12, #4 ; 0x4
    800c: e2422001  sub r2, r2, #1 ; 0x1
    8010: e3720001  cmn r2, #1 ; 0x1
    8014: 091ba800  ldmeqdb r11, {r11, sp, pc}
    8018: e7d03002  ldrb r3, [r0, r2]
    801c: e7c13002  strb r3, [r1, r2]
    8020: e2422001  sub r2, r2, #1 ; 0x1
    8024: e3720001  cmn r2, #1 ; 0x1
    8028: 1afffffa  bne 8018 <directcopy+0x18>
    802c: e91ba800  ldmdb r11, {r11, sp, pc}

00008030 <optimizecopy>:
    8030: e1a0c00d  mov r12, sp
    8034: e92dd800  stmdb sp!, {r11, r12, lr, pc}
    8038: e24cb004  sub r11, r12, #4 ; 0x4
    803c: e3520000  cmp r2, #0 ; 0x0
    8040: b2823007  addlt r3, r2, #7 ; 0x7
    8044: a1a03002  movge r3, r2
    8048: e1a0c1c3  mov r12, r3, asr #3
    804c: e2022007  and r2, r2, #7 ; 0x7
    8050: e3520007  cmp r2, #7 ; 0x7
    8054: 979ff102  ldrls pc, [pc, r2, lsl #2]
    8058: ea000007  b 807c <optimizecopy+0x4c>
    805c: 000080bc  streqh r8, [r0], -r12
    8060: 000080b4  streqh r8, [r0], -r4
    8064: 000080ac  andeq r8, r0, r12, lsr #1
    8068: 000080a4  andeq r8, r0, r4, lsr #1
    806c: 0000809c  muleq r0, r12, r0
    8070: 00008094  muleq r0, r4, r0
    8074: 0000808c  andeq r8, r0, r12, lsl #1
    8078: 00008084  andeq r8, r0, r4, lsl #1
    807c: e4d03001  ldrb r3, [r0], #1
    8080: e4c13001  strb r3, [r1], #1
    8084: e4d03001  ldrb r3, [r0], #1
    8088: e4c13001  strb r3, [r1], #1
    808c: e4d03001  ldrb r3, [r0], #1
    8090: e4c13001  strb r3, [r1], #1
    8094: e4d03001  ldrb r3, [r0], #1
    8098: e4c13001  strb r3, [r1], #1
    809c: e4d03001  ldrb r3, [r0], #1
    80a0: e4c13001  strb r3, [r1], #1
    80a4: e4d03001  ldrb r3, [r0], #1
    80a8: e4c13001  strb r3, [r1], #1
    80ac: e4d03001  ldrb r3, [r0], #1
    80b0: e4c13001  strb r3, [r1], #1
    80b4: e4d03001  ldrb r3, [r0], #1
    80b8: e4c13001  strb r3, [r1], #1
    80bc: e1a0300c  mov r3, r12
    80c0: e24cc001  sub r12, r12, #1 ; 0x1
    80c4: e3530000  cmp r3, #0 ; 0x0
    80c8: caffffeb  bgt 807c <optimizecopy+0x4c>
    80cc: e91ba800  ldmdb r11, {r11, sp, pc}

000080d0 <_main>:
    80d0: e1a0c00d  mov r12, sp
    80d4: e92dd800  stmdb sp!, {r11, r12, lr, pc}
    80d8: e24cb004  sub r11, r12, #4 ; 0x4
    80dc: e59f001c  ldr r0, [pc, #1c] ; 8100 <_main+0x30>
    80e0: e59f101c  ldr r1, [pc, #1c] ; 8104 <_main+0x34>
    80e4: e3a02ffa  mov r2, #1000 ; 0x3e8
    80e8: ebffffc4  bl 8000 <directcopy>
    80ec: e59f000c  ldr r0, [pc, #c] ; 8100 <_main+0x30>
    80f0: e59f100c  ldr r1, [pc, #c] ; 8104 <_main+0x34>
    80f4: e3a02ffa  mov r2, #1000 ; 0x3e8
    80f8: ebffffcc  bl 8030 <optimizecopy>
    80fc: e91ba800  ldmdb r11, {r11, sp, pc}
    8100: 00008210  andeq r8, r0, r0, lsl r2
    8104: 00008600  andeq r8, r0, r0, lsl #12

如果我们确实仔细的看了一下这些汇编代码,你还会有新的发现。没有吗?再仔细
看看。用switch来进行变量的判断要比if else 有效率的多。取出switch的代码。
    8050: e3520007  cmp r2, #7 ; 0x7
    8054: 979ff102  ldrls pc, [pc, r2, lsl #2]
取出if else 的代码
    8024: e3720001  cmn r2, #1 ; 0x1
    8028: 1afffffa  bne 8018 <directcopy+0x18>
看起来谁然大家都是两条指令而已,但是switch可以进行多个值的判断,在这一点
上要比if else有效率的多,switch再编译的时候构建了一个表根据索引值来定位
表项,所以当有人要写冗长的if else if else if else....代码的时候,是不是
应该考虑以下可不可以用switch () case 来代替呢?
在推敲一下我们所写的代码,是不是还可以改进?不能了,真的不能了吗?现在很
多mcu都是32位的了,用32位的赋值要比8位有效率的多。32位的赋值只要一条指令
就可以了,而8位的赋值却要4条,16位的赋值要2条。如果有人仔细看过memcpy的
标准c函数的实现,就会发现memcpy恰恰对32位cpu系统做了优化,但要注意子节对
齐的问题哦。
#define CYG_STR_OPT_BIGBLOCKSIZE     (sizeof(CYG_WORD) << 2)
#define CYG_STR_OPT_LITTLEBLOCKSIZE (sizeof (CYG_WORD))

typedef unsigned long  CYG_WORD;
#define CYG_STR_UNALIGNED(X, Y)      (((CYG_WORD)(X) & (sizeof
(CYG_WORD) - 1)) |       ((CYG_WORD)(Y) & (sizeof (CYG_WORD) - 1)))
void *
_memcpy( void *s1, const void *s2, size_t n )
{
#if defined(CYGIMP_INFRA_PREFER_SMALL_TO_FAST_MEMCPY) ||
defined(__OPTIMIZE_SIZE__)
    char *dst = (char *) s1;
    const char *src = (const char *) s2;

#ifdef CYG_TRACING_FIXED
    CYG_REPORT_FUNCNAMETYPE( "_memcpy", "returning %08x" );
    CYG_REPORT_FUNCARG3( "dst=%08x, src=%08x, n=%d", dst, src, n );

    if (n != 0)
    {
        CYG_CHECK_DATA_PTR( dst, "dst is not a valid pointer!" );
        CYG_CHECK_DATA_PTR( src, "src is not a valid pointer!" );
        CYG_CHECK_DATA_PTR( dst+n-1, "dst+n-1 is not a valid address!"
);
        CYG_CHECK_DATA_PTR( src+n-1, "src+n-1 is not a valid address!"
);
    }
#endif

    while (n--)
    {
        *dst++ = *src++;
    } /* while */

#ifdef CYG_TRACING_FIXED
    CYG_REPORT_RETVAL( s1 );
#endif
    return s1;
#else
    char *dst;
    const char *src;
    CYG_WORD *aligned_dst;
    const CYG_WORD *aligned_src;

#ifdef CYG_TRACING_FIXED
    CYG_REPORT_FUNCNAMETYPE( "_memcpy", "returning %08x" );
#endif

    dst = (char *)s1;
    src = (const char *)s2;

#ifdef CYG_TRACING_FIXED
    CYG_REPORT_FUNCARG3( "dst=%08x, src=%08x, n=%d", dst, src, n );

    if (n != 0)
    {
        CYG_CHECK_DATA_PTR( dst, "dst is not a valid pointer!" );
        CYG_CHECK_DATA_PTR( src, "src is not a valid pointer!" );
        CYG_CHECK_DATA_PTR( dst+n-1, "dst+n-1 is not a valid address!"
);
        CYG_CHECK_DATA_PTR( src+n-1, "src+n-1 is not a valid address!"
);
    }
#endif

    /* If the size is small, or either SRC or DST is unaligned,
     * then punt into the byte copy loop.  This should be rare.
     */
    if (n < sizeof(CYG_WORD) || CYG_STR_UNALIGNED (src, dst))
    {
        while (n--)
            *dst++ = *src++;
#ifdef CYG_TRACING_FIXED
        CYG_REPORT_RETVAL( s1 );
#endif
        return s1;
    } /* if */

    aligned_dst = (CYG_WORD *)dst;
    aligned_src = (const CYG_WORD *)src;

    /* Copy 4X long words at a time if possible.  */
    while (n >= CYG_STR_OPT_BIGBLOCKSIZE)
    {
        *aligned_dst++ = *aligned_src++;
        *aligned_dst++ = *aligned_src++;
        *aligned_dst++ = *aligned_src++;
        *aligned_dst++ = *aligned_src++;
        n -= CYG_STR_OPT_BIGBLOCKSIZE;
    } /* while */

    /* Copy one long word at a time if possible.  */
    while (n >= CYG_STR_OPT_LITTLEBLOCKSIZE)
    {
        *aligned_dst++ = *aligned_src++;
        n -= CYG_STR_OPT_LITTLEBLOCKSIZE;
    } /* while */

    /* Pick up any residual with a byte copier.  */
    dst = (char*)aligned_dst;
    src = (const char*)aligned_src;
    while (n--)
        *dst++ = *src++;

#ifdef CYG_TRACING_FIXED
    CYG_REPORT_RETVAL( s1 );
#endif
    return s1;
#endif /* not defined(CYGIMP_PREFER_SMALL_TO_FAST_MEMCPY) ||
        * defined(__OPTIMIZE_SIZE__) */
} /* _memcpy() */

好了,这段程序的优化也可告一段落,对于memcpy的优化的实现现在也应经很清楚
了,大家可以尝试着写以下优化我们的代码。
这个时候,肯定会有人跳出来说,靠,我们的cpu,mpu这么快,不要吃饱了撑着,
其实这种想法是不对的,我们做的是嵌入式系统,不是普通的在P4 2.5G上跑的应
用程序,严格要求自己,写的代码会很经典并且速度狂快。特别是在我写LCD 驱动
的时候,一开始没有优化的代码跑起来是很慢的。优化了之后测试了一下速度,居
然快了两个数量级。效果是很明显的。

供大家讨论参考。
页: [1]
查看完整版本: 关于程序优化的一点心得(arm, Cygwin )