浅议如何提高汽车嵌入式软件运行效率(转)

任何一个ECU供应商去拿项目的时候,客户需求中一般都会明确提出CPU Load不能超过百分之多少多少(70%~80%)。OEM提这么个要求是有他的考虑的,因为在项目的前期,OEM自己也不甚确定将来是否有功能的扩展,而随着项目的进展,对零件理解的深入,他们则往往会有这样或那样的新的要求提出来。下面的这个漫画的故事,我相信在不少公司都上演过。

 

因此,怎样让软件运行的更高效一些,降低CPU Load,是软件工程师要一直记在心头的一个话题。笔者认为,软件运行效率可以从如下几个方面进行改进:

 

1   合理的Task Scheduling

嵌入式软件是有大大小小很多个任务组合起来的,需要合理的进行任务的调度,以降低CPU时间。

 

  • 比如EPS软件需要其助力特性曲线模块每1ms计算一次助力力矩,其助力特性曲线是根据车速查表得到,车速信号每10ms更新一次,我们就没有必要把查表的模块也放到1ms的任务中。

 

 

  • 软件中有1ms任务和10ms任务甚至更多,务必将10ms任务在分成10个小的任务10.1,10.2……10.10,且保证每个小任务的运行时间比较类似。避免出现在某一个10ms之内任务比较集中的现象。

 

 

2   定点化软件中合理的定标

 

2.1 在定点化软件中,定标往往是比较头疼的一个问题,数据长度选择16位的话,在涉及到传递函数相关的模型里,中间变量往往会出现精度不够的导致失真严重,因此很多同学往往就是一股脑将所有数据长度选为32位,所有变量的长度与输出一直,这样做一版来说不会有大的问题,但是32位数据的运算时间远远高于16位,会占用更多的CPU资源。笔者的做法是首先分析好输入变量的值的范围和频率范围,输出信号的精度要求之后,设定10%左右的Tolerance后用Matlab的Fix Point Tool进行参数Scaling的初步设定后,再检查一遍对个别变量的scaling进行调整。

 

2.2 在软件中,尽可能使用2^-x作为scaling,不要使用10^-x作为scaling。因为用二进制的scaling,软件可以简单的用移位进行scaling的转换,用十进制则要用到乘除法。

 

3   LookupTable与PreLookUpTable与DirectLookupTable

LookUpTable太占用运行时间,不建议使用,可以用PreLookUpTable代替,如果表的数据量很大,建议直接使用DirectTable,省去interpolation算法的时间。

 

4   尽量减少对除法的使用

除法要占用的CPU load要远远的超过乘法,一般来说16位整数乘法在1个到几个机器周期之内可以完成,而除法则要几十个机器周期。如果是变量除以常量,那么在实现的时候可以用变量乘以该常量的倒数来代替除法运算。如果是变量之间的相除,则在算法上尽量合并同类项,减少对除法的使用,例如将b/a + c/a 简化为(1/a)*(b+c),原来的表达式中有两个除法和一个加法,合并同类项以后一个除法、一个乘法、一个加法,能够节省不少运行时间。

 

5   自加自检运算符的使用

能够自加自减的地方不要用+1或-1,因为二者的汇编一般来说是有区别的,前者的效率更高。

 

使用增量操作符a++;得到的汇编:

incra ;a加1

使用数学运算a=a+1;得到的汇编:

move A,a;把a从内存取出存入累加器A 
add A,1;累加器A加1 
store a;把新值存回a

孰优孰劣一目了然。但是切记滥用自加自减,一个表达式中最多出现一个自加自减,虽然诸如x=i+++++j这样的表达式也是合理的,但太过晦涩,不要使用。

 

6   对于需要经常需要频繁调用的功能,尽量使用宏函数而不是函数

比如实现两个数中求较小者,

使用宏函数:#define min(X, Y)  ((X) < (Y) ? (X) : (Y))

使用函数函数:

Int16 min(x,y)

