我没有观察到 constexpr 的有意义的优化优势

Posted

技术标签:

【中文标题】我没有观察到 constexpr 的有意义的优化优势【英文标题】:I do not observe a meaningful optimization advantage of constexpr 【发布时间】:2018-02-19 05:18:32 【问题描述】:

谢谢,我不需要任何书来教我constexpr 的含义。我正在教constexpr,而我的简单示例未能说服学生为什么他们应该通过constexpr 使用编译时计算的优势。

另外请严格避免链接到诸如this 这样没有汇编代码或分析的问题,它们对我的问题毫无意义。


我正在寻找一个例子来说明为什么constexpr 完全有用并且不能被忽略。

好吧,在许多情况下,如果 constexprconst 替换,则不会发生任何错误。因此,我设计了以下示例:

main_const.cpp

#include <iostream>
using namespace std;

const int factorial(int N)

    if(N<=1)
        return 1;
    else 
        return N*factorial(N-1);


int main()

    cout<<factorial(10)<<endl;
    return 0;

main_constexpr.cpp

#include <iostream>
using namespace std;

constexpr int factorial(int N)

    if(N<=1)
        return 1;
    else 
        return N*factorial(N-1);


int main()

    cout<<factorial(10)<<endl;
    return 0;

但问题是,对于他们前者来说,汇编代码是

main_const.asm

12:main_last.cpp **** int main()
13:main_last.cpp **** 
132                     .loc 1 13 0
133                     .cfi_startproc
134 0000 4883EC08       subq    $8, %rsp
135                     .cfi_def_cfa_offset 16
14:main_last.cpp ****   cout<<factorial(10)<<endl;
136                     .loc 1 14 0
137 0004 BE005F37       movl    $3628800, %esi
137      00
138 0009 BF000000       movl    $_ZSt4cout, %edi
138      00
139 000e E8000000       call    _ZNSolsEi

对于后者来说是

main_constexpr.asm

12:main_now.cpp  **** int main()
13:main_now.cpp  **** 
11                      .loc 1 13 0
12                      .cfi_startproc
13 0000 4883EC08        subq    $8, %rsp
14                      .cfi_def_cfa_offset 16
14:main_now.cpp  ****   cout<<factorial(10)<<endl;
15                      .loc 1 14 0
16 0004 BE005F37        movl    $3628800, %esi
16      00
17 0009 BF000000        movl    $_ZSt4cout, %edi
17      00
18 000e E8000000        call    _ZNSolsEi
18      00

这意味着编译器显然已经使用cosntconstexpr 对这两种情况执行了(10!) = 3628800 的常量折叠。

编译通过

g++ -O3 -std=c++17 -Wa,-adhln -g main.cpp>main.asm

尽管在大多数情况下,许多人认为代码现在运行得更快而没有任何调查,考虑到编译器很聪明的事实,我想知道constexpr背后是否有任何真正、诚实和有意义的优化好处?

【问题讨论】:

Difference between `constexpr` and `const`的可能重复 @ar2015:“你读过这个问题吗?”我不确定我是否看到这两个问题之间有特别大的区别。这两个问题最终都在问为什么要使用constexpr。他们只是以不同的方式询问。 @ar2015:“我正在教 constexpr,我的简单示例未能说服学生为什么他们应该通过 constexpr 使用编译时间计算的优势。” 使用此类微不足道的学生在这一点上,示例无法质疑他们被告知要做什么。 constexpr 是一个重要的工具,但在您开始了解模板实例化或其他必须在编译时发生的机制之前,它并不是那么重要。 @ar2015: constexpr 不是关于“优化优势”。这不是为什么要将此功能添加到 C++ 中。 @ar2015:我不在乎什么是“非常普遍的信念”。程序员有很多不真实的“非常普遍的信念”。你受到了“巨大的攻击”,因为你的问题是基于不真实的成见和想法。 【参考方案1】:

为了优化的唯一目的,不可能构造一个constexpr表达式/函数调用序列,编译器不可能优化一个非-constexpr 等价物。这当然是因为constexpr 对其使用有很多要求。任何constexpr 代码都必须内联并且对该翻译单元的编译器可见。递归地,通过所有导致生成constexpr 值的表达式。

同样,constexpr 函数不允许执行诸如分配内存(尚未)、执行低级函数指针操作(尚未)、调用非constexpr 函数以及其他类似的事情会阻止编译器无法在编译时执行它们。

因此,如果您有constexpr 构造,则等效的非constexpr 版本将具有其实现的所有这些相同属性。而且由于编译器必须能够在编译时执行constexpr 代码,它至少在理论上必须能够对非constexpr 等效代码执行相同的操作。

在任何特定情况下是否对其进行优化都无关紧要;这样的事情会随着每个编译器版本而改变。能做到这一点就足够了。

您的问题是您认为constexpr 的主要目的是性能。这是一项优化,以允许您无法做到的事情。

constexpr 在性能中的作用主要在于,通过将函数或变量标记为 constexpr,编译器阻止您执行实现在编译时可能无法执行的操作时间。如果你想跨平台编译时执行,你必须保持在编译时执行的标准定义边界内。

语法意味着编译器会主动阻止你做非constexpr 的事情。您不能意外编写无法在编译时运行的代码。

也就是说,您应该关注的问题不是constexpr 代码是否可以在没有它的情况下以相同的方式编写。这是您是否会在没有关键字的情况下以constexpr 的方式编写代码。对于任何复杂的系统,答案越来越接近“否”,如果没有其他原因,很容易意外地做一些编译器在编译时无法运行的事情。

【讨论】:

【参考方案2】:

您必须在 constexpr 表达式中使用它来强制编译时评估:

int main()

    constexpr int fact_10 = factorial(10); // 3628800
    std::cout << fact_10 << std::endl;
    return 0;

否则你依赖编译器优化。

此外,constexpr 允许使用它,而简单的 const 则不允许:

所以假设:

constexpr int const_expr_factorial(int) /*..*/
int factorial(int) /*..*/

你有:

char buffer[const_expr_factorial(5)]; // OK
char buffer[factorial(5)]; // KO, might compile due to VLA extension

std::integral_constant<int, const_expr_factorial(10)> fact_10; // OK
std::integral_constant<int, factorial(10)> fact_10; // KO

【讨论】:

用 g++ 你可以尝试 -pedantic 来禁用我认为的 VLA。

以上是关于我没有观察到 constexpr 的有意义的优化优势的主要内容,如果未能解决你的问题,请参考以下文章

函数内的静态 constexpr 变量是不是有意义?

上一次暑假最有意义的事是什么?

如何将状态保存为 db 中的代码,但将它们映射到 rails 中有意义的单词?

没有有意义的错误的快速尾随关闭失败

DB2调优资源监控

Scikit-Learn 中的神经网络没有产生有意义的结果