当前位置: 首页 > 知识库问答 >
问题:

GCC循环展开标志真的有效吗?

祁嘉瑞
2023-03-14

在C语言中,我有一个任务,我必须做乘法、求逆、移位、加法等,用巨大的矩阵分配为二维数组(数组的数组)。

我找到了gcc标志-funroll all loops。如果我理解正确,这将自动展开所有循环,而无需程序员进行任何努力。

我的问题是:

a) gcc是否包括这种带有各种优化标志的优化,如-O1-O2等。?

b)我必须在我的代码中使用任何Pragmas来利用循环展开,还是自动识别循环?

c)如果展开提高了性能,为什么默认情况下不启用此选项?

d) 为了以最好的方式编译程序,建议使用哪些gcc优化标志?(我必须运行针对单个CPU系列优化的程序,这与我编译代码的机器相同,实际上我使用march=native-O2标志)

编辑

似乎对于展开的使用存在争议,在某些情况下可能会降低性能。在我的情况下,有各种方法可以在2个嵌套的循环中对大量元素进行迭代矩阵元素的数学运算。在这种情况下,展开如何降低或提高性能?

共有3个答案

濮阳君浩
2023-03-14

你对这个问题有了一个理论背景,这就给你留下了足够的空间来猜测你在实际运行中得到了什么。据说,该选项并不总是提高性能,因为它取决于多种因素,例如循环实现、其负载/主体等。

每种代码都是不同的,如果您想找到性能更好的解决方案,最好运行这两种变体,测量它们的执行时间并进行比较。

看看下面答案中的方法,了解时间测量的概念。换句话说,您只需将代码包装到循环中,这将导致程序运行几秒钟。在优化循环本身时,最好编写一个shell脚本,它可以多次运行应用程序。

袁雅逸
2023-03-14

如果编译器不能在编译时预测循环的确切迭代量(或者至少预测一个上界,然后根据需要跳过尽可能多的迭代),循环展开就不起作用。这意味着如果你的矩阵大小是可变的,标志将没有影响。

现在回答你的问题:

a) gcc是否包括这种带有各种优化标志的优化,如-O1、-O2等。?

不,您必须显式地设置它,因为它可能会或可能不会使代码运行更快,并且通常会使可执行文件更大。

b)我必须在我的代码中使用任何实用程序来利用循环展开还是循环自动识别?

没有语法。对于-funroll-loops,编译器启发式地决定要展开哪些循环。如果您想强制展开,可以使用-funroll-all-loops,但这通常会使代码运行得更慢。

c)如果展开提高了性能,为什么默认情况下不启用此选项?

它并不总能提高性能!此外,并不是一切都与性能有关。有些人实际上很在乎小的可执行文件,因为它们的内存很少(参见:嵌入式系统)

d)推荐的gcc优化标志是什么,以尽可能最好的方式编译程序?(我必须运行这个针对单个CPU系列进行优化的程序,这与我编译代码的机器相同,实际上我使用的是游行=本机和-O2标志)

没有灵丹妙药。你需要思考、测试和观察。实际上有一个定理指出,没有完美的编译器可以存在。

你介绍过你的节目吗?对这些事情来说,分析是一项非常有用的技能。

资料来源(主要是):https://gcc.gnu.org/onlinedocs/gcc-3.4.4/gcc/Optimize-Options.html

阙辰龙
2023-03-14

现代处理器采用流水线指令。他们喜欢知道下一步会发生什么,并根据指令执行顺序的假设进行各种奇特的优化。

然而,在循环结束时,有两种可能性!要么你回到顶部,要么继续。处理器会做出一个有根据的猜测,判断哪一个会发生。如果它答对了,一切都很好。如果没有,它必须冲洗管道,并在准备接管另一个分支时暂停一会儿。

正如你所能想象的,展开一个循环可以消除分支和那些停滞的可能性,尤其是在概率不利于猜测的情况下。

想象一个代码循环执行三次,然后继续。如果你假设(处理器可能会这样做)最后你会重复这个循环。三分之二的时间,你是对的!但是三分之一的时间,你会停顿。

