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

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

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

我的问题:

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

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

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

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

编辑

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

为什么要展开循环?

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

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

正如您可以想象的那样,展开循环会消除分支以及这些停顿的可能性,尤其是在几率与猜测相反的情况下。

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

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

为什么展开循环?

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

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

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

那我该怎么办?

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

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

过早的优化是所有邪恶的根源 – 唐纳德克努特

首先介绍,稍后优化。

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

现在回答你的问题:

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

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

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

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