如何防止GCC优化繁忙的等待循环?


72

我想为Atmel AVR微控制器编写C代码固件。我将使用GCC进行编译。另外,我想启用编译器优化(-Os-O2),因为我没有理由不启用它们,并且它们可能比手动编写程序集更快地产生更好的组装方式。

但是我想要一小段未优化的代码。我想将函数的执行延迟一段时间,因此我想编写一个空循环,以浪费时间。无需精确,只需等待一段时间。

由于AVR内存访问是慢了很多,我想ij被关在CPU寄存器。


更新:我刚刚从AVR Libc中找到util / delay.hutil / delay_basic.h。尽管大多数时候使用这些功能可能是一个更好的主意,但这个问题仍然有效且有趣。


相关问题:


也许有某种“睡眠”系统调用?也许您可以嵌入一些汇编逻辑?
乔治,

为什么不volatile asm ("rep; nop;")浪费无用的CPU周期来插入忙碌暂停之类的内容呢?

5
为什么不将这段代码放入函数中,并与其余的-O2代码分开使用-O0进行编译?并将它们链接在一起。
2011年

@乔治:刚刚检查。avr-libc没有睡眠功能,只能等待一段时间。相反,它映射到CPUsleep指令,从而启动低功耗模式之一(有效地停止CPU)。不过,这是个好主意。
DenilsonSáMaia)

5
人民,您正在评论中给出解决方案!将它们添加为答案!:)
DenilsonSáMaia

Answers:


85

我在查看了dmckee的答案的链接后开发了此答案,但是采用的方法与他/她的答案不同。

GCC的功能属性文档中提到:

noinline 此函数属性可防止考虑将函数内联。如果函数没有副作用,那么尽管函数调用是实时的,但除了内联之外,还有一些优化可以使函数调用被优化掉。为了避免此类呼叫被优化,请放置asm ("");

这给了我一个有趣的主意...我没有nop在内部循环中添加指令,而是尝试在其中添加空的汇编代码,如下所示:

而且有效!该循环尚未优化,没有nop插入任何额外的指令。

更重要的是,如果您使用volatile,gcc会将这些变量存储在RAM中并添加一堆ldd并将std其复制到临时寄存器中。另一方面,这种方法不使用也不volatile产生任何开销。


更新:如果您使用的编译代码-ansi或者-std,你必须更换asm以关键字__asm__,如GCC文档中描述

另外,__asm__ __volatile__("")如果汇编语句必须在我们放置它的位置执行(例如,不得将其作为优化对象移出循环),也可以使用


2
您将如何在Visual Studio中执行此操作?__asm{""}将无法正常工作
woosah

我有点担心循环展开,因此我建议使用具有明确数据依赖性的该版本:stackoverflow.com/a/58758133/895245
Ciro Santilli郝海东冠状病六四事件法轮功

25

声明ij变量volatile。这将阻止编译器优化涉及这些变量的代码。


10
尽管这可行,但具有将这些变量强制进入内存的副作用。因此,GCC将在每次循环迭代中对其进行读写,从而增加了很多开销。(无论如何,如果我想要非常细粒度的控制,我应该直接编写汇编程序)
DenilsonSáMaia

1
@DenilsonSá另一方面,如果该值是否可编码16位,则强制执行内存访问将确保等待始终花费相同的时间。
Oswin

@Oswin,能否请您详细说明?“如果该值是否可编码的16位”是什么意思。您所说的“价值”是什么?和可编码成什么?
DenilsonSáMaia

只是好奇,您能将它们标记为volatile register避免它们被记忆吗?
Mark K Cowan

这不是一个好主意,因为每次将这些变量写入内存都会增加额外的延迟,因此,与使用寄存器循环计数器的循环相比,粒度要小得多,更不用说它给缓存/数据总线带来了压力,而您要做的只是“没有”。
卡罗·伍德

6

__asm__语句是不够的:更好地使用数据依赖项

像这样:

main.c

编译和反汇编:

输出:

我相信这是可靠的,因为它在循环变量上放置了显式的数据依存关系,i如以下建议:在C ++中强制执行语句顺序并生成所需的循环:

这标记i为内联汇编的输入和输出。然后,内联汇编对于GCC来说是一个黑匣子,它不知道它是如何修改的i,因此我认为确实无法对其进行优化。

如果我对空白执行相同操作__asm__

它似乎完全消除了循环并输出:

还要注意,__asm__("")并且__asm__ volatile("")应该相同,因为没有输出操作数:asm,asm volatile和破坏内存之间的区别

如果我们将其替换为:

产生:

因此,在这种情况下,我们看到GCC只是循环展开nop循环,因为循环足够小。

