在C中,我有一个任务,我必须进行乘法,反演,trasposition,add等等,将巨大的矩阵分配为二维数组(数组的数组).
我找到了gcc旗帜-funroll-all-loops.如果我理解正确,这将自动展开所有循环,而无需程序员的任何努力.
我的问题:
一) GCC是否包括这种优化的各种优化标志-O1, -O2等等?
b)我是否必须使用pragma代码中的任何内容来利用循环展开或自动识别循环?
c)如果展开提高了性能,为什么默认情况下不启用此选项?
d)以最佳方式编译程序的推荐gcc优化标志是什么?(我必须运行这个针对单个CPU系列优化的程序,这与我编译代码的机器相同,实际上我使用march=native和-O2标记)
编辑
似乎存在关于使用展开的争议,在某些情况下可能会降低性能.在我的情况下,有各种方法可以简单地将数学运算嵌套在循环中,用于为大量元素完成的迭代矩阵元素.在这种情况下,展开如何减慢或提高性能?
eth*_*ger 23
现代处理器管道指令.他们喜欢知道接下来会发生什么,并根据假设执行指令的顺序做出各种奇特的优化.
在循环结束时,有两种可能性!要么你回到顶部,要么继续.处理器对将要发生的事情做出有根据的猜测.如果它做对了,一切都很好.如果没有,它必须冲洗管道并在准备接收另一个分支时停转一段时间.
你可以想象,展开一个循环可以消除分支和这些失速的可能性,特别是在赔率与猜测相反的情况下.
想象一下执行3次的代码循环,然后继续.如果你假设(作为处理器可能会)最后你将重复循环.2/3的时间,你会是正确的!1/3的时间,你会停滞不前.
另一方面,想象相同的情况,但代码循环3000次.在这里,展开的时间可能只有1/3000倍.
上面提到的处理器功能的一部分涉及将来自存储器中的可执行文件的指令加载到处理器的板载指令高速缓存(缩写为I-cache).这包含可以快速访问的有限数量的指令,但是当需要从存储器加载新指令时可能会停止.
让我们回到前面的例子.假设循环中相当少量的代码占用了nI-cache的字节.如果我们展开循环,它现在占用了n * 3字节.多一点,但它可能恰好适合单个缓存行,因此您的缓存将以最佳方式工作,而不需要停止从主内存读取.
然而,3000循环展开使用高达n * 3000I-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