有这10个关键点,在ARM下高效C编程没问题!

发布者:快乐的天使最新更新时间:2018-10-09 来源: eefocus关键字:ARM  C编程 手机看文章 扫描二维码
随时随地手机看文章

通过一定的方法来编写C程序,可以帮助C编译器生成执行速度更快的ARM代码。下面就是一些与性能相关的关键点:

1.对局部变量、函数参数和返回值要使用signed和unsigned int类型。这样可以避免类型转换,而且可高效地使用ARM的32位数据操作指令。

2.最高效的循环体形式是减计数到零(counts down to zero)的do-while循环。

3.展开重要的循环来减少循环的开销。

4.不要依赖编译器来优化掉重复的存储器访问。指针别名会阻止编译器的这种优化。

5.尽可能把函数参数的个数限制在4个以内。如果函数参数都存放在寄存器内,那么函数调用就会快得多。

6.按元素尺寸从小到大排列的方法来安排结构体,特别是在thumb模式下编译。

7.不要使用位域,可以用掩码和逻辑操作来替代。

8.避免除法,可以用倒数的乘法来替代。

9.避免边界不对齐的数据。如果数据有可能边界不对齐,那么就要使用char *指针类型来访问。

10.在C编译器中使用内嵌汇编可以利用到C编译器本来不支持的指令或优化。

一、 数据类型使用上的优化

1.局部变量
一个char类型的数据比int类型的数据占用更小的寄存器空间或者更小的ARM堆栈空间。这两种设想对于ARM来说,都是错误的。所有的ARM寄存器都是32位的,所有的堆栈入口至少是32位的。当我们执行i ,要利用当i=255后,i =0这个条件时,可以把它定义为char类型。

2.函数参数
尽管宽和窄的函数调用规则各有其优点,但char或short类型的函数参数和返回值都会产生额外的开销,导致性能的下降,并增加了代码尺寸。所以,即使是传输一个8位的数据,函数参数和返回值使用int类型也会更有效。

总结:

1)对于存放在寄存器中的局部变量,除了8位或16位的算术模运算外,尽量不要使用char和short类型,而要使用有符号或无符号int类型。除法运算时使用无符号数执行速度更快。

2)对于存放在主存储器中的数组和全局变量,在满足数据大小的前提下,应尽可能使用小尺寸的数据类型,这样可以节省存储空间。ARMv4体系结构可以有效地装载和存储所有宽度的数据,并可以使用递增数组指针来有效地访问数组。对于short类型数组,要避免使用数组基地址的偏移量,因为LDRH指令不支持偏移寻址。

3)通过读取数组或全局变量并赋给不同类型的局部变量时,或者把局部变量写入不同类型的数组或者全局变量时,要进行显式数据类型转换。这种转换使编译器可以明确、快速地处理,把存储器中数据宽度比较窄的数据类型扩展,并赋给寄存器中较宽的类型。

4)由于隐式或者显式的数据类型转换通常会有额外的指令周期开销,所以在表达式中应尽量避免使用。Load和store指令一般不会产生额外的转换开销,因为load和store指令是自动完成数据类型转换的。

5)对于函数参数和返回值应尽量避免使用char和short类型。即使参数范围比较小,也应该使用int类型,以防止编译器做不必要的类型转换。

二、C循环结构

在ARM上,一个循环其实只要2条指令就足够了:

一条减法指令,进行循环减法计数,同时设置结果的条件标志;
一条条件分支指令。

这里的关键是,循环的终止条件应为减计数到零,而不是计数增加到某个特定的限制值。由于减计数结构已存储在条件标志里,与零比较的指令就可以省略了。由于不用i作为数组的下标索引,采用减计数就没有任何问题了。

总而言之,无论对于有符号的循环计数值,都应使用i!=0作为循环的结束条件。对有符号数i,这比使用条件i>0少了一条指令。

总结:
1) 使用减计数到零的循环结构,这样编译器就不需要分配一个寄存器来保存循环终止值,而且与0比较的指令也可以省略。

2) 使用无符号的循环计数值,循环继续的条件为i!=0而不是i>0,这样可以保证循环开销只有两条指令。

3) 如果事先知道循环体至少会执行一次,那么使用do-while循环要比for循环要好,这样可以使编译器省去检查循环计数值是否为零的步骤。

4) 展开重要的循环体可降低循环开销,但不要过度展开,如果循环的开销对整个程序来说占的比例很小,那么循环展开反而会增加代码量并降低cache的性能。

5) 尽量使数组的大小是4或8的倍数,这样可以容易的以2,4,8次等多种选择展开循环,而不需要担心剩余数组元素的问题。

