gcc 愚者标准优化行为

p5fdfcr1  于 2022-11-30  发布在  其他
关注(0)|答案(3)|浏览(186)

这里我编译了一个优化级别为-O2的输入程序(使用gcc 4.8.4),并测量了执行时间:

gcc -O2 -c test.c -o obj.o
TIMEFORMAT='%3R' &&  time(./obj.o)
execution time = 1.825

当我将-O2标志替换为在愚者manuel中定义的选项列表时,在级别-O2 www.example.com中打开https://gcc.gnu.org/onlinedocs/gcc-4.8.4/gcc/Optimize-Options.html#Optimize-Options如下所示:

gcc -fauto-inc-dec -fcompare-elim -fcprop-registers -fdce -fdefer-pop -fdse -fguess-branch-probability -fif-conversion2 -fif-conversion -fipa-pure-const -fipa-profile -fipa-reference -fmerge-constants -fsplit-wide-types -ftree-bit-ccp  -ftree-builtin-call-dce -ftree-ccp -ftree-ch -ftree-copyrename -ftree-dce -ftree-dominator-opts -ftree-dse -ftree-forwprop -ftree-fre -ftree-phiprop -ftree-slsr -ftree-sra -ftree-pta -ftree-ter -funit-at-a-time -fthread-jumps -falign-functions  -falign-jumps -falign-loops  -falign-labels -fcaller-saves -fcrossjumping -fcse-follow-jumps  -fcse-skip-blocks -fdelete-null-pointer-checks -fdevirtualize -fexpensive-optimizations -fgcse  -fgcse-lm  -fhoist-adjacent-loads -finline-small-functions -findirect-inlining -fipa-sra -foptimize-sibling-calls -fpartial-inlining -fpeephole2 -fregmove  -freorder-blocks  -freorder-functions -frerun-cse-after-loop -fsched-interblock  -fsched-spec -fschedule-insns  -fschedule-insns2 -fstrict-aliasing -fstrict-overflow -ftree-switch-conversion -ftree-tail-merge -ftree-pre -ftree-vrp -c test.c -o obj.o
    TIMEFORMAT='%3R' &&  time(./obj.o)
execution time = 2.652

我的问题是,为什么执行时间不同,即使如此,我应用了相同的优化?

更新

如果(根据愚者文件):
并非所有优化都直接由标志控制。
那么,研究人员如何使用来重现优化序列,甚至比标准优化序列更快(使用他们用来生成数千个优化序列的进化算法,并收集那些在执行时间方面影响最大的优化序列)
例如“Acovea”http://hg.ahs3.net/acovea/debian/html/acoveaga.html
和“科尔”http://users.elis.ugent.be/~leeckhou/papers/cgo08.pdf

e0uiprwp

e0uiprwp1#

您知道程序的单一内存布局会对性能产生高达40%的巨大影响吗?下面是一些会改变应用程序内存布局的相当愚蠢的事情:

  • 链接时链接外部库的顺序
  • 运行时环境变量的数量和内容
  • 运行二进制文件的目录的名称

是的,我不是在开玩笑。仅仅因为你从位置A而不是位置B运行相同的二进制文件,它可能会运行快/慢40%,因为路径被推到堆栈,因此它的长度改变了堆栈布局,这影响了缓存,分支预测等。
现在大多数人测量性能的方法都是错误的。分析器远不如几年前那么有用,那时CPU仍然是线性扩展的。
以下是一段关于性能的演讲视频,它可以让你真正了解这一主题,以及如何正确衡量性能:
https://www.youtube.com/watch?v=r-TLSBdHe1A
我强烈推荐每一个低级程序员观看这段视频。它有42分钟,非常值得一看。同时,它还向你展示了一些免费的工具,你可以用它们来衡量性能的提高,而不会被统计学上的欺骗所欺骗。
是的,它直接解决了你的问题,编译器开发人员如何知道O2真的比O 1好,或者他们真的知道吗?事实上,情况并不总是这样。
性能优化是一件脆弱的事情,在一个系统上让你的应用更快的优化可能会在另一个系统上产生相反的效果,所以对于真实的的优化,你需要计算它的可能性有多大,运行时的变化只是因为内存布局等事情发生了变化,或者是一个真正的、实际的改进。甚至这个主题在视频中也有简要的介绍。
除了这个视频,我只能建议你看一下生成的汇编代码。如果代码不同,这可能解释了运行时的差异,也提供了优化差异的提示。也许这一切都归结为文档过时,事实上O2只是打开/关闭没有提到的标志。如果代码是相同的,也许这是一种只有在喜欢时才会发生的巧合效应。
因此,尝试将-S添加到gcc调用中,并比较生成的汇编代码。

bis0qfac

bis0qfac2#

上次我检查了一下,传递所有-O2所隐含的-f选项(但不是任何-O选项)仍然没有阻止愚者在语句之间溢出/重新加载值到内存,也就是说,调试模式行为是为了一致的调试。
Why does clang produce inefficient asm with -O0 (for this simple floating point sum)?)如果您查看asm输出,这将是显而易见的:
How to remove "noise" from GCC/clang assembly output?
当然,这对于性能来说是完全无用的。存储/重载延迟将是一个很大的瓶颈,即使对于具有乱序执行的现代CPU也是如此。而且不会有任何通过非const变量的常量传播。
Marc Glisse在几年前对此进行了评论,并指出愚者文档中说,在3.11 Options That Control Optimization的顶部-

**并非所有优化都由标志直接控制。**本节仅列出具有标志的优化。

因此,如果您希望愚者真正努力地优化您的代码,始终以-O3开始(如果您只想在此计算机上运行它,最好是-march=native,也希望是-flto或其他支持跨文件内联的代码)。
然后,如果-O3中有一些选项未启用,请考虑向-O3添加更多-f和/或-m选项。(例如-funroll-loops,这可能会使大型程序的情况变得更糟,因此仅在默认情况下作为档案导引优化的一部分启用,-fprofile-generate/使用一些 * 代表性 * 输入至少运行一次/-fprofile-use。)
或者从-O3中减去一些,或者从-O2开始,如果这是你的目标,如果-O3可能对优化启发式有一些影响,比如更愿意展开得更远,那么再加上一些。

xxb16uws

xxb16uws3#

有2个好的优化应该知道:

  • -O2:优化空间和时间(注意:不初始化变量)
  • -Os:优化空间和时间(注意:不初始化变量)

相关问题