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 驱动
的时候,一开始没有优化的代码跑起来是很慢的。优化了之后测试了一下速度,居
然快了两个数量级。效果是很明显的。
供大家讨论参考。