为什么 a*b/c 而不是 a*(b/c) 在 AVR 上给出大 3 倍的程序大小?
Why a*b/c instead of a*(b/c) gives 3x bigger program size on AVR?
最近我试图将我的代码打包到带有 1kB 闪存的小型 ATTiny13 中。在优化过程中,我发现了一些对我来说很奇怪的东西。让我们以示例代码为例:
#include <avr/interrupt.h>
int main() {
TCNT0 = TCNT0 * F_CPU / 58000;
}
它当然没有意义,但有趣的是输出大小——它产生 248 字节。
代码快速解释:F_CPU
是由 -DF_CPU=...
开关为 avr-gcc
定义的常量,TCNT0
是 8-位寄存器(在 ATTiny13 上)。在实际程序中,我将等式结果分配给 uint16_t,但仍然观察到相同的行为。
如果表达式的一部分被括在方括号中:
TCNT0 = TCNT0 * (F_CPU / 58000);
输出文件大小为 70 字节。巨大的差异,但这些操作的结果是相同的(对吧?)。
我查看了生成的汇编代码,尽管我不太了解 ASM,但我看到无括号版本添加了一些标签,例如:
00000078 <__divmodsi4>:
78: 05 2e mov r0, r21
7a: 97 fb bst r25, 7
7c: 16 f4 brtc .+4 ; 0x82 <__divmodsi4+0xa>
7e: 00 94 com r0
80: 0f d0 rcall .+30 ; 0xa0 <__negsi2>
82: 57 fd sbrc r21, 7
84: 05 d0 rcall .+10 ; 0x90 <__divmodsi4_neg2>
86: 14 d0 rcall .+40 ; 0xb0 <__udivmodsi4>
88: 07 fc sbrc r0, 7
8a: 02 d0 rcall .+4 ; 0x90 <__divmodsi4_neg2>
8c: 46 f4 brtc .+16 ; 0x9e <__divmodsi4_exit>
8e: 08 c0 rjmp .+16 ; 0xa0 <__negsi2>
还有更多。我只学了一段时间的 x86 汇编程序,但据我所知,除法有简单的助记符。为什么 avr-gcc
在第一个示例中添加这么多代码?
另一个问题是如果两个数字在编译时已知,为什么编译器不内联等式的右边部分。
我们有这个:
x = x * 1200000 / 58000
注意1200000/58000 = 20.69...
不是整数,所以必须先乘后除。您的架构没有针对此数据类型的本机整数除法,因此必须对其进行模拟,从而导致大量代码。
但是这样:
x = x * (1200000 / 58000)
我们发现1200000 / 58000 = 20
,因为C使用了flooring division,所以这段代码简化为:
x = x * 20
最近我试图将我的代码打包到带有 1kB 闪存的小型 ATTiny13 中。在优化过程中,我发现了一些对我来说很奇怪的东西。让我们以示例代码为例:
#include <avr/interrupt.h>
int main() {
TCNT0 = TCNT0 * F_CPU / 58000;
}
它当然没有意义,但有趣的是输出大小——它产生 248 字节。
代码快速解释:F_CPU
是由 -DF_CPU=...
开关为 avr-gcc
定义的常量,TCNT0
是 8-位寄存器(在 ATTiny13 上)。在实际程序中,我将等式结果分配给 uint16_t,但仍然观察到相同的行为。
如果表达式的一部分被括在方括号中:
TCNT0 = TCNT0 * (F_CPU / 58000);
输出文件大小为 70 字节。巨大的差异,但这些操作的结果是相同的(对吧?)。
我查看了生成的汇编代码,尽管我不太了解 ASM,但我看到无括号版本添加了一些标签,例如:
00000078 <__divmodsi4>:
78: 05 2e mov r0, r21
7a: 97 fb bst r25, 7
7c: 16 f4 brtc .+4 ; 0x82 <__divmodsi4+0xa>
7e: 00 94 com r0
80: 0f d0 rcall .+30 ; 0xa0 <__negsi2>
82: 57 fd sbrc r21, 7
84: 05 d0 rcall .+10 ; 0x90 <__divmodsi4_neg2>
86: 14 d0 rcall .+40 ; 0xb0 <__udivmodsi4>
88: 07 fc sbrc r0, 7
8a: 02 d0 rcall .+4 ; 0x90 <__divmodsi4_neg2>
8c: 46 f4 brtc .+16 ; 0x9e <__divmodsi4_exit>
8e: 08 c0 rjmp .+16 ; 0xa0 <__negsi2>
还有更多。我只学了一段时间的 x86 汇编程序,但据我所知,除法有简单的助记符。为什么 avr-gcc
在第一个示例中添加这么多代码?
另一个问题是如果两个数字在编译时已知,为什么编译器不内联等式的右边部分。
我们有这个:
x = x * 1200000 / 58000
注意1200000/58000 = 20.69...
不是整数,所以必须先乘后除。您的架构没有针对此数据类型的本机整数除法,因此必须对其进行模拟,从而导致大量代码。
但是这样:
x = x * (1200000 / 58000)
我们发现1200000 / 58000 = 20
,因为C使用了flooring division,所以这段代码简化为:
x = x * 20