如何在ARM下实现高效C编程_10个关键点给你答案

发布者:gamma14最新更新时间:2020-08-31 来源: elecfans关键字: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%60);

  }

  转换成:

  uint counter2(uint count)

  {

  if (++count 》=60)

  count=0;

  return (count);

  }

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


  十、内联函数和内嵌汇编

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


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

关键字:ARM  C编程  关键点 引用地址:如何在ARM下实现高效C编程_10个关键点给你答案

上一篇:一种基于ARM的嵌入式系统开发的方案详细讲解
下一篇:怎么区分ARM Cortex系列处理器

推荐阅读最新更新时间:2024-11-09 22:10

linux-arm开发环境简单配置
关于linux-arm开发环境简单配置是ARM学习的第一步,很多初学者会在这问题上纠结很久都不能配置好开发环境。推荐大家看一下韦东山视频,讲得很详细,代码基本上都会给你解释(很多视频都不会给你解释),适合初学者。 fedora8开发环境简单配置 linux-arm开发环境简单配置 Window上: 安装终端软件 SecureCRT,putty等 安装ftp软件 cutftp等 配置串口工具 超级终端 安装tftp服务,也可以使用linux上的tftp。 linux上: 1,ssh 默认情况下,ssh在fedora8下应该是安装的,那么就无需安装了。 可以如下查询: rpm -qa openssh 如果显示openssh-4
[单片机]
基于ARM的卷烟32位码防伪识别系统的设计与实现
为了保证消费者利益,卷烟防伪技术越来越受到烟草行业的重视,烟草工商企业不断寻求技术含量高、保密性强的新型防伪技术和防伪产品。目前常见的卷烟防伪技术有包装新型激光全息防伪标识和防伪技术光致变色油墨等。一些卷烟产品采用数码防伪技术,如部分“芙蓉王”香烟在每一盒(条)产品透明纸上喷印有一组惟一性的18位产品防伪数码(即产品的身份代码),使造假者无法批量假冒,对该数码进行查询,便可获得产品真伪信息。该方法可以确定该产品是否出自正规厂家,但无法得知卷烟是由哪家零售户出售的,且只适用于特定品牌的香烟。因此,本文利用国家烟草专卖局对每条卷烟所编32位代码进行查询,32位代码是每条卷烟的惟一身份代码,与零售户信息存在对应关系,通过32位码可获取条
[单片机]
Holtek新一代 Cortex-M0+ 无刷直流马达专用单片机
Holtek 推 出 新 一 代 Arm® Cortex®-M0+ 无 刷 直 流 马 达 控 制 专 用 单 片 HT32F65232,适合 Hall sensor 或 Sensor-less 1-shunt FOC 以及方波 Sensorless 控制。频率最高可达 60MHz,具备 2.5V~5.5V 宽电压操作,系统电压采用5V 可带来更高的模拟信号分辨率及马达驱动时不易受到噪声干扰的好处,具备高效能、高性价比及高整合度特色。适合电动滑板车、抽油烟机、吸尘器、各型泵类、扇类等等。 HT32F65232 的 Flash 容 量 为 32KB,SRAM 容 量 为 4KB。 针 对 1-Shunt FOC及方波 Senso
[单片机]
ARM芯片选择的一般原则
1.1 ARM芯核 如果希望使用WinCE或Linux等操作系统以减少软件开发时间,就需要选择ARM720T以上带有MMU(memory management unit)功能的ARM芯片,ARM720T、StrongARM、ARM920T、ARM922T、ARM946T都带有MMU功能。而 ARM7TDMI没有MMU,不支持Windows CE和大部分的Linux, 但目前有uCLinux等少数几种Linux不需要MMU的支持。 1.2 系统时钟控制器 系统时钟决定了ARM芯片的处理速度。ARM7的处理速度为0.9MIPS/MHz,常见的ARM7芯片系统主时钟为20MHz- 133MHz,ARM9的处理速度为1.1MI
[单片机]
Maxim宣布MAX32600MBED成为ARM mbed最新成员
Maxim Integrated的低功耗MAX32600MBED提供集安全性与高精度模拟性能于一体的IoT方案。 Maxim Integrated Products, Inc. (NASDAQ: MXIM)宣布MAX32600MBED成为ARM mbed 物联网设备平台项目的最新成员,该平台能够帮助mbed工程师和IoT开发人员快速开发基于MAX32600微控制器(MCU)嵌入式系统。 ARM mbed操作系统是一种专为物联网 (IoT) 中的 物体 设计的开源嵌入式操作系统。该操作系统包含您基于ARM Cortex-M微控制器开发连接产品所必需的全部功能,非常适合涉及智能城市、智能家庭和穿戴式设备等领域的
[物联网]
Maxim宣布MAX32600MBED成为<font color='red'>ARM</font> mbed最新成员
PLC编程简单的启停与自锁电路分析
在编制PLC程序时,不管是新手还是老手,都会犯下面的这种低级错误。因为这种错误是非语法上的,所以用编程软件也不能检查出错误之处。此错误一旦发生,自己有时还很难发现,直至上机调试运行时,所控设备不能运行或运行到某个位置停止不前,才察觉出来有问题,再对PLC程序逐条逐句查找分析,或采取对程序逐条逐句执行,费时费工。 那么究竟是什么问题易使我们犯下这种低级错误呢? 继电器电气控制的固有思维,在编制程序时,某个或几个输入点采用物理常闭触点(如停止开关、行程限位开关),在程序中,仍延续继电器电气控制方式编制,即仍采用常闭接点作为导通条件使用。 下面用一个简单的启停与自锁电路示例来说明: 根据上图编制的不能运行的错误PLC程序如下
[嵌入式]
PL<font color='red'>C编程</font>简单的启停与自锁电路分析
Ashling宣布RiscFree支持Arm和RISC-V的同时调试
Ashling是一家嵌入式开发工具提供商,日前宣布在Ashling的RiscFree中为异构多核Arm和RISC-V开发提供高级支持IDE和调试器。该解决方案允许复杂的多架构、多核异构项目的开发人员使用连接到多核目标设备的单个调试探针。 越来越多的设计师开始转向多核设计,并将不同的处理器架构(即异构内核)集成到单个SoC上。随着工业、汽车、医疗、电信、人工智能和物联网设计的发展和需求,SoC设计变得越来越复杂,需要组合多个CPU核(如Arm和RISC-V)已成为一种必须,而不仅仅是奢侈品,以满足新兴市场的需求,包括功能、性能和功耗要求。 Ashling RiscFree IDE现在为嵌入式开发市场提供了一个工具集,使用一个
[半导体设计/制造]
Arm ToTal Design正在大获成功
编译自tomshardware 当Arm在2023年10月推出其Total Design计划时,其成功并未得到保证。然而,根据Arm的一篇博文,Total Design确实取得了成功。Arm的Total Design生态系统快速扩展,在一年内规模翻了一倍。该生态系统目前涉及30多家公司,包括三星代工厂(Samsung Foundry)、ADTechnology和Rebellions,他们合作开发了一款基于三星2纳米级工艺技术的AI解决方案。 在该生态系统中,最引人注目的合作之一是Arm、三星、ADTechnology和Rebellions的合作,他们共同开发了一款基于Neoverse CSS V3的AI CPU芯片平台。这
[半导体设计/制造]
小广播
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

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

换一换 更多 相关热搜器件

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

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