ARMCC 5对strtol和strtod的优化
ARMCC 5 optimization of strtol and strtod
我有一块基于 STM32L4 MCU(超低功耗 Cortex-M4)的电路板用于 GNSS 跟踪。我不使用 RTOS,所以我使用自定义调度程序。编译器和环境是 KEIL uVision 5(编译器 5.05 和 5.06,行为没有改变)
MCU 通过普通 UART 与 GNSS 模块通信,协议是 NMEA 和 AT 的混合。 GNSS 位置以纯文本形式给出,必须转换为一对 float/double 坐标。
要从文本中获取 double/float 值,我使用 strtod(或 strtof)。
请注意,字符串操作是在单独的缓冲区中进行的,与 UART RX 不同。
UART 上纬度的典型字符串是
4256.45783
表示 42° 56.45783'
要获得以度为单位的绝对位置,我使用以下公式
42 + 56.45783 / 60
在没有优化的情况下,代码工作正常,位置转换正确。当我打开 1 级优化(或更高)时,如果我使用标准 C 库,我可以转换整数部分(示例中为 42),而在转换 56.45783 时,我只得到 56(所以分钟的整数部分直到点)。
如果我摆脱标准库并使用从 ANSI C 源库下载的自定义 strtod 函数,我只会得到 0 和 ERANGE 错误。
在代码的其他部分,我使用了 strtol,当 L1 优化打开时,它有一个奇怪的行为:当第一个数字是 9 并且转换基数是 10 时,它只是跳过 9 继续与其他数字。
因此,如果缓冲区中有 92 个,我将只解析 2 个。为了摆脱这个,我只是在数字前加了一个符号 +,结果总是好的(据我所知)。此 WA 不适用于 strtod。
请注意,我尝试使用静态、可变和堆栈变量,行为没有改变。
编辑:我简化了代码,以便找到出错的地方,根据后面的评论
C代码是这样的:
void GnssStringToLatLonDegMin(const char* str, LatLong_t* struc)
{
double dbl = 0.0;
dbl = strtod("56.45783",NULL);
if(struc != NULL)
{
struc->Axis = (float)((dbl / 60.0) + 42.0);
}
}
0 级优化:
559: void GnssStringToLatLonDegMin(const char* str, LatLong_t* struc)
0x08011FEE BDF8 POP {r3-r7,pc}
560: {
0x08011FF0 B570 PUSH {r4-r6,lr}
0x08011FF2 4605 MOV r5,r0
0x08011FF4 ED2D8B06 VPUSH.64 {d8-d10}
0x08011FF8 460C MOV r4,r1
561: double dbl = 0.0;
0x08011FFA ED9F0BF8 VLDR d0,[pc,#0x3E0]
0x08011FFE EEB08A40 VMOV.F32 s16,s0
0x08012002 EEF08A60 VMOV.F32 s17,s1
562: dbl = strtod("56.45783",NULL);
0x08012006 2100 MOVS r1,#0x00
0x08012008 A0F6 ADR r0,{pc}+4 ; @0x080123E4
0x0801200A F7FDFED1 BL.W __hardfp_strtod (0x0800FDB0)
0x0801200E EEB08A40 VMOV.F32 s16,s0
0x08012012 EEF08A60 VMOV.F32 s17,s1
563: if(struc != NULL)
564: {
0x08012016 B1A4 CBZ r4,0x08012042
565: struc->Axis = (float)((dbl / 60.0) + 42.0);
566: }
0x08012018 ED9F0BF5 VLDR d0,[pc,#0x3D4]
0x0801201C EC510B18 VMOV r0,r1,d8
0x08012020 EC532B10 VMOV r2,r3,d0
0x08012024 F7FEF880 BL.W __aeabi_ddiv (0x08010128)
0x08012028 EC410B1A VMOV d10,r0,r1
0x0801202C ED9F0BF2 VLDR d0,[pc,#0x3C8]
0x08012030 EC532B10 VMOV r2,r3,d0
0x08012034 F7FDFFBC BL.W __aeabi_dadd (0x0800FFB0)
0x08012038 EC410B19 VMOV d9,r0,r1
0x0801203C F7FDFF86 BL.W __aeabi_d2f (0x0800FF4C)
0x08012040 6020 STR r0,[r4,#0x00]
567: }
1 级优化
557: void GnssStringToLatLonDegMin(const char* str, LatLong_t* struc)
0x08011FEE BDF8 POP {r3-r7,pc}
558: {
559: double dbl = 0.0;
0x08011FF0 B510 PUSH {r4,lr}
0x08011FF2 460C MOV r4,r1
560: dbl = strtod("56.45783",NULL);
0x08011FF4 2100 MOVS r1,#0x00
0x08011FF6 A0F7 ADR r0,{pc}+2 ; @0x080123D4
0x08011FF8 F7FDFEDA BL.W __hardfp_strtod (0x0800FDB0)
561: if(struc != NULL)
562: {
0x08011FFC 2C00 CMP r4,#0x00
0x08011FFE D010 BEQ 0x08012022
563: struc->Axis = (float)((dbl / 60.0) + 42.0);
564: }
0x08012000 ED9F1BF7 VLDR d1,[pc,#0x3DC]
0x08012004 EC510B10 VMOV r0,r1,d0
0x08012008 EC532B11 VMOV r2,r3,d1
0x0801200C F7FEF88C BL.W __aeabi_ddiv (0x08010128)
0x08012010 ED9F1BF5 VLDR d1,[pc,#0x3D4]
0x08012014 EC532B11 VMOV r2,r3,d1
0x08012018 F7FDFFCA BL.W __aeabi_dadd (0x0800FFB0)
0x0801201C F7FDFF96 BL.W __aeabi_d2f (0x0800FF4C)
0x08012020 6020 STR r0,[r4,#0x00]
565: }
我查看了这些函数调用的 __hardfp_strtod 和 __strtod_int 的反汇编,由于它们被合并为二进制文件,因此它们不会因优化级别而改变。
由于优化,strtod 没有工作。
感谢@old_timer,我必须制作自己的 strtod 函数,即使将优化级别设置为 2 也能正常工作。
double simple_strtod(const char* str)
{
int8 inc;
double result = 0.0;
char * c_tmp;
c_tmp = strchr(str, '.');
if(c_tmp != NULL)
{
c_tmp++;
inc = -1;
while(*c_tmp != 0 && inc > -9)
{
result += (*c_tmp - '0') * pow(10.0, inc);
c_tmp++; inc--;
}
inc = 0;
c_tmp = strchr(str, '.');
c_tmp--;
do
{
result += (*c_tmp - '0') * pow(10.0,inc);
c_tmp--; inc++;
}while(c_tmp >= str);
}
return result;
}
它可以通过不调用 'pow' 并使用更聪明的东西来进一步优化,但就像这样它完美地工作。
我有一块基于 STM32L4 MCU(超低功耗 Cortex-M4)的电路板用于 GNSS 跟踪。我不使用 RTOS,所以我使用自定义调度程序。编译器和环境是 KEIL uVision 5(编译器 5.05 和 5.06,行为没有改变)
MCU 通过普通 UART 与 GNSS 模块通信,协议是 NMEA 和 AT 的混合。 GNSS 位置以纯文本形式给出,必须转换为一对 float/double 坐标。 要从文本中获取 double/float 值,我使用 strtod(或 strtof)。 请注意,字符串操作是在单独的缓冲区中进行的,与 UART RX 不同。
UART 上纬度的典型字符串是
4256.45783
表示 42° 56.45783'
要获得以度为单位的绝对位置,我使用以下公式
42 + 56.45783 / 60
在没有优化的情况下,代码工作正常,位置转换正确。当我打开 1 级优化(或更高)时,如果我使用标准 C 库,我可以转换整数部分(示例中为 42),而在转换 56.45783 时,我只得到 56(所以分钟的整数部分直到点)。
如果我摆脱标准库并使用从 ANSI C 源库下载的自定义 strtod 函数,我只会得到 0 和 ERANGE 错误。
在代码的其他部分,我使用了 strtol,当 L1 优化打开时,它有一个奇怪的行为:当第一个数字是 9 并且转换基数是 10 时,它只是跳过 9 继续与其他数字。
因此,如果缓冲区中有 92 个,我将只解析 2 个。为了摆脱这个,我只是在数字前加了一个符号 +,结果总是好的(据我所知)。此 WA 不适用于 strtod。
请注意,我尝试使用静态、可变和堆栈变量,行为没有改变。
编辑:我简化了代码,以便找到出错的地方,根据后面的评论
C代码是这样的:
void GnssStringToLatLonDegMin(const char* str, LatLong_t* struc)
{
double dbl = 0.0;
dbl = strtod("56.45783",NULL);
if(struc != NULL)
{
struc->Axis = (float)((dbl / 60.0) + 42.0);
}
}
0 级优化:
559: void GnssStringToLatLonDegMin(const char* str, LatLong_t* struc)
0x08011FEE BDF8 POP {r3-r7,pc}
560: {
0x08011FF0 B570 PUSH {r4-r6,lr}
0x08011FF2 4605 MOV r5,r0
0x08011FF4 ED2D8B06 VPUSH.64 {d8-d10}
0x08011FF8 460C MOV r4,r1
561: double dbl = 0.0;
0x08011FFA ED9F0BF8 VLDR d0,[pc,#0x3E0]
0x08011FFE EEB08A40 VMOV.F32 s16,s0
0x08012002 EEF08A60 VMOV.F32 s17,s1
562: dbl = strtod("56.45783",NULL);
0x08012006 2100 MOVS r1,#0x00
0x08012008 A0F6 ADR r0,{pc}+4 ; @0x080123E4
0x0801200A F7FDFED1 BL.W __hardfp_strtod (0x0800FDB0)
0x0801200E EEB08A40 VMOV.F32 s16,s0
0x08012012 EEF08A60 VMOV.F32 s17,s1
563: if(struc != NULL)
564: {
0x08012016 B1A4 CBZ r4,0x08012042
565: struc->Axis = (float)((dbl / 60.0) + 42.0);
566: }
0x08012018 ED9F0BF5 VLDR d0,[pc,#0x3D4]
0x0801201C EC510B18 VMOV r0,r1,d8
0x08012020 EC532B10 VMOV r2,r3,d0
0x08012024 F7FEF880 BL.W __aeabi_ddiv (0x08010128)
0x08012028 EC410B1A VMOV d10,r0,r1
0x0801202C ED9F0BF2 VLDR d0,[pc,#0x3C8]
0x08012030 EC532B10 VMOV r2,r3,d0
0x08012034 F7FDFFBC BL.W __aeabi_dadd (0x0800FFB0)
0x08012038 EC410B19 VMOV d9,r0,r1
0x0801203C F7FDFF86 BL.W __aeabi_d2f (0x0800FF4C)
0x08012040 6020 STR r0,[r4,#0x00]
567: }
1 级优化
557: void GnssStringToLatLonDegMin(const char* str, LatLong_t* struc)
0x08011FEE BDF8 POP {r3-r7,pc}
558: {
559: double dbl = 0.0;
0x08011FF0 B510 PUSH {r4,lr}
0x08011FF2 460C MOV r4,r1
560: dbl = strtod("56.45783",NULL);
0x08011FF4 2100 MOVS r1,#0x00
0x08011FF6 A0F7 ADR r0,{pc}+2 ; @0x080123D4
0x08011FF8 F7FDFEDA BL.W __hardfp_strtod (0x0800FDB0)
561: if(struc != NULL)
562: {
0x08011FFC 2C00 CMP r4,#0x00
0x08011FFE D010 BEQ 0x08012022
563: struc->Axis = (float)((dbl / 60.0) + 42.0);
564: }
0x08012000 ED9F1BF7 VLDR d1,[pc,#0x3DC]
0x08012004 EC510B10 VMOV r0,r1,d0
0x08012008 EC532B11 VMOV r2,r3,d1
0x0801200C F7FEF88C BL.W __aeabi_ddiv (0x08010128)
0x08012010 ED9F1BF5 VLDR d1,[pc,#0x3D4]
0x08012014 EC532B11 VMOV r2,r3,d1
0x08012018 F7FDFFCA BL.W __aeabi_dadd (0x0800FFB0)
0x0801201C F7FDFF96 BL.W __aeabi_d2f (0x0800FF4C)
0x08012020 6020 STR r0,[r4,#0x00]
565: }
我查看了这些函数调用的 __hardfp_strtod 和 __strtod_int 的反汇编,由于它们被合并为二进制文件,因此它们不会因优化级别而改变。
由于优化,strtod 没有工作。 感谢@old_timer,我必须制作自己的 strtod 函数,即使将优化级别设置为 2 也能正常工作。
double simple_strtod(const char* str)
{
int8 inc;
double result = 0.0;
char * c_tmp;
c_tmp = strchr(str, '.');
if(c_tmp != NULL)
{
c_tmp++;
inc = -1;
while(*c_tmp != 0 && inc > -9)
{
result += (*c_tmp - '0') * pow(10.0, inc);
c_tmp++; inc--;
}
inc = 0;
c_tmp = strchr(str, '.');
c_tmp--;
do
{
result += (*c_tmp - '0') * pow(10.0,inc);
c_tmp--; inc++;
}while(c_tmp >= str);
}
return result;
}
它可以通过不调用 'pow' 并使用更聪明的东西来进一步优化,但就像这样它完美地工作。