ARM汇编优化
要做程序的优化,最彻底的方法当然是汇编!还有除了汇编以外(除了二进制)能让你对你的处理器有更全面的控制吗?!对于ARM汇编,作为一个初学者,也就只好先补补基础了@_@。
首先,程序段的定义从AREA 开始,它命名一个代码区域,注意,用非阿拉伯数字作为名字时,应该用|把名字包起来,CODE关键字声明程序(猜测),readonly声明访问权限(猜测)。EXPORT 来表示某个可以用作外部连接的符号(简单点,应该就是函数名?)。END用来结尾。
#eg:
AREA |.text|, CODE, READONLY
EXPORT square
; int square(int i)
square ;armcc把不缩进的正文作为一个标号定义
MUL r1,r0,r0
MOV r0,r1 ;ARM乘法指令有一个限制,就是目标寄存器不能和第一个参数寄存器相同
MOV pc,lr ;对Thumb指令,应该改为BX lr
END
使用import,可以声明其他文件中定义的标号,要用ARM C库的话,就import |Lib$Request$armlib|, WEAK表示本行的标号如果找不到,不会报告连接错误。如果程序包含主程序main,那么要引入标号__main,代表C库初始化的开始。RN可以让用户给寄存器命名。
#eg:
AREA |.text|, CODE,READONLY
EXPORT main
IMPORT |Lib$$Request$$armlib|, WEAK
IMPORT __main ;C library entry
IMPORT printf ;prints to stdout
i RN 4
;int main(void)
main
STMFD sp!,{i,lr}
MOV i,#0
loop
ADR r0,print_string
MOV r1,i
MUL r2,i,i
BL printf
ADD i,i,#1
CMP i,#10
BLT loop
LDMFD sp!,{i,pc}
print_string
DCB " Square of %d is %d/n", 0
END
MAP(别名^)和FIELD(别名#),可以在堆栈中为变量和数组定义和分配空间。
MAP 0 ;map symbols to offsets starting at offset 0?
a FIELD 4 ;a is 4 bytes integer(at offset 0)
b FIELD 2 ;b is bytes integer(at offset 4)
c FIELD 64 ;c is an array of 64 characters (at offset 6)
length FIELD 0 ;length records the current offset reached?
MACRO用来声明宏:
MACRO
CHECKSUM $ alignment
checksum_$ alignment
LDR w,[data],#4
l0 ;loop
IF $ alignment<>0
ADD sum,sum,w,LSR #8, $alignement
LDR w,[data],#4
SUBS N,N,#1
ADD sum,sum,w,LSL#32-8* $ alignment
ELSE
ADD sum,sum,w
LDR w,[data],#4
SUBS N,N,#1
ENDIF
BGT %BT l0
MOV pc,lr
MEND
针对汇编的优化主要是面向硬件的。首先是流水线,有些load指令要需要多个周期来完成,可以通过调整指令的顺序(当然要保证逻辑)来改善性能。另外,尽量让程序只是用寄存器,方法是搞清楚数据占用寄存器的时间关系,实现寄存器有效的分时复用。另外,可以将长度较小的变量合并到一个32位寄存器中保存,以节省寄存器。由于PC可以通过程序操作,对于条件指令,可以直接用PC与形成分支的参数作运算来寻找对应的分支:
;int switch_relative(int x)
switch_relative
CMP x,#8
ADDLT pc,pc,x,LSL,#2
B method_d ;利用流水线,如果PC还是按顺序那么default分支的预取址就不
;会被冲掉
B method_0
B method_1
ARM上的C编程
1.arm c编译器默认char类型是8位无符号的,与其它编译器有点不同
2.局部变量最好用int型,因为寄存器是32位的,如果变量不是32位的就需要额外的指令限制范围.
例如: 变量i,操作i++ ,如果int i, 则只需add r1,r1,#1 如果char i,则变成add r1,r1,#1
and r1,r1, 0xff .多了一条指令
3.循环最好用do{}while()型的,相比for(;;)型循环每次循环可以节省3条指令
4.函数参数也最好用int 型的,例如 short add(short x,short y)
编译器为了保证输入参数的是short型的会添加额外的指令,比如确保x是short型的,需要
mov r0,r0,lsr #16 mov r0,r0,asr #16
5.函数参数最好不要超过4个,因为前4个参数是通过寄存器r0-r3传递的,超过4个后的参数使用堆栈传递,速度慢多了.
6.适当的展开循环.循环有一定的开销,在一个循环中多做几遍操作,减少循环的次数可以减少循环的开销.
例如: i = 0; int i;
do do{
{ i++;
i++; i++;
}while(i<64) 改为 i++;}while(i<64)
当然,这样做也增加了代码长度.
7.使用减计数到0的循环结构,这样就不用用寄存器保存终止值.
8.使用无符号的循环计数值,循环条件是i!=0,而不是i>0, 这样循环的开销只有2条指令
ARM上的汇编优化小方法
1. 加减法,逻辑操作占一个周期,目的地址是PC寄存器时增加一个周期。分支指令占3个周期。在cache命中的情况下,16位和8位的装载指令(LDRH、LDRH等)占一个周期,但紧跟的2个周期不能使用装入的数据。32位装载指令占一个周期,紧跟的一个周期不能使用装载数据。如果装载入PC,同样要增加2个周期。
LDR r1,[r2] ADD r1,r1,r3 ADD r4,r4,r5 占4个周期
改变次序后
LDR r1,[r2] ADD r4,r4,r5 ADD r1,r1,r3 占3个周期
2. load指令占时间比较长,在循环中可以使用预载的方法将load与跳转指令放在一起,减少流水线的断流。
例如:
loop
LDRB r2,[r1]
............... //do
B loop
更改为
LDRB r2,[r1]
loop
.............. //do
LDRB r2,[r1]
B loop
3. 循环展开时,可以在计算i步时就加载i+1步的数据,在i步的结果还没准备好时执行i+1步计算。
4. ARM只有16个可见寄存器,其中14个通用寄存器,1个堆栈指针r13,1个程序计数器r15。在图像处理的应用中很多是8位的操作数,可以利用32为寄存器一次进行两组运算。
例如:加操作 100 + 50 和 2 + 3
位 24 16 8 0
操作数1 0 100 0 2
操作数2 0 50 0 3
结果 0 150 0 5
5. 寄存器数量不够时,可用32为寄存器保存两个16位变量和4个8位变量。
初入嵌入式软件开发不久,最近在看UC/OS2的内核源码,感觉很有意思,记下一些学习过程,欢迎大家拍砖。
嵌入式软件经常要同时完成若干任务,可以在无人干预的情况下应对所有的事件及异常,并且可以根据事件的轻重缓急自动保证最先完成最紧急的任务。
嵌入式软件由RTOS跟其上跑的应用部分软件组成,应用部分软件可简单看成一个个任务,每个任务可以对相关的外界产生的事件或是异常响应。而RTOS的核心功能就是管理各个任务,并建立起任务和外界事件的联系。
一个典型的任务示意如下:
void Task()
{
for (;;)
{
//Do something initial;
OSFlagPend();
OSMboxPend();
OSMutexPend();
OSQPend();
OSSempend();
OSTaskPend(priority);
OSTaskDel(priority);
OSTimeDly();
/* application code */
}
}
首先任务应该包含一个无限循环,这个循环意在一直处理跟任务自己感兴趣的事件。可以看到上面的示意代码里有一串的Pend调用,这些Pend函数就是每个RTOS都会提供的,使用了这些Pend调用后RTOS就可以让这个任务与它感兴趣的事件建立起联系。如果有它感兴趣的事件发生,就会向下执行到application code来处理这个事件。处理完以后又进入循环回等待它感兴趣事件的状态,直到有它感兴趣的事件发生,然后又处理,如此周而复此。如果没有这个任务感兴趣事件发生会怎么呢?那RTOS就会挂起这个任务,去执行其它的任务处理其自身感兴趣的事件。如果没有任何事件发生会怎么样呢?这种情况下通常RTOS会运行一个自建的任务TaskIdle(), 这个任务什么事也不干,通常是给一个整数进行自加动作。
上面就是嵌入式软件开发最根本的东西,但RTOS是究竟是怎样工作的呢?后续日子里我们再一起学习分享吧。^0^