ARM 架构下的编码和优化
2. volatile 关键字
* 中断中使用的全局变量必须使用 volatile 修饰
* 读取外设的变量必须使用全局变量
* 线程间通信或者同步用变量, 使用volatile修饰, 避免cpu从缓存中取数
3. 循环性能优化
* 如果循环次数是确定的, 循环截止条件使用变量而不要调用函数
* 循环中, 迭代变量更新, 尽量使用局部变量而不要使用解指针
* 循环体中, 操作尽量简单
* 循环中从内存中读取数据, 使用循环展开优化取数
* 循环中动作使用循环展开, 充分应用 流水线超量 #pragma unroll(4)
* 循环嵌套, 尽量考虑时间局部性和空间局部性, 增大缓存命中率
* 在 O1优化中, 使用-fvectorize 启用矢量优化, 调用SIMD指令
* 循环条件 1) 循环终止条件简单, 2) 判断循环变量是否等于0终止循环. 3) 循环变量使用 unsigned int 类型
* 编译器可能会优化无副作用的无线循环, 推荐使用: while (1) { __asm volatile("wfe");} 避免被编译器优化
4. 内联函数
5. 堆栈使用
* apcs标准, 函数入参不要超过 4个int32, 这样可以完全使用寄存器传参, 否则多余的参数会通过栈来传输. 传参效率低.
* 结构体参数, 通过结构体指针传参. 如果使用结构体传参, 参数通过栈传递, 传参效率低.
* 结构通常分配给堆栈。在堆栈上保留一个等效于 sizeof(struct) 的空间,填充为 <n> 字节的倍数,其中 <n> 对于 AArch64 状态为 16,对于 AArch32 状态为 8.
* 如果局部整数或浮点变量溢出(即未分配给寄存器),则会为它们分配堆栈内存。
* 如果数组的大小在编译时已知,则编译器会在堆栈上分配内存。同样,在堆栈上保留了相当于填充到 <n> 字节倍数的 sizeof(array) 的空间
* 可变长度数组的内存在运行时在堆上分配.
* 链接时, 使用 --info=stack 查看堆栈使用情况.
** 减少堆栈使用的方法
* 编写只需要几个变量的小函数
* 避免使用大型局部结构或者数组
* 避免递归
* 使用尾调用
* 最小化函数中每个点, 在任何给定时间使用的变量数
* 使用C块作用域的语法, 并仅在需要变量的地方声明变量, 以便不同的作用域可以使用相同的内存
* 支持使用C99 变长数组特性, 而不是定义栈上或者全局的固定长度数组
6. pack 注意事项
* 如果结构体或者复合类型指定对齐方式, 因此没有自然对齐,在这种情况下要访问此成员, 必须使用包含此成员的结构;
不得直接使用此成员的指针来访问, 因为此成员的指针可能未对齐, 而某些指令始终需要字对齐的地址。
7. 关于代码大小和优化
* 优化循环条件可以提高代码大小和性能. 特别是, 计数器递减为0的循环通常比计数器递增的循环生成更小更快的代码.
* 通过减少循环迭代次数手动展开循环, 但是增加每次迭代中完成的工作量, 效果是牺牲代码大小换区运行效率.
* 减少对象和库中的调试信息可以减小映像的大小
* 使用内联函数可以在代码大小和性能之间进行权衡
* 使用内部函数可以提高性能
8. 优化传参效率
* 确保函数采用4个或者更少的参数
* 如果函数传递参数超过4个, 确保函数执行大量任务, 以抵消传参带来的消耗
* 结构体传参时, 尽量使用结构体指针传参
* 尽量减少 long long参数的数量, 这些参数使用2个参数寄存器, 并且必须在偶数寄存器上对齐
* 使用软件浮点时, 尽量减少double参数的数量
9. 调试体验
* arm 推荐调试优化级别为 o1
10. aapcs 规定的调用方函数和被调用函数之间的协议
* 寄存器使用R0-R3将参数值传递给被调用函数, 并在堆栈上传递后续参数.
* 寄存器R0 将return值传递回调用方函数
* 调用方函数必须保留R0-R3, R12; 因为被调用方函数允许修改这些寄存器上的值
* 被调用函数必须保存R4-R11和LR, 因为被调用函数不允许损坏这些寄存器上的值