一、背景
与Cortex-M3和M4不同,Cortex-M0/M0+不支持硬件除法,这意味着当应用程序执行除法运算时,编译器将调用C库中的除法函数进行运算。 因此,如果用户的应用程序或它调用的库中使用了除法运算,则这些被用到C库中的除法函数将会消耗一部分Flash中的空间。
在LPC800系列产品中,Flash最大为32KB, 由于Flash大小的限制,使得Flash中的每一块空间都显得弥足珍贵。为了减少C库中除法对Flash空间的消耗,LPC800的ROM中包含了一些优化的除法代码用来执行除法运算,若用户使用了对应的除法代码,则无须使用C库中的除法函数了,此时链接器便不会将C库中除法函数链接到工程中,从而实现节省Flash空间的作用。
ROM中的除法代码以“接近恒定时间”运行,不管输入和结果如何,特定的除法消耗的周期是大致相同的;而C库中除法的运算时间是不固定的,和商的位数有关。
ROM除法的运算时间可能比C库中除法的最快性能慢,但要快于C库除法的最慢情况。
二、ROM除法的使用
2.1 在Keil中使用ROM除法函数
1,直接调用ROM除法API函数
在LPC800系列产品中,都提供了ROM除法的API 接口,包括优化的有符号和无符号整数除法,及带余数的有符号和无符号整数除法,用户可以直接通过这些API接口调用这些除法函数。
下面是ROM中函数的排列方式。在0x0F00-1FF8处是ROM函数表的起始地址,ROM函数表中的第五项(偏移位置是0x10)是除法函数的入口地址表。
下面是LPC800用户手册中关于ROM除法函数的原型说明,这里包含了四个函数。
直接调用这些函数的使用方法如下:
2,重载“/”和“%”运算符
LPC804的Code Boundle Rom divide工程中提供了一个rom_div_84x.c文件,文件中的代码实现了将“/”和“%”运算符重载到ROM除法API函数上。
LPC804 Code Boundle可以在恩智浦的官网上下载,下载链接为http://www.nxp.com/downloads/en ... 04-EX-CODE-KEIL.zip。
使用时,用户只需将rom_div_84x.c文件添加到自己的工程中,即可实现“/”和“%”运算符的重载。此时我们就可以通过使用“/”和“%”运算符来实现调用ROM的除法函数。
例如执行下面两个语句的结果是相同的:
res1 = I32Dividend / I32Divisor;
res1 = pROMDiv->sidiv(I32Dividend, I32Divisor);
2.2 在MCUXpresso IDE中使用ROM的除法函数
1,直接调用ROM的除法API函数
在MCUXpresso IDE中直接调用ROM的除法API函数,方法和在Keil中是相同的,具体方法请参考前面2.1节。
2,重载”/” 和 “%” 运算符
在MCUXpresso IDE中重载“/”和“%”操作符的方法和在Keil中的方法是不同的,具体操作方法如下:
① 将"MCUXpresso_aeabi_romdiv_patch.s"文件添加到自己工程路径中;
② 选择Properties->C/C++ Build->Settings->MCU C Compiler/Preprocessor,添加“__USE_ROMDIVIDE”宏定义;
③ 选择Properties->C/C++ Build->Settings->MCU Assembler/General,添加“-D__USE_ROMDIVIDE”。
完成这三步之后,“/”和“%”运算符就已经被重载到ROM的除法函数上。
三、ROM除法与C库的除法对比
为了比较ROM的除法和C库除法性能的差异,选取一组数据进行测试,执行有符号的整数计算和带余数的无符号整数计算,分别使用直接调用ROM除法API、重载“/”和“%”运算符及C库的除法三种方法进行计算,每种方式计算2000次,并分别在Keil 5.25和MCUXpresso 10.2.0两种IDE中进行了计算。选取的数据如下:
volatile const int32_t I32Dividends[10] = {-65530, -12002, -64498, 30883, 41538, 23513, 12972, 53852, 10253, 37127};
volatile const int32_t I32Divisors[10] = {-65525, -50973, -36407, -21845, - 7281, 7281, 21845, 36407, 50971, 65535};
volatile const uint32_t U32Dividends[10] = { 37480, 4630, 59349, 20616, 727, 29784, 56556, 53166, 35237, 41271};
volatile const uint32_t U32Divisors[10] = { 1, 7283, 14563, 21845, 29127, 36407, 43691, 50973, 58253, , 65535};
3.1 Keil中的测试结果对比
1,除法运算时间的对比
在Keil中使用三种方法的测试结果如下表所示:
注:上表所有时间单位是ms,下同。
从表中可以看出,在进行有符号整数计算(sidiv)时,直接调用ROM除法API的方式要慢于使用C库除法的方式;但在进行无符号带余数的整数计算(uidivmod)时,直接调用的ROM除法API的方式要快于C库中的除法,在优化等级为O0时,比C库除法快约20.44%。
在使用运算符重载的方式计算两种除法时,其运算时间都要大于C库除法所用时间,主要原因在于重载时,执行了一些参数入栈和出栈操作,消耗了一些时间,如下图所示:
2,Flash使用率的对比
使用ROM除法和C库除法时的Flash使用量如下表所示:
从上表中可以看出,使用ROM除法可以减小Flash空间的使用,大约节省了300字节的Flash空间。这主要是因为使用ROM除法时,链接器没有将C库中的aeabi_sdiv.o文件链接到工程中。在Keil中,aeabi_sdiv.o文件占用大小346字节。
3.2 MCUXpresso 10.2.0 中测试结果的对比
1,除法运算时间的对比
在MCUXpresso 10.2.0中使用三种方法的测试结果如下表所示:
在MCUXpresso中的测试结果与Keil中的测试结果是相似的,在进行有符号的整数计算时,直接调用ROM除法API的方式要慢于C库除法;在进行无符号带余数的整数计算时,直接调用ROM除法API的方式要快于C库除法,在优化等级为00时,比C库除法快约23.76%。
在使用运算符重载的方式计算两种除法时,其运算时间都要大于C库除法所用时间。因此在对除法运算时间有要求的情况下,推荐使用直接调用ROM除法API的方式。
2,Flash使用率的对比
使用ROM除法和C库除法时的Flash使用量如下表所示:
从表中可以看出,使用ROM除法减小了Flash空间的使用,在MCUXpresso IDE中使用ROM除法节省了约90字节的Flash空间。
3.3 测试方法
在测试工程中设置每种除法计算完成之后,翻转一下GPIO口的电平,使用逻辑分析仪测量各GPIO口的高电平时间,如下图所示:
部分测试代码如下:
LPC_GPIO_PORT->SET[0] = 1<<20; for (i=0; i<200; i++) { for (k=0; k!=10; k++) direct_i32[k] = pROMDiv->sidiv(I32Dividends[k], I32Divisors[k]); } LPC_GPIO_PORT->CLR[0] = 1<<20; LPC_GPIO_PORT->SET[0] = 1<<13;for (i=0; i<200; i++) { for (k=0; k!=10; k++) overload_i32[k] = (I32Dividends[k] / I32Divisors[k]); } LPC_GPIO_PORT->CLR[0] = 1<<13; LPC_GPIO_PORT->SET[0] = 1<<21; for (i=0; i<200; i++) { for (k=0; k!=10; k++) direct_uidivmod[k] = pROMDiv->uidivmod(U32Dividends[k], U32Divisors[k]); } LPC_GPIO_PORT->CLR[0] = 1<<21; LPC_GPIO_PORT->SET[0] = 1<<15; for (i=0; i<200; i++) { for (k=0; k!=10; k++) { div_val[k] = (U32Dividends[k] / U32Divisors[k]); mod_val[k]= (U32Dividends[k] % U32Divisors[k]); } }
LPC_GPIO_PORT->CLR[0] = 1<<15;
四、总结
从以上的对比结果中我们可以看出,使用ROM除法代替C库除法的方式,可以在一定程度上帮助用户节省Flash空间,这对LPC800系列产品是很有意义的。
此外ROM除法的运算时间也是要快于C库除法的最慢时间,在计算无符号带有余数的除法运算时,ROM除法的计算时间比C库除法快约20%,如用户对除法的运算时间有较高要求,也推荐使用直接调用ROM除法的方式进行计算。
LPC800系列往期内容回顾
|