另一方面,想象同样的情况,但代码循环3000次。在这里,展开可能只会增加1/3000的时间。

上面提到的处理器幻想的一部分涉及将指令从内存中的可执行文件加载到处理器的板载指令缓存(简称I-cache)。它保存的指令数量有限,可以快速访问,但在需要从内存加载新指令时可能会暂停。

让我们回到前面的例子。假设循环中相当少量的代码占用I-cache的n字节。如果我们展开循环,它现在将占用n*3字节。再多一点,但它可能只适合一条缓存线,这样你的缓存将以最佳方式工作,而不需要暂停从主内存读取。

然而,3000循环会展开以使用巨大的n*3000字节的I-cache。这将需要从内存中进行多次读取,并且可能会将程序中其他地方的一些有用内容从I-cache中推出。

如您所见,展开为较短的循环提供了更多的好处,但如果您打算循环大量次,最终会破坏性能。

通常,聪明的编译器会很好地猜测要展开哪个循环,但是如果你确定你知道得更好,你可以强制它。你如何更好地了解?唯一的方法是两种方法都尝试并比较时间!

过早优化是万恶之源唐纳德·克努斯

先做个人简介,然后再进行优化。

 类似资料:
  • 是否有一种方法可以指示GCC(我使用的是4.8.4)完全展开底部函数中的<code>循环,即剥离该循环?循环的迭代次数在编译时是已知的:58。 让我先解释一下我所做的努力。 通过检查GAS输出: 使用了12个寄存器XMM0 - XMM11。如果我将标志< code>-funroll-loops传递给gcc: 循环只展开两次。我检查了GCC优化选项。GCC说也会打开,所以当GCC展开循环时,它在寄存

  • 问题内容: JIT的循环展开策略是什么?或者,如果没有简单的答案,那么有什么方法可以检查循环中展开循环的位置/时间? 基本上,我上面有一段代码,具有静态的迭代次数(八),当我保持for循环不变时,它的效果很差。但是,当我手动展开循环时,效果会更好。我有兴趣了解JIT是否确实展开了循环,如果没有,那么为什么。 问题答案: 如果 JVM展开,则可以通过实际打印生成的程序集来最好地解决该循环。请注意,这

  • 问题内容: 我想在python中创建一个高效的循环缓冲区(目标是取缓冲区中整数值的平均值)。 这是使用列表收集值的有效方法吗? 什么会更有效(为什么)? 问题答案: 我会用一个arg 在文档中有一个与您想要的菜谱相似的菜谱。我断言它是最有效的,这完全取决于它是由C语言实现的,这是由一个熟练掌握了一流代码的技术人员组成的。

  • 我正在使用GCC4.4.2构建一些大型项目。因为我想构建它以供发布,所以我使用了GCC优化标志,但不幸的是,它在某种程度上弄乱了我的代码,最终的二进制文件没有按照预期工作,当使用标志(或没有优化)构建时,一切都很好。我之前的项目也有类似的问题,当时是标志在优化级别上造成了问题,我通过搜索本文档中提到的所有标志,就优化级别而言,设法发现它是由该特定标志引起的: http://gcc.gnu.org/

  • 问题内容: 我已经听过好几次了。向后计数时,JavaScript循环真的更快吗?如果是这样,为什么?我已经看到了一些测试套件示例,这些示例显示了反向循环更快,但是我找不到关于原因的任何解释! 我假设这是因为循环不再需要在每次检查是否完成时都求值一个属性,而只需要检查最终的数值即可。 即 问题答案: 不是比快。实际上,它们都一样快。 在递增循环中需要花费时间的是,每个循环都需要评估数组的大小。在此循

  • 问题内容: 我正在编写一种简单的算法来检查整数的素数,但在将以下Java代码转换为Python时遇到问题: 因此,我一直在尝试使用此方法,但是显然我跳过了除法3: 问题答案: 从技术上讲,Python中唯一的-loop是“ for-each”,因此您可以使用类似 当然,Python可以做得更好: 也将是等效的(假设该Java循环的末尾有一个)。确实,后者将被视为采用 Python的 方式来实现。