我想为Atmel AVR微控制器编写一个C代码固件。我将使用GCC编译它。此外,我想启用编译器优化(-Os
或-O2
),因为我认为没有理由不启用它们,而且它们可能会比手动编写汇编程序更快地生成更好的汇编程序。
但是我想要一小段没有优化的代码,我想延迟一段时间执行一个函数,因此我想写一个什么都不做的循环,只是为了浪费一些时间。不需要精确,只是等待一些时间。
/* How can it 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。虽然大多数时候使用这些函数可能是一个更好的主意,但这个问题仍然有效和有趣。
相关问题:
9条答案
按热度按时间zpqajqem1#
我在从dmckee的答案中找到一个链接后开发了这个答案,但它采用了与他/她的答案不同的方法。
来自GCC的Function Attributes文档提到:
noinline
此函数属性可防止考虑将函数内联。如果函数没有副作用,则存在内联以外的优化,会导致函数调用被优化掉,尽管函数调用是实时的。若要防止此类调用被优化掉,请将asm ("");
这给了我一个有趣的想法.我尝试在内部循环中添加一个空的汇编代码,而不是在内部循环中添加
nop
指令,如下所示:字符串
这个循环没有被优化,也没有插入额外的
nop
指令。更重要的是,如果你使用
volatile
,gcc会将这些变量存储在RAM中,并添加一堆ldd
和std
来将它们复制到临时寄存器中。另一方面,这种方法不使用volatile
,也不会产生这样的开销。-ansi
或-std
编译代码,你必须用__asm__
替换asm
关键字,作为described in GCC documentation。此外,如果 *assembly语句必须在我们放置它的位置执行(即不能作为优化移出循环),则也可以使用
__asm__ __volatile__("")
。nwwlzxa72#
将
i
和j
变量解析为volatile
。这将阻止编译器优化包含这些变量的代码。字符串
jdgnovmf3#
空的
__asm__
语句是不够的:最好使用数据依赖关系就像这样:
文件 main.c
字符串
编译和反汇编:
型
输出量:
型
我相信这是健壮的,因为它在循环变量
i
上放置了一个显式的数据依赖项,如:Enforcing statement order in C++所示,并产生了所需的循环:这就把
i
标记为内联汇编的输入和输出,那么,内联汇编对于GCC来说就是一个黑盒子,GCC不知道它是如何修改i
的,所以我认为这一点真的无法优化掉。如果我对一个空的
__asm__
执行相同的操作,如下所示:文件 * 不正确.c*
型
它看起来完全消除了循环和输出:
型
另请注意,
__asm__("")
和__asm__ volatile("")
应该相同,因为没有输出操作数:The difference between asm, asm volatile and clobbering memory如果我们将其替换为:
型
其产生:
型
我们可以看到,GCC在本例中只是loop unrolled
nop
循环,因为该循环足够小。因此,如果您依赖于一个空的
__asm__
,您将依赖于难以预测的GCC二进制大小/速度权衡,如果以最佳方式应用,应该总是会删除代码大小为零的空__asm__ volatile("");
的循环。noinline
忙碌循环功能如果在编译时不知道循环的大小,那么完全展开是不可能的,但是GCC仍然可以决定以块的形式展开,这将使延迟不一致。
将其与Denilson's answer结合在一起,一个忙碌循环函数可以写成:
型
其在以下位置拆卸:
型
这里需要使用
volatile
将程序集标记为可能有副作用,因为在本例中我们有一个输出变量。双循环版本可以是:
型
GitHub upstream的一个。
相关主题:
在Ubuntu 19.04和GCC 8.3.0中进行了测试。
ztyzrc3y4#
我不知道为什么还没有提到这种方法是完全错误的,很容易被编译器升级等破坏。确定你想要等待的时间值并旋转轮询当前时间直到超过所需的值会更有意义。在x86上,你可以使用
rdtsc
来实现这个目的,但更可移植的方法是调用clock_gettime
(或非POSIX操作系统的变体)来获取时间。当前的x86_64 Linux甚至会避免clock_gettime
的系统调用,而在内部使用rdtsc
。或者,如果您可以处理系统调用的成本,只需使用clock_nanosleep
来开始.7lrncoxx5#
我不知道编译器的AVR版本是否支持full set of
#pragma
s(链接中有趣的内容都来自GCC版本4.4),但这是您通常会开始的地方。xwmevbvl6#
对我来说,在GCC 4.7.0上,空的汇编代码无论如何都是用-O3优化的(我没有尝试用-O2)。在 register 或 volatile 中使用i++会导致很大的性能损失(在我的情况下)。
我链接了另一个空函数,编译器在编译“主程序”时看不到它。
基本上是这样的:
我创建了“helper.c”,并声明了这个函数(空函数):
字符串
然后编译
gcc helper.c -c -o helper.o
,型
并通过
gcc my_benchmark.cc helper.o
连接。这给了我最好的结果(从我的信念,没有开销,但我不能测试,因为我的程序将无法工作没有它:))
我认为它也应该能在ICC上工作。如果你启用了链接优化,也许不行,但在GCC上可以。
scyqe7ek7#
把volatile asm放在这里应该会有帮助。你可以在这里阅读更多:
如果你在Windows上工作,你甚至可以尝试将代码放在pragmas下,如下所述:
https://www.securecoding.cert.org/confluence/display/cplusplus/MSC06-CPP.+Be+aware+of+compiler+optimization+when+dealing+with+sensitive+data的
xt0899hw8#
把这个循环放在一个单独的.c文件中,不要优化那个文件。更好的是,用汇编程序编写这个例程,然后从C调用它。无论哪种方式,优化器都不会参与进来。
我有时会做volatile的事情,但通常会创建一个asm函数,它只是返回对该函数的put调用,优化器会使 for/while 循环紧密,但它不会优化它,因为它必须对dummy函数进行所有调用。Denilson Sá的nop答案做同样的事情,但更紧密。
qybjjes19#
你也可以使用register关键字。使用register声明的变量存储在CPU寄存器中。
在您的案例中:
字符串