循环展开是一种常见的优化,但反过来也是如此?
(减少目标文件输出的大小,减小二进制)。
我很好奇,如果编译器将连续的,相同的代码块(或函数调用)重复数据删除到循环中,或者将重复的块提取到静态函数中,这是一种常见的技术。
我很感兴趣,因为C中只有标头库*
可以添加大量重复的代码,所以知道一些C编译器是否能够检测到并处理它更有用有效。
*
通过header-only-library,我的意思是直接定义代码而不是函数定义的头。
如果这样做,了解在什么条件和条件下是有用的。限制适用于确保可以使用。
注意(出于问题的目的 - 任何流行的C编译器都是精细的GCC / Clang / Intel / MSVC)。
我发现的一个仅限标头的库,名为 uthash 使用了一些非常大的宏,我想知道是否有一些编译器技巧可以巧妙地去除这些巨大的代码块,参见:例如uthash.h,另一个类似的例子是inline qsort.h
可以重复数据删除的块示例(事实证明Py_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);
请注意,这是设计的,但基于一个真实的例子。
似乎我的问题的简短答案是没有(或仅在某些有限/无关紧要的情况下),只是为了澄清我为什么要进一步询问。
评论中的一些回复似乎假设任何人只需将代码重构为函数 当然,几乎总是是最好的选择,但有时会出现非常相似的代码块。
在所有这些情况下,它可以去除(生成更智能的代码,在函数中包装宏,修补第三方库),但在努力做这些事情之前,值得知道如何编译器为我们做了很多工作。
答案 0 :(得分:13)
根据工具链的不同,您可以选择指导编译器和链接器识别和合并冗余代码。一些好的谷歌关键字包括:
请注意,之前评论中提到的gcc optimizations page提供了一些感兴趣的标记,即:
最后,这些博客文章提供了丰富的信息:
答案 1 :(得分:5)
最简单的描述方法可称为重构代码。这是你可以作为开发人员手工完成的事情,但这不是现代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
<强>参考强>
<https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html>
答案 2 :(得分:3)
虽然它似乎非常可行,但我从未见过任何优化,它将采用两个相似的代码片段并将它们组合成一个循环。
然而,有两种重复的情况,编译器删除了 (最明显的是,因为它还有性能增益,而不仅仅是代码大小增益):
当两个表达式计算到相同的值时,在同一个函数中,一个名为common subexpression elimination的技术会删除冗余计算。
有趣的是,通过积极的内联和链接时间优化,可以涵盖越来越多的案例 - 但当然内联有其严重的二进制大小成本。
当可以对代码进行矢量化时,vectorizing compiler通常可以识别并为您进行矢量化。
除此之外,您还必须手动重构代码以删除重复。