我没有观察到 constexpr 的有意义的优化优势
Posted
技术标签:
【中文标题】我没有观察到 constexpr 的有意义的优化优势【英文标题】:I do not observe a meaningful optimization advantage of constexpr 【发布时间】:2018-02-19 05:18:32 【问题描述】:谢谢,我不需要任何书来教我constexpr
的含义。我正在教constexpr
,而我的简单示例未能说服学生为什么他们应该通过constexpr
使用编译时计算的优势。
另外请严格避免链接到诸如this 这样没有汇编代码或分析的问题,它们对我的问题毫无意义。
我正在寻找一个例子来说明为什么constexpr
完全有用并且不能被忽略。
好吧,在许多情况下,如果 constexpr
被 const
替换,则不会发生任何错误。因此,我设计了以下示例:
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
这意味着编译器显然已经使用cosnt
或constexpr
对这两种情况执行了(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 的有意义的优化优势的主要内容,如果未能解决你的问题,请参考以下文章