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

时间:2014-06-13 00:40:47

标签: c gcc gcc4.8

在C中,我有一个任务,我必须进行乘法,反演,trasposition,加法等等,将巨大的矩阵分配为二维数组(数组数组)。

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

我的问题:

a) gcc是否将这种优化包含在-O1-O2等各种优化标记中?

b)我是否必须在代码中使用任何pragma来利用循环展开或自动识别循环?

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

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

修改

似乎有关使用展开的争议,在某些情况下可能会降低性能。在我的情况下,有各种方法可以简单地将数学运算嵌套在循环中,用于为大量元素完成的迭代矩阵元素。在这种情况下,展开如何减慢或提高性能?

3 个答案:

答案 0 :(得分:19)

为什么要展开循环?

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

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

你可以想象,展开一个循环会消除分支和这些失速的可能性,特别是在赔率与猜测相反的情况下。

想象一下执行3次的代码循环,然后继续。如果你假设(作为处理器可能会)最后你将重复循环。 2/3的时间,你是对的!但是,有1/3的时间,你会停滞不前。

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

为什么展开循环?

上面提到的处理器功能的一部分涉及将来自存储器中的可执行文件的指令加载到处理器的板载指令高速缓存(缩写为I-cache)。这包含有限数量的指令,可以快速访问,但是当需要从内存中加载新指令时可能会停止。

让我们回到之前的例子。假设循环中相当少量的代码占用I-cache的n个字节。如果我们展开循环,它现在占用n * 3个字节。更多一点,但它可能恰好适合单个缓存行,因此您的缓存将以最佳方式工作,而不需要停止从主内存读取。

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

那我该怎么办?

正如您所看到的,展开为缩短循环提供了更多好处,但如果您打算多次循环,则会导致性能下降。

通常情况下,智能编译器会对要展开的循环进行合理的猜测,但如果你确定 你知道的更好,那么你可以强制它。你如何更好地了解?唯一的方法是尝试两种方式并比较时间!

过早优化是所有邪恶的根源 - Donald Knuth

优先配置文件,稍后进行优化。

答案 1 :(得分:7)

如果编译器无法在编译时预测循环的精确迭代次数(或者至少预测上限,然后根据需要跳过尽可能多的迭代),则循环展开不起作用。这意味着如果您的矩阵大小是可变的,则该标志将不起作用。

现在回答你的问题:

  

a)gcc是否包含各种各样的优化   优化标志为-O1,-O2等?

不,你必须明确设置它,因为它可能会或可能不会使代码运行得更快,它通常会使可执行文件更大。

  

b)我是否必须在代码中使用任何编译指示来利用循环展开或自动识别循环?

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

  

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

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

  

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

没有银弹。你需要思考,测试和看到。实际上有一个定理表明不存在完美的编译器。

您是否对自己的计划进行了介绍?对于这些事情,分析是非常有用的技能。

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

答案 2 :(得分:3)

您正在获得有关该问题的理论背景,并留下足够的空间来猜测您在实际运行中获得的内容。据说该选项并不总是提高性能,因为它取决于各种因素,例如循环实现,其负载/主体等。

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

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