三、寄存器分配

高效的寄存器分配:应该尽量限制函数内部循环所用局部变量的数目,最多不超过12个,这样,编译器就可以把这些变量都分配给ARM寄存器。

四、函数调用

4寄存器规则:带有4个或者更少参数的函数,要比多于4个参数的函数执行效率高得多。对带有少于4个参数的函数来说,编译器可以用寄存器传递所有的参数;而对于多于4个参数的函数,函数调用者和被调用者必须通过访问堆栈来传递一些参数。

如果函数体积很小,只用到很少的寄存器,那么还有一些其他的方法来减少函数调用的开销。可以把调用函数和被调用函数放在同一个C文件中,这样编译器就知道了被调用函数生成的代码,并以此对调用函数进行一些优化。

总结:
1) 尽量限制函数的参数,不要超过4个,这样函数调用的效率会更高。也可以将几个相关的参数组织在一个结构体中,用传递结构体指针来代替多个参数。
2) 把比较小的被调用函数和调用函数放在同一个源文件中,并且要先定义,后调用,编译器就可以优化函数调用或者内联较小的函数。
3) 对性能影响较大的重要函数可使用关键字_inline进行内联。

五、指针别名

定义:当2个指针指向同一个地址对象时,这2个指针被称作该对象的别名(alias)。如果对其中一个指针进行写入,就会影响从另一个指针的读出。在一个函数中,编译器通常不知道哪一个指针是别名,哪一个不是;或哪一个指针有别名,哪一个没有。

避免指针别名:
1) 不要依赖编译器来消除包含存储器访问的公共子表达式,而应建立一个新的局部变量来保存这个表达式的值,这样可以保证只对这个表达式求一次值;
2) 避免使用局部变量的地址,否则对这个变量的访问效率会比较低。

六、结构体安排

在ARM上使用结构体有2个问题需要考虑:结构体地址边界对齐和结构体总的大小。

获得高效结构体的原则:
1) 把所有8位大小的元素安排在结构体的前面;
2) 以此安排16位、32位和64位的元素;
3) 把所有数组和比较大的元素安排在结构体最后;
4) 对于一条指令,如果结构体太大而不能访问所有的元素,那么把元素组织到一个子结构体中。编译器可以维持单独的子结构体的指针。

总结:
结构体元素要按照元素的大小来排列,以最小的元素放在开始,最大的元素安排在最后;避免使用很大的结构体,可以用层次化的小结构体来代替;为了提高可移植性,人工对API的结构体增加填充位,这样,结构体的安排将不会依赖与编译器;在API的结构体中要谨慎使用枚举类型。一个枚举类型的大小是编译器相关的。

七、位域

注意事项:
1) 应避免使用位域,而使用#define或者enum来定义屏蔽位;
2) 使用整型逻辑运算AND、OR、“异或”操作和屏蔽对位域进行测试、取反和设置操作。这些操作编译效率高,还可以同时对多个位域进行测试、取反和设置。

八、边界不对齐数据和字节排列方式(大/小端)

边界不对齐数据和字节排列方式这2个问题,可使内存访问和移植问题复杂化。须考虑数组指针是否边界对齐,ARM配置是大端(big-endian),还是小端(little-endian)的存储器系统。

总结:
1) 尽量避免使用边界不对齐的数据;
2) 使用类型char *可指向任意字节边界的数据。通过读字节来访问数据,使用逻辑操作来组合数据,这样代码就不会依赖于边界是否对齐或者ARM的字节排列方式的配置;
3) 为了快速访问边界不对齐的结构体,可以根据指针边界和处理器的字节排序方式写出不同的程序变体。

九、除法

ARM硬件上不支持除法指令,当代码中出现除法运算时,ARM编译器会调用C库函数(有符号的除法调用_rt_sdiv,无符号的调用_rt_udiv),来实现除法操作。有许多不同类型的除法程序来适应不同的除数和被除数。

总结:
1) 尽可能避免使用除法。对环形缓冲区的处理可以不用除法。

2) 如果不能避免除法运算,那么尽可能考虑使用除法程序同时产生商n/d和余数n%d的好处。

3) 对于重复对同一除数d的除法,预先计算好s=(2k-1)/d。可用乘以s的2k位乘法来代替除以d的k位无符号整数除法。

4)使用2的整数次幂作除数。当2的整数次幂做除数时,编译器会自动将除法运算转换成移位运算。所以在编写程序算法时,尽量使用2的整数次幂做除数。

5)求余运算。可以将一些典型的求余运算进行转换,以避免在程序中使用除法运算。

如:

