如何防止GCC优化出一个繁忙的等待循环?
我想为Atmel AVR微控制器编写一个C代码固件。 我将使用GCC进行编译。 另外,我想启用编译器优化( -Os
或-O2
),因为我没有理由不启用它们,并且它们可能会比手动编写汇编产生更好的汇编方式。
但是我想要一小段代码没有优化。 我想推迟一段时间的函数执行,因此我想写一个什么都不做的循环,只是浪费一些时间。 不需要精确,只需等待一段时间。
/* 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寄存器中。
更新:我刚刚从AVR Libc发现util / delay.h和util / delay_basic.h 。 虽然大多数时候使用这些函数可能是一个更好的主意,但这个问题仍然有效且有趣。
我从dmckee的回答中find了一个答案 ,但是这个答案不同于他/她的答案。
GCC的函数属性文档中提到:
noinline
该函数属性可以防止函数被考虑用于内联。 如果函数没有副作用,除了内联以外,还有一些优化可以使函数调用被优化掉,尽pipe函数调用是实时的。 为了避免这样的调用被优化,把asm ("");
这给了我一个有趣的想法…而不是在内部循环添加一个nop
指令,我试着在那里添加一个空的汇编代码,就像这样:
unsigned char i, j; j = 0; while(--j) { i = 0; while(--i) asm(""); }
它的工作! 该循环没有被优化,并且没有插入额外的nop
指令。
更重要的是,如果你使用volatile
,gcc会将这些variables存储在RAM中,并添加一堆ldd
和std
以将它们复制到临时寄存器。 另一方面,这种方法不使用volatile
,不产生这种开销。
更新:如果使用-ansi
或-std
编译代码,则必须使用__asm__
replaceasm
关键字,如GCC文档中所述 。
另外,如果你的汇编语句必须在我们放置的地方执行 ,你也可以使用__asm__ __volatile__("")
(即不能作为优化移出循环) 。
将variablesi
和j
声明为volatile
。 这将阻止编译器优化涉及这些variables的代码。
unsigned volatile char i, j;
如果avr版本的编译器支持全套#pragma
(链接中所有有趣的内容都来自于gcc版本4.4),那么我不知道该如何解决,但这通常是你开始的地方。
我不确定为什么还没有提到这种方法是完全错误的,并且很容易被编译器升级破坏等等。确定要等待的时间值并旋转当前的查询会更有意义直到超过期望的值。 在x86上,你可以使用rdtsc
来达到这个目的,但更方便的方法是调用clock_gettime
(或者你的非POSIX操作系统的变种)来获得时间。 当前的x86_64 Linux甚至会避免clock_gettime
的系统调用,并rdtsc
内部使用rdtsc
。 或者,如果您可以处理系统调用的成本,只需使用clock_nanosleep
开始…
把这个循环放在一个单独的.c文件中,不要优化那个文件。 甚至更好的在汇编程序中编写这个例程,并从C中调用它,无论哪种方式,优化器都不会涉及到。
我有时做了不稳定的事情,但通常创build一个asm函数,只是简单地返callback用该函数优化器将使for / while循环紧,但它不会优化它,因为它必须进行所有调用虚拟函数。 德尼尔森萨的答复做同样的事情,但更严格的…
把挥发性砷应该有所帮助。 你可以在这里阅读更多: –
http://www.nongnu.org/avr-libc/user-manual/optimization.html
如果你在Windows上工作,你甚至可以尝试把代码放在编译指示下,如下所述:
希望这可以帮助。
对我来说,在GCC 4.7.0上,空的asm已经被-O3优化了(没有尝试用-O2)。 并使用i + +注册或挥发性导致一个很大的性能损失(在我的情况)。
我所做的是链接到编译器在编译“主程序”时看不到的另一个空函数,
基本上这个:
用这个函数创build“helper.c”(空函数)
void donotoptimize(){}
然后编译“gcc helper.c -c -o helper.o”然后
while (...) { donotoptimize();}
这给了我最好的结果(并从我的信仰,根本没有开销,但不能testing,因为我的程序不会没有它:))
我认为它也应该与icc一起工作。 也许不是,如果你启用链接优化,但它与gcc。
您也可以使用register关键字 。 用寄存器声明的variables存储在CPU寄存器中。
在你的情况下:
register unsigned char i, j; j = 0; while(--j) { i = 0; while(--i); }
我有一个情况下for循环正在优化。 以下结果只对pulse_low函数有一个调用:
for (long i = 0; i < 16 * blocks; i++) pulse_low(P_WR, B_WR);
添加大括号可以防止优化。 正如所料,pulse_low函数被称为16 *块时间:
for (long i = 0; i < 16 * blocks; i++) { pulse_low(P_WR, B_WR); }
这是使用默认的编译器设置。