引言
嵌入式系统的核心部件是嵌入式处理器。在众多的处理器中,ARM是专为嵌入式应用而设计的处理器,由于其低功耗、高性价比和易扩展性等特点,在嵌入式系统中得到了最为广泛的应用。在许多成功的32位嵌入式系统中,ARM处理器都是其核心组成部分。ARM内核已被广泛应用于移动电话、掌上设备以及种类繁多的便携式消费类产品中[1]。随着ARM处理器越来越广泛的应用,如何提高代码的执行效率已成为工程师关注的问题[23],同时如何在实时嵌入式应用中,通过代码优化以减少消耗过多的CPU运行时间已成为人们关注的焦点。软件开发中,常用的代码优化技巧有“循环展开”、“减少外存访问”、“考虑CPU带宽”、“循环减计数”、“循环变量数据类型”、“使用SWITCH取代条件判断”等。而图像在不同屏幕尺寸之间缩放的程序是嵌入式领域常常用到的功能。这里就以实现这样一个常见功能的程序为例,说明程序优化的技巧[46]。
1 实例分析
程序的目标是将一个长宽为240×160,格式为RGB565的显示缓冲区的内容映射到长宽为320×240,格式也为RGB565的显示缓冲区内。因为源数据宽度是240点,所以,放大到目的区域的时候,就需要每3个源数据点中,重复1点的数据,变成4点,放到目的显示缓冲区中。同样,每2行源数据也要重复1行,变成3行,放到目的显示缓冲区中。源数据和目的数据分别定义如下
(保证源显示缓冲区和目的显示缓冲区都是4字节对齐起始的):
U16 u16_src_buf[240*160];//源显示缓冲区
U16 u16_dst_buf[320*240];//目的显示缓冲区
注:以下所有程序均是使用ADS1.1编译,CPU主频为100 MHz,使用ARM7EJS为目标处理器,小端编译方式,测试的时间是以所有的程序和数据均是Cache全命中为前提测试得到的。
2 原始程序
下面这段程序是没有经过任何优化的程序,仅仅实现了相应的功能要求,运行时间为10 ms。
unsigned short u16_i,u16_j; unsigned short*pu16_src,*pu16_dst;
pu16_src = u16_src_buf; pu16_dst = u16_dst_buf;
for(u16_i = 0;u16_i < 160;u16_i++) {
for(u16_j = 0;u16_j < 240;u16_j++) {
*pu16_dst++ = *pu16_src++;
if((u16_j % 3) == 2 )
*pu16_dst++ = *pu16_src;
}
if((u16_i % 2) == 1) {
memcpy(pu16_dst, pu16_dst320, 640);
pu16_dst+=320;
}
}
3 优化步骤
第一,采用循环展开的技巧进行优化,也就是尽量减少内层循环的次数。这里在行循环中,由原来的每次处理1行源数据,一共循环160次,改成每次处理2行源数据,一共只需要循环80次。同时,在行内部的列循环中,由原来的每次处理1个源像素点,一共循环240次,改成1次处理3个源像素点,一共只需要循环80次。运行时间缩短为8 ms。
unsigned short u16_i,u16_j;unsigned short *pu16_src, *pu16_dst;
pu16_src = u16_src_buf;pu16_dst = u16_dst_buf;
for(u16_i = 0;u16_i < 80;u16_i++) {
for(u16_j = 0;u16_j < 80;u16_j++) {
(*(unsigned short *)pu16_dst++) = (*(unsigned short *)pu16_src++);
(*(unsigned short *)pu16_dst++) = (*(unsigned short *)pu16_src++);
(*(unsigned short *)pu16_dst++) = (*(unsigned short *)pu16_src++);
(*(unsigned short *)pu16_dst++) = (*(unsigned short *)pu16_src);
}
for(u16_j = 0;u16_j < 80;u16_j++) {
(*(unsigned short *)pu16_dst++) = (*(unsigned short *)pu16_src++);
(*(unsigned short *)pu16_dst++) = (*(unsigned short *)pu16_src++);
(*(unsigned short *)pu16_dst++) = (*(unsigned short *)pu16_src++);
(*(unsigned short *)pu16_dst++) = (*(unsigned short *)pu16_src);}
for(u16_j = 0;u16_j < 320;u16_j++) {
(*(unsigned short *)pu16_dst) = (*(unsigned short *)(pu16_dst 320));pu16_dst++;
}
}
第二,因为ARM处理器的带宽是4字节,所以,取数据时也使用4字节的方式是效率最高的,程序设计时也要尽量利用这个特点提高效率。下面就利用这个特点,每次取源数据时都取4字节。因为行内部是每3点要重复1点,因此,行内部循环改为每次处理6个像素点,这样,又进一步减少了循环次数。运行时间缩短为4 ms。
unsigned int temp, temp1;unsigned short u16_i,u16_j;unsigned int *pu32_src, *pu32_dst, *pu32_dst1;
pu32_src = (unsigned int *)u16_src_buf;pu32_dst = (unsigned int *)u16_dst_buf;
for(u16_i = 0;u16_i < 80;u16_i++) {
for(u16_j = 0;u16_j < 40;u16_j++) {
*pu32_dst++ = *pu32_src++;
temp = *pu32_src++;
*pu32_dst++ =((temp&0xffff) | (temp<<16));
*pu32_dst++ =((temp&0xffff0000) | (temp>>16));
*pu32_dst++ = *pu32_src++;
}
for(u16_j = 0;u16_j < 40;u16_j++) {
*pu32_dst++ = *pu32_src++;
temp = *pu32_src++;
*pu32_dst++ = ((temp&0xffff) | (temp<<16));
*pu32_dst++ = ((temp&0xffff0000) | (temp>>16));
*pu32_dst++ = *pu32_src++;
}
pu32_dst1 = pu32_dst160;
for(u16_j = 0;u16_j < 160;u16_j++) {
*pu32_dst++ = *pu32_dst1++;
}
}
第三,访问外存往往是程序运行的瓶颈,因为外存的速度一般远远低于CPU运行速度,所以,在编程的过程中,要尽量减少对外存的访问。下面,将行循环中重复写入的那行在上一行生成的过程中直接完成,减少了拷贝重复行过程中读取1行的时间。运行时间缩短为3 ms。
unsigned int temp, temp1;unsigned short u16_i,u16_j; unsigned int *pu32_src, *pu32_dst, *pu32_dst1;
pu32_src = (unsigned int *)u16_src_buf;pu32_dst = (unsigned int *)u16_dst_buf;
for(u16_i = 0;u16_i < 80;u16_i++) {
for(u16_j = 0;u16_j < 40;u16_j++) {
*pu32_dst++ = *pu32_src++;
temp = *pu32_src++;
*pu32_dst++ =((temp&0xffff) | (temp<<16));
*pu32_dst++ =((temp&0xffff0000) | (temp>>16));
*pu32_dst++ =*pu32_src++;
}
pu32_dst1 = pu32_dst+160;
for(u16_j = 0;u16_j < 40;u16_j++) {
temp = *pu32_src++;
*pu32_dst++ = temp;
*pu32_dst1++ = temp;
temp=*pu32_src++;
*pu32_dst++=((temp&0xffff)|(temp<<16));
*pu32_dst1++=((temp&0xffff)|(temp<<16));
*pu32_dst++=((temp&0xffff0000)|(temp>>16));
*pu32_dst1++=((temp&0xffff0000)|(temp>>16));
temp = *pu32_src++;*pu32_dst++ = temp;*pu32_dst1++ = temp;
}
pu32_dst= pu32_dst1;
}
结语
本文以图像在不同屏幕尺寸之间缩放的程序为例讲解了代码优化技巧方法,给出了源代码进行探讨,并通过实验得以证实。希望有助于读者编写出在提高执行速度和减小代码尺寸方面更高效的C源代码。