uint counter1(uint count)
{
return ( count`);
}
转换成:
uint counter2(uint count)
{
if ( count >=60)
count=0;
return (count);
}

大多数ARM处理器硬件上并不支持浮点运算。这样在一个对价格敏感的嵌入式应用系统中,可节省空间和降低功耗。除了硬件向量浮点累加器VFP和ARM7500FE上的浮点累加器FPA外,C编译器必须在软件上提供浮点支持。

十、内联函数和内嵌汇编

高效地调用函数,使用内联函数可以完全去除函数调用的开销,另外许多编译器允许在C源程序中使用内嵌汇编。使用包含汇编的内嵌函数,可以使编译器支持通常不能有效使用的ARM指令和优化方法。

内联函数和内嵌汇编最大的好处是,可以实现一些在C语言部分中通常难以完成的操作。使用内联函数要比使用#define宏定义更好,因为后者不检查函数参数和返回值的类型。


关键字:ARM  C编程 引用地址:有这10个关键点,在ARM下高效C编程没问题!

上一篇:STM32F407之TF卡HAL库的使用
下一篇:单片机、ARM、FPGA嵌入式这些有什么区别,各自特点是什么?

推荐阅读最新更新时间:2024-03-16 16:15

深度解读威盛高端ARM核心板,搭载高通骁龙820四核处理器
2017年年底,威盛推出了一款名为SOM-9X20的超紧凑型模块,搭载Qualcomm® Snapdragon™ 820四核处理器,尺寸仅8.2cm x 4.5cm。SOM-9X20模块一经问世,便引起了业界热议。 满载荣誉的Snapdragon 820处理器 相信大家对于Snapdragon 820处理器并不陌生。作为一款成绩斐然的处理器,Snapdragon 820刚问世之时,就以超强性能、低功耗、高保真和零延迟等特点成为全世界手机厂商的宠儿。紧接着Snapdragon 820处理器又因为小米5和乐视手机的CPU首发权大战,声望达到了顶点。这也充分表现了企业对其性能的肯定。 作为骁龙(Snapdrago
[嵌入式]
深度解读威盛高端<font color='red'>ARM</font>核心板,搭载高通骁龙820四核处理器
基于ARM处理器系统的机械车载监控终端研究
1 引言 近年来,随着土地资源不断的被开发利用,我国基础建设的规模也进一步扩大,工程机械市场呈现持续增长态势,但是国内的工程机械行业仍处于一种相对落后的生产方式,所以工程机械行业需要适合本行业要求的,智能的,性能优越的监控产品。嵌入式技术、总线技术和网络技术等高新技术融入到工程机械行业的监控系统中来,是十分必要的。为此,本文设计了一种基于嵌入式系统和总线技术的工程机械监控系统终端,详细讨论了监控系统终端的构成和CAN总线的设计。 2 监控系统终端的总体构成 监控终端负责前端控制系统所有单元的监控,包括安全监控、电液比例等单元并通过液晶显示器和键盘进行人机交互,同时,连接视频监控设备,随时监控现场的情况。 如图1所示,监控系
[单片机]
基于<font color='red'>ARM</font>处理器系统的机械车载监控终端研究
Cortex系列ARM核心及体系结构介绍
众所周知,英国的ARM公司是嵌入式微处理器世界当中的佼佼者。ARM一直以来都是自己研发微处理器内核架构,然后将这些架构的知识产权授权给各个芯片厂商,精简的CPU架构,高效的处理能力以及成功的商业模式让ARM公司获得了巨大的成功,使他迅速占据了32位嵌入式微处理器的大部分市场份额,甚至现在,ARM芯片在上网本市场的也大有与INTEL的ATOM处理器一较高低的实力。 目前,随着对嵌入式系统的要求越来越高,作为其核心的嵌入式微处理器的综合性能也受到日益严峻的考验,最典型的例子就是伴随3G网络的推广,对手机的本地处理能力要求很高,现在一个高端的智能手机的处理能力几乎可以和几年前的笔记本电脑相当。为了迎合市场的需求,ARM公司也在加紧研发他们
[单片机]
Cortex系列<font color='red'>ARM</font>核心及体系结构介绍
ARM MPCore --(1)
在ARM世界,MP Init目前没有一个统一的规范。(Andrew Fish said) 1. Barriers and Synchronization DSB -- Data Synchronization Barrier DMB -- Data Memory Barrier 2. Cluster Cortex-A15 MPCore,结合AMBA 4 ACE,支持多个coherent clusters. Cluster和CPU ID概念,与X86类似. 3. MPCore例子 使用Snoop Control Unit同步每個Core各自的L1 Data Cache
[单片机]
<font color='red'>ARM</font> MPCore --(1)
ARM架构下LDR、STR、MOV和伪指令LDR指令理解
ARM是RISC结构,数据从RAM到CPU寄存器之间的移动只能通过L/S指令来完成,也就是ldr/str指令。 比如想把数据从RAM中某处读取到CPU寄存器中,只能使用ldr 比如: ldr r0, 0x12345678 就是把0x12345678这个地址中的值存放到r0中。 而mov不能干这个活,mov只能在CPU寄存器之间移动数据,或者把立即数移动到CPU寄存器中,这个和x86这种CISC架构的芯片区别最大的地方。 x86中没有ldr这种指令,因为x86的mov指令可以将数据从RAM中移动到CPU寄存器中。 另外,还有一个就是ldr伪指令,虽然ldr伪指令和ARM的ldr指令很像,但是作用不太一样。ldr伪指令可以在
[单片机]
ARM发布首款可即量产的基于TSMC90纳米工艺的DDR1和DDR2存储器接口IP
Velocity DDR 存储器接口获得 TSMC IP 质量认证 ARM 公司(伦敦证交所: ARM ;纳斯达克: ARMHY )今天发布了其 Artisan 物理 IP 系列中的 ARM Velocity TM DDR1 和 DDR2 ( 1/2 )存储器接口,支持 TSMC 的 90 纳米通用工艺。 ARM Velocity DDR1/2 存储器接口是第一个通过 TSMC IP 质量安全测试的 90 纳米、可即量产的 IP 。   TSMC 设计服务市场代理总监 Kuo Wu 表示:“我们一贯致
[焦点新闻]
快速学Arm(31)--存储器寻址(4)
为了与将来的器件兼容,整个Boot ROM都被映射到片内存储器空间的顶端.在这种方式下,使用较大或较小的片内Flash块都不会影响到Boot ROM的地址.而Boot ROM的最起始的字节就是异常中断向量表.这样就可以理解其实向量表的重映射是由于Boot ROM的重映射造成的,他们是一体的. 如果代码试图访问一个保留区域地址或者未分配区域地址,ARM将产生预取指中止或者数据中止异常.我们从前面的图中可以观察到,其实ARM的地址分配并不是连续的,每种存储器之间有些地址是未被分配的,如果我们的代码中试图访问这些地址,就会产生这样的异常.
[单片机]
快速学<font color='red'>Arm</font>(31)--存储器寻址(4)
ARM汇编程序基本知识
1.汇编程序的基本组成 ARM汇编语言程序中,程序是以程序段为单位组织代码的。段是相对独立的指令或者代码序列,拥有特定的名称。段的种类有代码段、数据段和通用段,代码段的内容为执行代码,数据段存放代码运行时需要用到的数据,通用段不包含用户代码和数据,所有通用段共用一个空间。段使用AREA伪操作来定义,并且说明相关属性,如 代码段定义 AREA Init, CODE, READONLY 数据段定义 AREA Stack1,DATA,READWRITE,NOINIT,ALIGN=3 等 一个汇编程序至少应该有一个代码段,可以有零或者多个数据段。在格式上,一个汇编程序需要至少
[单片机]
<font color='red'>ARM</font>汇编程序基本知识
小广播
添点儿料...
无论热点新闻、行业分析、技术干货……
设计资源 培训 开发板 精华推荐

最新单片机文章
  • 学习ARM开发(16)
    ARM有很多东西要学习,那么中断,就肯定是需要学习的东西。自从CPU引入中断以来,才真正地进入多任务系统工作,并且大大提高了工作效率。采 ...
  • 学习ARM开发(17)
    因为嵌入式系统里全部要使用中断的,那么我的S3C44B0怎么样中断流程呢?那我就需要了解整个流程了。要深入了解,最好的方法,就是去写程序 ...
  • 学习ARM开发(18)
    上一次已经了解ARM的中断处理过程,并且可以设置中断函数,那么它这样就可以工作了吗?答案是否定的。因为S3C44B0还有好几个寄存器是控制中 ...
  • 嵌入式系统调试仿真工具
    嵌入式硬件系统设计出来后就要进行调试,不管是硬件调试还是软件调试或者程序固化,都需要用到调试仿真工具。 随着处理器新品种、新 ...
  • 最近困扰在心中的一个小疑问终于解惑了~~
    最近在驱动方面一直在概念上不能很好的理解 有时候结合别人写的一点usb的例子能有点感觉,但是因为arm体系里面没有像单片机那样直接讲解引脚 ...
  • 学习ARM开发(1)
  • 学习ARM开发(2)
  • 学习ARM开发(4)
  • 学习ARM开发(6)
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved