编译器是不是将给定常量参数的简单函数简化为唯一指令?

Posted

技术标签:

【中文标题】编译器是不是将给定常量参数的简单函数简化为唯一指令?【英文标题】:Do compilers reduce simple functions given constant arguments into unique instructions?编译器是否将给定常量参数的简单函数简化为唯一指令? 【发布时间】:2015-07-19 13:21:01 【问题描述】:

这是我一直认为是真实的,但从未得到任何验证。考虑一个非常简单的函数:

int subtractFive(int num) 
    return num -5;

如果对该函数的调用使用编译时间常数,例如

  getElement(5);

启用了优化的编译器很可能会内联它。然而,我不清楚的是 num - 5 是否将在运行时或编译时进行评估。表达式简化会以这种方式通过内联函数递归扩展吗?还是不超越功能?

【问题讨论】:

我希望一个像样的编译器将第二个表达式替换为 0,是的。但是除了查看程序集之外没有其他方法可以确定 如果是简单的代码,我喜欢使用this page,因为它可以方便地过滤掉gcc -S 产生的大部分噪音。 我不是专家,但我认为这可能有助于实现 constexpr 的功能。 如果它是内联的,它就不再是一个不同的函数了。 @Olaf 如果有人不想获得 C++ 答案,他们不应该标记 C++ 问题。 【参考方案1】:

我们可以简单地查看生成的程序集来找出答案。这段代码:

int subtractFive(int num) 
    return num -5;


int main(int argc, char *argv[]) 
  return subtractFive(argc);

使用g++ -O2 编译产生

leal    -5(%rdi), %eax
ret

所以函数调用确实被简化为一条指令。这种优化技术被称为inlining。

当然可以使用相同的技术来查看编译器会走多远,例如稍微复杂一点

int subtractFive(int num) 
    return num -5;


int foo(int i) 
    return subtractFive(i) * 5;


int main(int argc, char *argv[]) 
  return foo(argc);

仍然被编译为

leal    -25(%rdi,%rdi,4), %eax
ret

所以这里的两个函数都在编译时被消除了。如果foo 的输入在编译时已知,则函数调用将(在这种情况下)简单地替换为编译时生成的常量 (Live)。

编译器还可以将此内联与常量折叠结合起来,如果所有参数都是编译时常量,则用其完全评估的结果替换函数调用。例如,

int subtractFive(int num) 
    return num -5;


int foo(int i) 
    return subtractFive(i) * 5;


int main() 
  return foo(7);

编译成

mov     eax, 10
ret

相当于

int main () 
    return 10;

编译器总是会在它认为是个好主意的地方执行此操作,并且(通常)在这种低级别优化代码方面比您更好。

【讨论】:

我会说如果你告诉它这样做(或者如果你不告诉它不要这样做)它会一直这样做。 IOW,这取决于编译器标志。 @ChristianHackl 好吧,在这个简单的例子中,它肯定总是内联的(如果它真的可以看到定义,否则只能通过 LTO)。但是当然不应该开始明确地告诉编译器要内联什么,除非通过分析证明编译器做出了错误的决定。 但是调试版本呢?如果subtractFive 中的断点没有被命中,我会感到惊讶,因为编译器无条件地内联函数。 @ChristianHackl 在调试构建中当然没有内联,但调试构建的性能无论如何都无关紧要。【参考方案2】:

做一个小测试很容易;考虑以下

int foo(int);
int bar(int x)  return x-5; 
int baz()  return foo(bar(5)); 

使用g++ -O3 编译,函数baz 的asm 输出为

xorl    %edi, %edi
jmp _Z3fooi

这段代码在第一个参数中加载一个0,然后跳转到foo的代码中。所以 bar 中的代码完全消失了,传递给 foo 的值的计算已经在编译时完成了。

另外返回调用函数的值变成了函数代码的跳转(这叫做“尾调用优化”)。

【讨论】:

【参考方案3】:

智能编译器将在编译时评估它并替换 getElement(5),因为它永远不会有不同的结果。没有一个变量被认为是易变的。

【讨论】:

以上是关于编译器是不是将给定常量参数的简单函数简化为唯一指令?的主要内容,如果未能解决你的问题,请参考以下文章

array_reduce — 用回调函数迭代地将数组简化为单一的值

array_reduce — 用回调函数迭代地将数组简化为单一的值

如何按值传递sql函数参数

我正在尝试将我的多个数组方法调用简化为 if 语句或更简单的基于条件的语句

按位常量和指针:

C字符串常量定义