C编译器是否重复(合并)代码?

循环展开是一种常见的优化,但反过来也是如此?
(减小目标文件输出的大小,减小二进制)。

我很好奇,如果编译器将连续的,相同的代码块(或函数调用)重复数据删除到循环中,或者将重复的块提取到静态函数中,这是一种常见的技术。

我很感兴趣,因为C中只有头文件库*可以添加大量重复代码,因此了解一些C编译器是否能够检测到这一点并更有效地处理它会很有用。

* 通过header-only-library,我的意思是直接定义代码而不是函数定义的头。

如果这样做,了解适用于哪些条件和约束以确保可以使用它将是有用的。

注意(出于问题的目的 – 任何流行的C编译器都是精细的GCC / Clang / Intel / MSVC)。

我找到的一个只有头文件库,名为uthash,它使用了一些非常大的宏,我想知道是否有一些编译器技巧可以巧妙地去除这些巨大的代码块,请参阅:例如uthash.h ,另一个类似的示例是内联qsort.h


可以重复数据删除的块的示例(事实certificatePy_DECREF可以扩展为相当大的代码块)。

 #define PY_ADD_TO_DICT(dict, i) \ do { PyDict_SetItemString(dict, names[i], item = PyUnicode_FromString(values[i])); \ Py_DECREF(item); \ } while (0) /* this could be made into a loop */ PY_ADD_TO_DICT(d, 0); PY_ADD_TO_DICT(d, 1); PY_ADD_TO_DICT(d, 2); PY_ADD_TO_DICT(d, 3); 

请注意,这是设计的,但基于一个真实的例子。


澄清

似乎我的问题的简短答案是否定的 (或仅在一些有限的/微不足道的情况下),只是为了澄清我为什么要求进一步提问。

评论中的一些回复似乎假设任何人只需将代码重构为函数。
当然,这几乎总是最好的选择,但有时会出现非常相似的代码块。

  • 锅炉板代码由一个非常非常智能的代码生成器创建。
  • 当使用外部API将其某些function公开为宏时(在这种情况下,在函数中本地包装当然在大多数情况下工作,但这意味着你的代码会得到它们自己的怪癖,而这些怪癖对于那些API的使用并不常见)
  • 当你不能用函数替换宏时,有一些罕见的情况,这样做最终是不切实际的。
  • 当从外部代码库导入代码时,进入并开始清理代码并不总是理想的,在评估代码库时,了解编译器在优化代码时会有多聪明。

在所有这些情况下,它可以去重复(生成更智能的代码,在函数中包装宏,修补第三方库) ,但在努力做这些事情之前,值得知道编译器为我们做了多少工作。

根据工具链的不同,您可以选择指导编译器和链接器识别和合并冗余代码。 一些好的谷歌关键字包括:

  • “代码保理” 和其他关键字
  • “整个程序优化”
  • “过程间优化” 维基百科
  • “链接时间优化” LTO

请注意,之前评论中提到的gcc优化页面提供了一些感兴趣的标志,即:

  • -ftree尾合并
  • -ftree开关转换
  • -fgcse
  • -fcrossjumping
  • -fipa-PTA
  • -fipa-icf (相同的代码折叠),在GCC5.x中添加
  • -fopa-CP
  • -flto
  • -fwhole程序

最后,这些博客文章提供了丰富的信息:

最简单的描述方法可称为重构代码。 这是你可以作为开发人员手工完成的事情,但这不是现代C编译器和优化器的特性,至少就GCC而言是这样。 下面列出了您可以在GCC中设置的所有单个优化(例如,分别通过-O0-O2 ),并且它们都不重构一系列类似的语句以在循环中使用公共索引。

  Optimization Level Zero Optimization Level Two ============================================================================================================ -fauto-inc-dec -fthread-jumps -fbranch-count-reg -falign-functions -falign-jumps -fcombine-stack-adjustments -falign-loops -falign-labels -fcompare-elim -fcaller-saves -fcprop-registers -fcrossjumping -fdce -fcse-follow-jumps -fcse-skip-blocks -fdefer-pop -fdelete-null-pointer-checks -fdelayed-branch -fdevirtualize -fdevirtualize-speculatively -fdse -fexpensive-optimizations -fforward-propagate -fgcse -fgcse-lm -fguess-branch-probability -fhoist-adjacent-loads -fif-conversion2 -finline-small-functions -fif-conversion -findirect-inlining -finline-functions-called-once -fipa-cp -fipa-pure-const -fipa-sra -fipa-profile -fisolate-erroneous-paths-dereference -fipa-reference -foptimize-sibling-calls -fmerge-constants -foptimize-strlen -fmove-loop-invariants -fpartial-inlining -fshrink-wrap -fpeephole2 -fsplit-wide-types -freorder-blocks -freorder-blocks-and-partition -freorder-functions -ftree-bit-ccp -frerun-cse-after-loop -ftree-ccp -fsched-interblock -fsched-spec -fssa-phiopt -fschedule-insns -fschedule-insns2 -ftree-ch -fstrict-aliasing -fstrict-overflow -ftree-copy-prop -ftree-builtin-call-dce -ftree-copyrename -ftree-switch-conversion -ftree-tail-merge -ftree-dce -ftree-pre -ftree-dominator-opts -ftree-vrp -ftree-dse -fuse-caller-save -ftree-forwprop -ftree-fre -ftree-phiprop -ftree-sink -ftree-slsr -ftree-sra -ftree-pta -ftree-ter -funit-at-a-time 

参考


  1. 3.10 – 控制优化的选项 ,访问2014-07-07,

虽然它似乎非常可行,但我从未见过任何优化,它将采用两个相似的代码片段并将它们组合成一个循环。

但是,编译器删除两种重复的情况(最明显的是,因为还有性能提升,而不仅仅是代码大小增益):

  1. 当两个表达式求值为相同的值时,在同一个函数中,一种称为公共子表达式消除的技术将删除冗余计算。
    有趣的是,通过积极的内联和链接时间优化,可以涵盖越来越多的案例 – 但当然内联有其严重的二进制大小成本。

  2. 当可以对代码进行矢量化时, 矢量化编译器通常可以识别它并为您进行矢量化。

除此之外,您必须手动重构代码以删除重复。