{ return  (((x) < (y) ? (x) : (y));}

 

使用宏函数就比函数的效率来的要高一些,因为宏函数仅仅只是将预先写好的代码替换,而在调用函数的时候,CPU需要保存和恢复当前的现场,进行压栈和出栈的操作,因此相对而言宏函数会占用更小的CPU Load。

 

但是宏函数用的不恰当,也会产生很多很多的坑,以下事项需注意:

  • 不论如何,宏函数最外面一定一定一定要加一个括号(重要的事情说三遍)

  • 宏函数的参数里不要调用函数

  • 宏参数不要自身递归

  • 两个不同的宏函数之间不要有交叉引用

 

7   变量的内存模式定义

  • Near与Far空间:对16位单片机而言,near空间是指能够通过16bit进行快速寻址的内存空间,Far空间是整个RAM空间。Near空间的访问速度要高于Far空间的访问速度。一般来说,不加特别的定义,所有的全局变量都会优先定义到near空间中。因此在进行软件开发时,应尽量将频繁访问的数据放到near空间,如每次运行周期内都会使用到的变量;将低概率事件触发的变量、数组放到Far空间。

 

  • PSRAM、DSRAM和DPRAM

    PSRAM虽然名字为程序RAM,其实也可以存储数据用。三者的访问速度为:PSRAM<DSRAM<DPRAM.

 

DPRAM(Dual Port Ram)访问速度极快,但成本又比较高,一般的单片机中只有极少的DPRAM,一般用来配置为堆栈、全局寄存器的镜像、和一些特别需要快速访问速度的变量(如单片机内的快速MAC运算单元)。

 

8   使用DMA/PEC

DMA(Direct Memory Access直接内存存取)和PEC(Peripheral Event Controller外围时间控制器)是不同的公司对同一个功能的叫法。其作用注要是让外设和存储器之间实现直接的数据交互而不需要CPU的过多干预,减少中断的次数,从而降低CPU的负载率。比较典型的应用是高速的AD采样、对一串协议信号的解析(如SENT信号等)。

 

9   逻辑运算的执行顺序

软件对a&b&c的执行顺序为必须a,b,c都为1,该指令才会全部被执行;只要a为0,该指令的执行结果直接返回为0,软件也不会去检查b和c的取值到底是多少。因此对&运算,尽量把结果为0发生频率高的条件放在前面。

反之,对于或运算,只要有一个条件为1,那么返回值就会为1,尽量把结果为1发生频率高的条件放在前面。

 

10   if条件/switch分支的执行顺序

 Switch语句是一个普通的编程技术,编译器会产生if-else-if的嵌套代码,并按照顺序进行比较,发现匹配时,就跳转到满足条件的语句执行。每一个条件/分支实现的跳转仅仅是为了决定下一步要做什么,为了提高速度,把最可能发生的情况放在第一位,最不可能的情况放在最后可以提高程序的运行效率。 另外,还可以将各种分支进行分组,在switch中嵌套switch。

 

11   For循环的嵌套

当for循环发生嵌套时,尽量将大循环放在里面,小循环放在外面,减少CPU跨切循环的次数。下面这篇文章讲得非常透彻,有兴趣的同学可以去看一下。

http://www.cnblogs.com/tolimit/p/4276844.html

 

12   Static的使用

对在函数中经常需要使用到的“不变量”,定义为Static变量;对于需要经常访问的函数,定义为static函数。提前分配好内存,节省软件开销。

 

13   iCache的使用

我们都知道程序是存储在Flash中的,需要用到该程序指令时,CPU才会去Flash进行取指,而访问Flash的速度明显是低于Ram的。iCache就是把Flash中的某一段程序代码搬到RAM中,直接在RAM中运行这段代码(就跟直接在PC机内存里面装一个Windows操作系统是一个道理)。使用iCache后,该段代码的运行效率一般能提高30%以上。但iCache的内存也是极少的,一般的应用是被频繁调用的程序放到iCache中。比如无刷电机的矢量控制一般都要求其运行周期为几十到几百个us,使用iCache就能很大程度的提高软件运行效率。

 

14   合理利用软件流水线

现在的单片机大多都使用了指令流水线技术,以提高CPU的运行速度。

以如下代码为例:

   for (i = 0; i < 100; i = i + 1)

   {

      a[i] = 10 * b[i];

      b[i] = 10 * c[i];

      c[i] = 10 * d[i];

   }

循环体内的三条指令具有相互依存的关系(a[i]=10*b[i]计算完成之前,不能对b[i]进行取指),因此编译器不能利用指令流水线进行优化。如果我们改成如下一种方式:

for (i = 0; i < 100; i = i + 4)

   {

      a[i] = 10 * b[i];

  a[i+1] = 10 * b[i+1];

  a[i+2] = 10 * b[i+2];

  a[i+3] = 10 * b[i+3];

b[i] = 10 * c[i];

      b[i+1] = 10 * c[i+1];

b[i+2] = 10 * c[i+2];

b[i+3] = 10 * c[i+3];

      c[i] = 10 * d[i];

c[i+1] = 10 * d[i+1];

c[i+2] = 10 * d[i+2];

c[i+3] = 10 * d[i+3];

   }

则编译器能够更好的利用指令流水线,节省运行时间。

 

  • 0
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值