RVDS编译高效代码 RVDS compile efficient code

263 篇文章 2 订阅

ARM公司开发的编译工具从诞生一刻起,就成为业界针对ARM处理器最有效率的工具,因为它是总结了无数经验技巧,由ARM处理器的开发者ARM公司推出的开发工具。而许多前人的经验技巧就隐藏在编译器的某个角落里,等待着你去发现并加以利用。
    本文就将与大家分享一些鲜为人知,但却可能使你大吃一惊的ARM编译、链接工具使用方法。

1.系统高效的关键字
    自从可编程的处理器出现之后,软件程序员就没有停止过对代码优化、代码高效的追求。在当今世界,电子产品领域每天都在更新,竞争异常激烈,单纯地追求系统能够正常运行、得到正确的结果,是远远不够的。现实要求系统开发人员在产品中实现最优化的代码。那么“高效”的具体含义是什么呢?站在开发者的角度,可以简单地归纳为以下几点:代码执行速度、代码密度、系统吞吐量和系统的功耗。毫无疑问,这4点正是衡量一个产品是否成功所需的关键字,而且这四者之间还有很多相互影响或者相互促进的联系。
    RVDS是ARM公司继SDT与ADS1.2之后主推的新一代开发工具,目前最高版本是3.0。它由RealView编译器(RVCT)、RealView汇编器(armasm)、RealView链接器(armlinker),以及RealView调试器(RVDebugger)组成。值得一提的是,在以上所提到的关键字中,有很多(如代码密度的提升、代码执行速度的提高)都可以由ARM开发工具RVDS自动实现,而不需要软件开发人员花费过多的时间手动优化高级语言代码。这也正是RVDS的优势所在。
    由于很多嵌入式软件工程师都对ARM的开发工具RVDS非常熟悉,很多OEM厂商都在使用ARM 
    RVDS作为主要开发工具进行产品开发。所以本文将重点介绍ARM 
RVDS工具的简单内部工作机理,这部分内容常常会被忽略,而掌握了这些内容很可能会帮助我们编写出更高效的代码。

2.使用RVDS编译器实现高效代码
   RVCT是RVDS的编译工具,它可以提供多种优化级别,帮助开发人员完成代码密度与代码执行速度上的不同层次优化。此外,RVCT的很多编译特性还可以帮助开发人员进一步提升代码效率。
   2.1 RVCT的优化级别与优化方向
    提到RVCT就不能不提armcc的4个优化级别和2个编译选项,分别是-O1、-O2、-O3、-O4,以及-Otime、-Ospace。-Ospace和-Otime负责提供代码优化的大方向,告知编译器编译任务的主要目标是代码密度(-Ospace)还是代码性能(-Otime);而-O1、-O2、-O3、-O4则分别代表4种逐次递进的不同优化级别。
(1)Ospace还是Otime?
    显然代码密度与代码执行速度在很多情况下是一对矛盾体。以下面的代码为例。例1中左右两段代码可以完成相同的任务,但是左边的有较高的代码密度,右边的则有较高的执行速度。因为当expr = 0且标志循环结束时,右边的代码可以顺序执行下去;而左边代码必须先跳转至循环体首部判断expr的值,随后再跳转至循环体尾,继续执行下一条指令。
   例1 代码执行速度与代码密度的对比。
        while (expr) {if (expr) do {
            do
          body;{ body; }
            while (expr);
          }  }
    那么什么时候使用Otime,什么时候使用Ospace呢?这需要开发人员根据系统实际需求来决定,最好是在两者之间找到一个合适的平衡点,而不是单纯地追求高速度或者小尺寸。也就是针对不同的代码模块根据其特性分别使用不同的编译选项。
    此外,RVCT编译器支持很多非常有用的编译选项,如--no_inline(取消所有代码的内联函数)、--split_ldm(限制LDM/STM指令的最大操作寄存器数目)、--split_sections(将每个函数,而不是源文件,作为一个编译单元进行操作)等。
    编译器的所有这一切都可以严格满足开发者的要求,帮助开发人员得到系统真正需要的优化过的代码。
   (2)O3还是O2?
    老的开发工具(如ADS1.2)中,只有3种递进的代码优化级别。对应3种编译选项,即-O0(Minimum optimization)、-O1(Restricted optimization)和-O2(High optimization )。使用-O0编译选项时,RVCT编译器只对代码进行最基本的优化操作,编译结束后用户得到的代码与用户手写源代码之间的差距很小。这种特性的主要作用是方便用户在程序开发阶段的调试工作,避免由于优化而产生的调试屏障。此外,很多资深软件工程师偏向于手写优化代码,在这种情况下,由于代码已经被优化过,可以使用-O0编译选项以减小RVCT的工作量,节省编译链接的时间。
    -O1与-O2则是相对于-O0更加高级别的编译优化选项。前者提供有限的优化,后者可对代码进行较大程度的优化改进操作。RVDS中新增加了-O3(Maximum optimization)编译选项,它可以最大程度地发挥RVCT编译器的优势,将代码编译成最优。-O3与-O2都是较高级别的编译优化选项,但-O3的主要优势有以下几点。当用户使用 O3选项时: 
   编译器会自动对代码进行髙阶标量优化,即编译器根据代码特点,针对循环、指针等进行髙阶优化;编译器会把尽可能多的函数编译为内联(inline)函数;multifile compilation功能被自动使能。
   (3)对于循环与指针的髙阶优化
     当编译选项为-O3 
    -Otime时,RVCT会根据代码的具体情况,针对循环、指针等部分进行髙阶优化,如循环解开、融合、位置调整、指针优化等。以例2的函数为例。
    例2 一段简单的C循环函数,在循环中含有数组指针调用。
      CodeA
      void increment(int *restrict b, int *restrict c) {
        int i;
        for (i=0; i<100; i++) {
          c[i]=b[i] + 1;
        }
      }
      CodeB
        void increment(int *b, int *c) {
          int i;
          int *pb, *pc;
          int b3, b4;
          pb=b-1; pc=c-1;
          b3=pb[1];
          for (i = (100 / 2); i != 0; i--) {
            b4 = *(pb += 2);
            pc[1] = b3 + 1; 
            b3 = pb[1]; 
            *(pc += 2) = b4 + 1;
          } 
        }
   仔细观察可以发现,CodeA与CodeB可以实现同样的功能,即将数组b的每个成员加1赋值给数组c的对应成员。但是与CodeA相比,CodeB具有更高的执行速度。主要体现在: 
