大佬教程收集整理的这篇文章主要介绍了如果只有一次迭代,gcc 优化会删除 for 循环吗?,大佬教程大佬觉得挺不错的,现在分享给大家,也给大家做个参考。
我正在编写一个实时 DSP 处理库。 我的目的是让它能够灵活地定义输入样本块大小,同时在逐个样本处理的情况下也具有最佳性能,即 - 单个样本块大小
我想我必须使用 volatile 关键字来定义循环变量,因为数据处理将使用指向输入/输出的指针。
这就引出了一个问题:
gcc 编译器会不会优化这段代码
int blockSize = 1;
for (volatilE int i=0; i<blockSize; i++)
{
foo()
}
或
//.h
#define BLOCKSIZE 1
//.c
for (volatilE int i=0; i<BLOCKSIZE; i++)
{
foo()
}
与简单地调用循环体相同:
foo()
?
谢谢
我想我必须使用 volatile 关键字来定义循环变量,因为数据处理将使用指向输入/输出的指针。
不,这没有任何意义。只有输入/输出硬件寄存器本身应该是 volatile
。指向它们的指针应声明为指向volatile
数据的指针,即volatile uint8_t*
。不需要让指针本身为volatile
,即uint8_t* volatile //wrong
。
就目前的情况而言,您强制编译器创建一个变量 i
并增加它,这可能会阻止循环展开优化。
使用 -O3 在 gcc x86 上尝试您的代码,这正是发生的情况。不管BLOCKSIZE
的大小,它仍然会因为volatile
而产生循环。如果我删除 volatile
,它会将循环完全展开到 BLOCKSIZE
== 7 并将其替换为多个函数调用。超过 8 它会创建一个循环(但将迭代器保存在寄存器中而不是 RAM 中)。
x86 示例:
for (int i=0; i<5; i++)
{
foo();
}
给予
call foo
call foo
call foo
call foo
call foo
但是
for (volatilE int i=0; i<5; i++)
{
foo();
}
导致效率低下
mov DWORD PTR [rsp+12],0
mov eax,DWORD PTR [rsp+12]
cmp eax,4
jg .L2
.L3:
call foo
mov eax,DWORD PTR [rsp+12]
add eax,1
mov DWORD PTR [rsp+12],eax
mov eax,4
jle .L3
.L2:
关于在嵌入式系统中正确使用 volatile
的进一步研究,请参见:
由于循环变量是 volatile
,因此不应对其进行优化。编译器无法知道在评估条件时 i
是否会是 1
,因此它必须保持循环。
从编译器的角度来看,循环可以运行不确定的次数,直到条件满足为止。
如果您曾经访问过硬件寄存器,那么应该将它们声明为 volatile,这对读者来说更有意义,并且还允许编译器在可能的情况下应用适当的优化。
,volatile
关键字告诉编译器该变量容易产生副作用 - 即它可以被编译器不可见的东西改变。
因为 volatile
变量必须在每次使用前读取并在每次修改后保存到其永久存储位置。
在您的示例中,无法优化循环,因为变量 i
可以在循环期间更改(例如,某些中断例程会将其更改为零,因此必须再次执行循环。
你的问题的答案是:如果编译器可以确定每次进入循环只执行一次,那么就可以消除循环。
通常,优化阶段会根据迭代之间的相互关系展开循环,这会使您的(例如不确定的)循环变大几倍,以换取避免反向循环(通常会导致气泡)在管道中,取决于 cpu 类型)但不会丢失太多缓存命中......所以它有点复杂......但收益是巨大的。但是,如果您的循环只执行一次,并且总是执行,通常是因为您编写的测试始终为真(同义反复)或始终为假(不可能的事实)并且可以消除,这使得跳回变得不必要,因此,没有循环没有了。
int blockSize = 1;
for (volatilE int i=0; i<blockSize; i++)
{
foo(); // you missed a semicolon here.
}
在您的情况下,变量被分配了一个值,该值不再被触及,因此编译器要做的第一件事就是用您分配给它的文字替换变量的所有表达式。 (缺乏上下文我假设 blocsize
是一个本地自动变量,在其他任何地方都不会改变)您的代码更改为:
for (volatilE int i=0; i<1; i++)
{
foo();
}
接下来是 volatile
不是必须的,因为它的作用域是循环的块体,这里没有用到它,所以可以用如下的代码序列代替:
do {
foo();
} while (0);
foo();
编译器分析每个数据集,分析数据和变量之间的依赖关系图...... out of life),这样代码就被淘汰了。如果您让编译器从 1 到 2^64 编译 for 循环,然后停止。然后优化它的编译,你会看到你的循环被破坏了,并且会错误地认为你的处理器能够在不到一秒的时间内从 1 数到 2^64 ......但事实并非如此,2^64 仍然是一个非常大的数字,可以在不到一秒的时间内计算出来。这不是像你这样的一个固定的循环......但是在程序中完成的数据计算没有用,所以编译器将其消除。
只需测试以下程序(在这种情况下,它不是对只有一次循环的测试,而是对 2^64-1 次执行的测试):
#include <stdint.h>
#include <stdio.h>
#include <unistd.h>
int main()
{
uint64_t low = 0UL;
uint64_t high = ~0UL;
uint64_t data = 0; // this data is updated in the loop body.
printf("counTing from %lu to %lu\n",low,high);
alarm(10); /* security break after 10 seconds */
for (uint64_t i = low; i < high; i++) {
#if 0
printf("data = $lu\n",data = i ); // either here...
#else
data = i; // or here...
#endif
}
return 0;
}
(你可以把#if 0
改成#if 1
,看看优化器在你需要打印结果的时候怎么没有消除循环,但是你看到程序本质上是一样的,除了用于使用赋值结果调用 printf)
只需编译/不优化:
$ cc -O0 pru.c -o pru_noopt
$ cc -O2 pru.c -o pru_optim
然后按时运行:
$ time pru_noopt
counTing from 0 to 18446744073709551615
Alarm clock
real 0m10,005s
user 0m9,848s
sys 0m0,000s
在运行优化版本时给出:
$ time pru_optim
counTing from 0 to 18446744073709551615
real 0m0,002s
user 0m0,002s
sys 0m0,002s
(不可能,最好的计算机都不能一个接一个地计算,在不到 2 毫秒的时间内达到那个数字)所以循环一定是在其他地方。您可以从汇编代码中检查。由于data
的更新值在赋值后不使用,循环体可以消除,因此也可以消除循环体的2^64-1次执行。
现在在循环后添加以下行:
printf("data = %lu\n",data);
您会看到,即使使用 -O3
选项,循环也不会受到影响,因为所有赋值之后的值都在循环之后使用。
(我宁愿不显示汇编代码,保持高层次,但你可以看看汇编代码,看看实际生成的代码)
@H_65_197@以上是大佬教程为你收集整理的如果只有一次迭代,gcc 优化会删除 for 循环吗?全部内容,希望文章能够帮你解决如果只有一次迭代,gcc 优化会删除 for 循环吗?所遇到的程序开发问题。
如果觉得大佬教程网站内容还不错,欢迎将大佬教程推荐给程序员好友。
本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
如您有任何意见或建议可联系处理。小编QQ:384754419,请注明来意。