因此,如果您依赖一个Empty __asm__,您将很难预测GCC二进制大小/速度的折衷,如果以最佳方式应用,它将始终删除__asm__ volatile("");代码大小为零的empty的循环。

noinline 忙循环功能

如果在编译时不知道循环大小,则不可能完全展开,但是GCC仍可以决定分块展开,这会使您的延迟不一致。

将其与Denilson的答案一起,可以将繁忙循环函数编写为:

在以下位置拆卸

这里volatile需要将组件标记为可能具有副作用,因为在这种情况下,我们具有输出变量。

双循环版本可能是:

GitHub上游

相关主题:

已在Ubuntu 19.04,GCC 8.3.0中测试。


5

我不确定为什么还没有提到它,因为这种方法完全被误导,很容易被编译器升级等破坏。确定要等待的时间值并旋转轮询当前值更有意义。直到超过所需值的时间。在x86上,您可以rdtsc用于此目的,但是更可移植的方法是致电clock_gettime(或非POSIX OS的变体)来获取时间。当前的x86_64 Linux甚至会避免在内部clock_gettime使用syscall rdtsc。或者,如果您可以处理syscall的费用,则只需使用clock_nanosleep...


3

我不知道该编译器的avr版本是否支持s的全集#pragma(链接中所有有趣的内容都源于gcc 4.4版),但这通常是您开始的地方。


2
您是否偶然知道哪个GCC选项启用/禁用了什么都不做的优化循环?我尝试使用#pragma GCC optimize 0#pragma GCC reset_options功能后面跟随),但是它禁用了所有优化(如预期的那样)。最好只禁用那个。
DenilsonSáMaia

2
编译指示仅适用于后续定义的功能(并在功能级别起作用)。
Foo Bah

1
我的意思是……optimize 0太多了,它甚至没有将那些变量存储在寄存器中(它们被保存在内存中)。因此,如果我知道哪个gcc-f选项禁止删除该空循环,则可以对该功能仅禁用该选项。那很好啊!
DenilsonSáMaia

3

对我而言,在GCC 4.7.0上,无论如何都使用-O3来优化空的asm(不尝试使用-O2)。并且在寄存器或volatile中使用i ++会导致很大的性能损失(在我的情况下)。

我所做的是与另一个空函数链接,编译“主程序”时编译器无法看到该函数

基本上是这样的:

使用此函数(空函数)声明创建的“ helper.c”

然后编译gcc helper.c -c -o helper.o 然后

并通过链接gcc my_benchmark.cc helper.o

这给了我最好的结果(并且据我所知,根本没有任何开销,但是无法测试,因为没有它我的程序将无法工作:))

我认为它也应与icc一起使用。如果启用链接优化,可能就不行,但是使用gcc可以。


1

将该循环放在单独的.c文件中,不要优化该文件。最好用汇编器编写该例程并从C调用该例程,无论哪种方式,优化器都不会参与。

我有时会做一些易变的事情,但通常会创建一个asm函数,该函数只是返回对该函数的调用,优化器将使for / while循环变紧,但由于必须对虚拟函数进行所有调用,因此它不会对其进行优化。DenilsonSá的nop回答做了同样的事情,但更加严格...


好主意。您能举例说明如何做吗?我从不了解这些makefile ...
Kamil

1
@Kamil,一个很简短的解释,运行gcc -c [your flags here] -o foo.o foo.c将源代码编译成一个对象,然后运行gcc [other flags here] -o foo.elf foo.o bar.o以将所有目标文件链接在一起。随时检查Makefile我的AVR项目中的s:atmega8-blinking-ledsatmega8-hidkeys-helloworldatmega8-magnetometer-usb-mouse
DenilsonSáMaia 2013年

单独进行编译的方法的问题在于,它引入了函数调用和返回的开销,对于此类事情有用的非常短的等待循环而言,这可能会很多。
Donal Fellows

volatile的问题在于它会导致volatile的开销,对于这种事情有用的非常短的等待循环,这可能会相当多。正确的答案是仅在纯asm中执行此操作,而不要与volatile或inline混淆,但是由于这种计时通常不准确,因此,如果您使用C,则绝对不能,那么稍长一点或更短就可以了。如果要更准确,则必须使用专用硬件来计时。
old_timer '20

1

放置挥发性的asm应该会有所帮助。您可以在这里阅读更多内容:

http://www.nongnu.org/avr-libc/user-manual/optimization.html

如果您使用的是Windows,甚至可以尝试将代码置于编译指示下,如下所述:

https://www.securecoding.cert.org/confluence/display/cplusplus/MSC06-CPP.+要+意识到+ of +编译器+优化+何时+使用+敏感+数据

希望这可以帮助。


正如在评论中提到这个答案,usiingvolatile具有迫使这些变量到内存中,与LD和ST指令的副作用。
einpoklum

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.