① 循环100次变成了循环50次,减少了跳转次数;
② 数组变成了指针,减少了每次计算数组偏移量的指令;
③ 微调了不同代码操作的执行顺序,减少了流水线stall的情况
④ 循环从++循环变成了--循环,可以使用ARM指令的条件位,为每次循环减少了一条判断指令。
    很多程序员就是通过手写不同的C代码,提高了代码执行效率。在RVDS中,使用-O3 -Otime编译选项,RVCT会自动帮助程序员进行这些髙阶标量优化,即直接将CodeA优化成以前由CodeB才能得到的汇编代码。虽然优化之后函数的代码尺寸大于原先的函数,但是执行速度也大大提高。经过统计,使用EEMBC benchmarking,-O3编译选项编译得到的最终代码平均性能相对于 O1可以有10%的提升,而总体代码尺寸只增加了1%。
      
2.2 multifile compilation
     按照传统的编译方式,先把各个C或C++文件单独编译成.obj文件,再将这些目标文件链接在一起。虽然在编译单独的C或C++文件时,编译器会充分发挥其优化特性;但此时编译器无法关注到大量的C或C++文件接口之间可以优化的部分。所以在传统的编译结果里,还有许多优化的余地。如何才能让编译器同时关注和编译所有的源代码呢?
     multifile compilation是RVDS一个较新的特性,它可以帮助开发人员将所有的源文件作为一个compilation unit进行编译,并最终生成一个大的目标文件。mutifile compilation给软件开发人员带来的直接优势有以下几点: 
     ① 增大了inline的可能性。由于inline只能发生在一个compilation unit中,所以在没有使用mutifile compilation时,inline只能发生在一个源文件范围内。multifile compilation将一个compilation unit扩大到了所有源文件的范围上,所以直接增加了inline发生的几率。
     ② 增大了基地址与函数间优化的可能性。同inline一样,所有的基地址与函数间的优化也必须在一个compilation unit中,随着conpilation unit的扩大这种优化的可能性也增加了。
     ③ 降低了scatter file的复杂性

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是使用RVDS编写的ARM的UART通信代码示例: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include "stm32f10x_usart.h" #define USART1_TX_PIN GPIO_Pin_9 #define USART1_RX_PIN GPIO_Pin_10 #define USART1_GPIO GPIOA #define USART1_CLK RCC_APB2Periph_USART1 #define USART1_GPIO_CLK RCC_APB2Periph_GPIOA #define USART1_IRQn USART1_IRQn #define RX_BUFFER_SIZE 256 volatile uint8_t rxBuffer[RX_BUFFER_SIZE]; volatile uint8_t rxReadIndex = 0; volatile uint8_t rxWriteIndex = 0; void USART1_IRQHandler() { if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) { rxBuffer[rxWriteIndex++] = USART_ReceiveData(USART1); if (rxWriteIndex == RX_BUFFER_SIZE) { rxWriteIndex = 0; } } } void USART1_Init(uint32_t baudRate) { USART_InitTypeDef USART_InitStructure; GPIO_InitTypeDef GPIO_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; RCC_APB2PeriphClockCmd(USART1_CLK | USART1_GPIO_CLK, ENABLE); GPIO_InitStructure.GPIO_Pin = USART1_TX_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(USART1_GPIO, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = USART1_RX_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(USART1_GPIO, &GPIO_InitStructure); USART_InitStructure.USART_BaudRate = baudRate; USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_InitStructure.USART_StopBits = USART_StopBits_1; USART_InitStructure.USART_Parity = USART_Parity_No; USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; USART_Init(USART1, &USART_InitStructure); NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); USART_Cmd(USART1, ENABLE); USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); } void USART1_Write(uint8_t data) { while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); USART_SendData(USART1, data); } void USART1_WriteString(char* str) { while (*str) { USART1_Write(*str++); } } void USART1_ReadString(char* str) { uint8_t data; do { while (rxWriteIndex == rxReadIndex); data = rxBuffer[rxReadIndex++]; if (rxReadIndex == RX_BUFFER_SIZE) { rxReadIndex = 0; } *str++ = data; } while (data != '\0'); } int main(void) { char buffer[100]; USART1_Init(9600); while (1) { USART1_ReadString(buffer); if (strcmp(buffer, "Hello") == 0) { USART1_WriteString("Hi there!\n"); } } } ``` 这段代码初始化了USART1,配置了USART1的GPIO和中断,并提供了一些函数来读取和写入数据。在主循环中,代码读取串口输入并根据输入进行响应。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值