高效C编程(上) 基础数据类型,Loop,寄存器分配,函数调用,指针别名 ARM
来源:互联网 发布:如何入侵网站修改数据 编辑:程序博客网 时间:2024/06/05 05:23
导读:
本章是基于ARM讲述了如何在C编程中提高代码的效率。涉及到五部分:
第一部分:在局部变量中基础数据类型的使用。
第二部分:循环结构的优化
第三部分:Register allocation
第四部分:函数调用
第五部分:Pointer aliasing
1.基础C数据类型
本文讲述了C编译器遇到的一系列问题,在理解这些问题的基础上,写出更快运行速度和更小的尺寸的代码。
C优化和编译概述
- 为了写出高效C代码,我们需要关注3个方面。1:C编译器保守的代码部分 2:C编译器所依赖的处理器架构的限制 3:特定C编译器的限制
- 本文大部分涉及到前2个方面:这适用于ARM C compiler。而第三个方面是取决于compiler vendor and compiler revision.所以我们不过多讲述。
基础C数据类型
- ARM处理器用于32位的寄存器以及32-bit data processing operations.ARM architecture is a RISC load/store architecture.in other words, you must load or store values from memory into registers before acting on them.ARM没有提供arthmetic or logical instruction to manipulate values in memory directly.
- 在早期ARM架构中提供了hardware support 用于加载和存储unsigned 8-bit和unsigned or signed 32-bit values.
- 在ARM中加载值转变为int类型是不需要额外的指令的。LDR等都是直接用于32Bit value的。8-bit 16-bit等都需要先转换为32-bit数据。此外int转换为smaller type does not cost extra instruction on a store.
- Because ARM processor were not good at handling signed 8-bit or any 16-bit values.Therefore ARM C compiler define char to be an unsigned 8-bit value, rather than a signed 8-bit values in many other C compilers.编译器也提供了如果你想使char变为signed类型的,在gcc中可以加上 -fsigned-char开启。在armcc则是-zc
local variable type
- 绝大多数ARM data processing operations are 32-bit only.所以要尽量避免使用short和char,因为这2种类型会产生不必要的转换类型的汇编代码而降低了效率。因此要尽可能的使用32-bit的数据类型,int, long,即使你需要复制一个8-bit,16-bit的值,也要尽可能用int or long.
short checkssum_v4(shor * data){ unsigned int i; int sum = 0; for(i = 0; i < 64; i++) { sum += *(data++); //如果采用: (效率低的代码) //short sum; sum = (short)(sum+data[i]); //data[i]内部运算的时候就是用了ADD LDRH2个汇编代码,而这个表达式(sum+data[i]结果就是int类型的,所以还需要(short)(sum+data[i]) } return (short)sum;}/*本段C代码,就是用了3个技巧来提高效率。1.首先循环的i使用了int而不是char之类的。2.其次避免使用data[i]的代码,通过*(data++)减少了一条用于找到&data[i]的汇编代码。3.最后,sum使用int在返回的时候才将其转为short,避免了运算过程中多余的转换。*/
Function Argument T ypes
- 函数的参数在传参的时候也会面临转换的问题。gcc采用了严谨的做法,在进入函数的时候会将参数先转换为需要的参数类型。然后再进行处理。最合理的做法是函数参数也是使用int或者long。
unsigned versus signed types
在进行addition,subtraction and multiplication的时候signed和unsigned是一样的。在进行division的时候,使用unsigned的类型是更高效的。因为signed type在除法的时候因为有符号位,不能单纯的a >> 1,在是负数的时候要先+1,再进行>>1。然而unsigned value却没有这种问题。
Besides
当在main memory使用全局变量和数组entries的时候,使用smallest size type是更好的。尽可能采用显性的类型转换。
2.C循环结构
固定长度的循环
不高效的反面代码
for(i = 0; i < 64; i++);//在成为汇编时指令需要3个
高效代码
for(i = 64; i!=0 ;i++); //汇编指令会减少为2个(没有了需要与64比较的代码)
- 有人会说可不可以将
i!=0
换成i>0
。实际上后者是低效和不安全的。在实际中i!=0
会等效于先i-1,再进行i!=0的比较
。并且在遇到i=-0x80000000的时候,i-1会导致i=0x7fffffff,这在i>0的情况会导致循环没有终止,而i!=0则不会遇到这种情况
变量长度的循环
如果可以确定数组不为空,那么可以将while()形式变为do-while()形式,能够节省循环前判断N不为0的指令。
loop unrolling
在ARM7和9中,subtract需要1个cycle而branch需要3 cycle。在循环中展开代码,可以提高效率。
要点:
* 仅仅在unroll loop 对APP整体性能重要的时候才展开loop。此外,unroll会增加代码尺寸却只有一点性能的提高。并且,unroll通过占据了在cache中更重要的代码,甚至能降低性能。
* 另外,当loop body很大的时候,如果unroll只会提升1%的性能,却会增加很多空间消耗,这是非常不可取的。
* 如果要使用unroll loop我们最好要保证你展开的代码是可以整除的(可以是4或8的整数倍 。如果不能保证,则可以增加额外的代码来处理,比如下面的代码:
for(i = N/4; i!=0; i--) { sum += *(data++); sum += *(data++); sum += *(data++); sum += *(data++); } for(i = N&3; i!=0; i--)//处理余下的代码 { sum += *(data++); }
3.Register allocation
Efficient Register Alloaction
* 函数的内层循环最大的变量数量不要超过12个
* 尽量让最重要的变量在最内层的循环中使用
4.Function Calls
- 前4个int参数被放置在ARM的寄存器r0,r1,r2,r3中。剩下的参数会放入到full descending stack中。
- longlong和double 参数会存放在连续的2个寄存器中,返回值会存放在r0,r1中。
- C++中第一个参数是this指针,所以只剩下3个参数。如果C超过了4个参数,C++超过了3个,最有效的办法是将其整合到一个结构体中。
- 可以将C function(较小的)和其调用者放到同一个文件中。C compiler may inline the code in the caller。这样可以完全移除function call overhead. 此外也可以在较小的函数前使用inline关键字,这样可以提高效率。但是如果对较大的函数使用_inline会大幅度提高代码尺寸,从而降低效率。
5.Pointer aliasing
使用局部变量保存参数中指针所指向的值
- 在函数中,编译器不知道哪个指针是别名的,哪个不是。所以会出现多次复制参数指针所指向的值。如下面的代码:
void timer(int * timer1, int * timer2, int *step){ *timer1 += *step; *timer2 += *step;//这里你可能会觉得汇编指令仅仅复制了一次*step的值。实际上复制了2次*step的值//最高效的办法就是先用一个局部变量存放*step的值。}高效:void timer(int * timer1, int * timer2, int *step){ int temp = *step; *timer1 += temp ; *timer2 += temp ;}
使用局部变量保存局部变量的地址
int data;int sum = 0;...data = getdata(&sum); //要避免直接使用变量的地址。sum++;//因为compiler担心这里有pointer aliasing,所以会在每次改变sum值的时候,都会read and write sum form stack。这样效率很低下。
如果不得不使用局部变量的地址。最好先用一个变量来保存sum的地址,然后使用,比如:
int data;int sum = 0;int * sum_addr = ∑...data = getdata(sum_addr ); //能有效提高效率sum++;
- 高效C编程(上) 基础数据类型,Loop,寄存器分配,函数调用,指针别名 ARM
- ARM寄存器别名
- arm寄存器别名
- arm寄存器别名
- arm寄存器别名
- ARM寄存器别名
- C语言编程基础-16动态内存分配 二级指针做形参 函数指针
- ARM寄存器别名及作用
- ARM寄存器的别名+APCS
- ARM下高效C编程
- ARM下高效C编程
- 高效ARM C编程(中)
- 高效 ARM C编程(下)
- ATPCS和内嵌汇编:arm处理器上函数调用寄存器的使用规则
- (三)C语言基础(数组,内存分配,函数指针)
- C语言函数指针 与typedef别名
- arm编程,关于函数调用形参实参在通用寄存器和栈帧里的对应关系。用汇编透视c语法操作
- 【C/C++语言基础学习】在主函数的定义的指针数组、二维数组通过三级指针在被调用函数分配内存
- 依次加载listview的每一个item实现动画的效果
- 关于vs2012按下键盘有提示但是按下enter不能选中所选提示
- [Android实例] MQTT消息推送,即时通讯
- 屏幕监听
- ImageCoverFlow
- 高效C编程(上) 基础数据类型,Loop,寄存器分配,函数调用,指针别名 ARM
- Android 快速开发系列 打造万能的ListView GridView 适配器
- Visual C++ Tips: 用 IP Helper 获得网络接口的友好名称(Friendly Name)
- JS----几种过滤重复的数组的方法
- 【项目练习】遇到的问题和解决方法
- 数据库设计步骤--了解用户需求:向客户请教
- window.location.reload();页面实现跳转和刷新
- 壳的编写(3)-- 编写壳(Stub)部分(2)
- 聚类算法:凝聚层次聚类