我想为Atmel AVR微控制器编写C代码固件。我将使用GCC编译它。此外,我想启用编译器优化(-Os
或-O2
),因为我认为没有理由不启用它们,并且它们可能比手动编写汇编更快地生成汇编方式。
但是我想要一小段没有优化的代码。我想延迟函数的执行一段时间,因此我想写一个do-nothing循环只是为了浪费一些时间。无需准确,只需等待一段时间。
/* How to NOT optimize this, while optimizing other code? */
unsigned char i, j;
j = 0;
while(--j) {
i = 0;
while(--i);
}
由于AVR中的内存访问速度慢很多,我希望i
和j
保存在CPU寄存器中。
更新:我刚从util/delay.h找到了util/delay_basic.h和AVR Libc。虽然大多数时候使用这些功能可能更好,但这个问题仍然有效且有趣。
相关问题:
答案 0 :(得分:73)
我在跟踪dmckee's answer的链接后得出了这个答案,但它的方法与他/她的答案不同。
来自GCC的Function Attributes文件提到:
noinline
此函数属性可防止将函数考虑为内联。如果函数没有副作用,那么除了内联之外,还有一些优化会导致函数调用被优化掉,尽管函数调用是实时的。为了防止此类调用被优化,请添加asm ("");
这给了我一个有趣的想法......我没有在内部循环中添加nop
指令,而是尝试在其中添加一个空的汇编代码,如下所示:
unsigned char i, j;
j = 0;
while(--j) {
i = 0;
while(--i)
asm("");
}
它有效!该循环尚未优化,并且未插入额外的nop
指令。
更重要的是,如果你使用volatile
,gcc会将这些变量存储在RAM中并添加一堆ldd
和std
来将它们复制到临时寄存器。另一方面,这种方法不使用volatile
并且不会产生这样的开销。
更新:如果您使用-ansi
或-std
编译代码,则必须将asm
关键字替换为__asm__
,因为{{ 3}}
此外,如果您的described in GCC documentation。
,也可以使用__asm__ __volatile__("")
答案 1 :(得分:21)
将i
和j
变量声明为volatile
。这将阻止编译器优化涉及这些变量的代码。
unsigned volatile char i, j;
答案 2 :(得分:5)
我不确定为什么还没有提到这种方法完全被误导,并且很容易被编译器升级等打破。确定你想要等待和旋转的时间值会更有意义。轮询当前时间直到超过所需的值。在x86上,您可以使用rdtsc
来实现此目的,但更便携的方式是调用clock_gettime
(或非POSIX OS的变体)来获取时间。当前的x86_64 Linux甚至可以避免clock_gettime
的系统调用,并在内部使用rdtsc
。或者,如果您可以处理系统调用的成本,只需使用clock_nanosleep
开始...
答案 3 :(得分:3)
如果编译器的avr版本支持full set of #pragma
s(gcc版本4.4中所有日期链接中的有趣版本),我不知道我的头脑,但这是你通常的地方启动。
答案 4 :(得分:2)
对于我来说,在GCC 4.7.0上,无论如何使用-O3对空asm进行了优化(没有尝试使用-O2)。 并且在寄存器或volatile中使用i ++会导致性能损失(在我的情况下)。
我所做的是链接另一个空函数,编译器在编译“主程序”时无法看到
基本上这个:
使用此函数声明(空函数)
创建“helper.c”void donotoptimize(){}
然后编译“gcc helper.c -c -o helper.o” 然后
while (...) { donotoptimize();}
这给了我最好的结果(据我所知,没有任何开销,但无法测试,因为没有它我的程序将无法工作:))
我认为它也适用于icc。如果启用链接优化,可能不会,但是使用gcc可以。
答案 5 :(得分:1)
将该循环放在单独的.c文件中,不要优化那个文件。甚至可以更好地在汇编程序中编写该例程并从C调用它,无论两种方式优化器都不参与。
我有时做易变的事情,但通常会创建一个简单返回的asm函数 调用该函数,优化器将使for / while循环变紧,但它不会优化它,因为它必须对虚函数进行所有调用。来自德尼尔森萨的回答做了同样的事情,但更加紧张......
答案 6 :(得分:1)
使用volatile asm应该会有所帮助。 你可以在这里阅读更多内容: -
http://www.nongnu.org/avr-libc/user-manual/optimization.html
如果您正在使用Windows,您甚至可以尝试将代码放在pragma下,详细说明如下: -
希望这有帮助。
答案 7 :(得分:1)
空的__asm__
语句还不够:请更好地使用数据依赖项
例如:
main.c
int main(void) {
unsigned i;
for (i = 0; i < 10; i++) {
__asm__ volatile("");
}
}
编译和反汇编:
gcc -O3 -ggdb3 -o main.out main.c
gdb -batch -ex 'disas main' main.out
输出:
0x0000000000001040 <+0>: xor %eax,%eax
0x0000000000001042 <+2>: retq
还要注意,__asm__("")
和__asm__ volatile("")
应该相同,因为没有输出操作数:The difference between asm, asm volatile and clobbering memory
如果我们将其替换为:
__asm__ volatile("");
产生:
0x0000000000001040 <+0>: nop
0x0000000000001041 <+1>: nop
0x0000000000001042 <+2>: nop
0x0000000000001043 <+3>: nop
0x0000000000001044 <+4>: nop
0x0000000000001045 <+5>: nop
0x0000000000001046 <+6>: nop
0x0000000000001047 <+7>: nop
0x0000000000001048 <+8>: nop
0x0000000000001049 <+9>: nop
0x000000000000104a <+10>: xor %eax,%eax
0x000000000000104c <+12>: retq
所以我们看到在这种情况下,GCC只是loop unrolled nop
循环,因为该循环足够小。
因此,如果使用此技术,则将难以预测GCC二进制大小/速度的折衷,如果进行最佳应用,则应始终删除代码量为零的空__asm__ volatile("");
的循环
我认为是可靠的一种解决方案,是将其替换为对循环变量i
的显式数据依赖,如Enforcing statement order in C++
__asm__ ("" : "+g" (i) : :);
产生所需的循环:
0x0000000000001040 <+0>: xor %eax,%eax
0x0000000000001042 <+2>: nopw 0x0(%rax,%rax,1)
0x0000000000001048 <+8>: add $0x1,%eax
0x000000000000104b <+11>: cmp $0x9,%eax
0x000000000000104e <+14>: jbe 0x1048 <main+8>
0x0000000000001050 <+16>: xor %eax,%eax
0x0000000000001052 <+18>: retq
这将i
标记为嵌入式汇编的输入和输出。然后,内联汇编对于GCC来说是一个黑匣子,它不知道它如何修改i
,因此我认为确实无法对其进行优化。
noinline
忙循环功能
如果在编译时未知循环大小,则不可能完全展开,但是GCC仍可以决定分块展开,这会使您的延迟不一致。
将其与Denilson's answer放在一起,我会寻求:
void __attribute__ ((noinline)) busy_loop(unsigned max) {
for (unsigned i = 0; i < max; i++) {
__asm__ volatile("" : "+g" (i) : :);
}
}
int main(void) {
busy_loop(10);
}
在以下位置拆卸:
Dump of assembler code for function busy_loop:
0x0000000000001140 <+0>: test %edi,%edi
0x0000000000001142 <+2>: je 0x1157 <busy_loop+23>
0x0000000000001144 <+4>: xor %eax,%eax
0x0000000000001146 <+6>: nopw %cs:0x0(%rax,%rax,1)
0x0000000000001150 <+16>: add $0x1,%eax
0x0000000000001153 <+19>: cmp %eax,%edi
0x0000000000001155 <+21>: ja 0x1150 <busy_loop+16>
0x0000000000001157 <+23>: retq
End of assembler dump.
Dump of assembler code for function main:
0x0000000000001040 <+0>: mov $0xa,%edi
0x0000000000001045 <+5>: callq 0x1140 <busy_loop>
0x000000000000104a <+10>: xor %eax,%eax
0x000000000000104c <+12>: retq
End of assembler dump.
这里需要volatile
来将程序集标记为可能具有副作用,因为在这种情况下,我们有一个输出变量。
双循环版本可能是:
void __attribute__ ((noinline)) busy_loop(unsigned max, unsigned max2) {
for (unsigned i = 0; i < max; i++) {
for (unsigned j = 0; j < max2; j++) {
__asm__ volatile ("" : "+g" (j), "+g" (j) : :);
}
}
}
int main(void) {
busy_loop(10, 10);
}
相关线程:
在Ubuntu 19.04,GCC 8.3.0中进行了测试。
答案 8 :(得分:-1)
您也可以使用register keyword。用寄存器声明的变量存储在CPU寄存器中。
在你的情况下:
register unsigned char i, j;
j = 0;
while(--j) {
i = 0;
while(--i);
}