Я не наблюдаю значимого преимущества оптимизации constexpr
Спасибо, мне не нужна книга, чтобы научить меня constexpr
средства. я преподаю constexpr
и мой простой пример не убедил студентов, почему они должны использовать преимущество вычисления времени компиляции через constexpr
,
Также, пожалуйста, строго избегайте ссылок на такие вопросы, как этот, который не имеет ассемблерного кода или профилирования, и они не имеют смысла для моего вопроса.
Я ищу пример, чтобы показать, почему 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
это означает, что компилятор, очевидно, выполнил постоянное сворачивание (10!) = 3628800
для обоих случаев либо с использованием cosnt
или же constexpr
,
Компиляция выполняется через
g++ -O3 -std=c++17 -Wa,-adhln -g main.cpp>main.asm
Несмотря на то, что в большинстве случаев многие полагают, что код теперь работает быстрее без какого-либо расследования, учитывая тот факт, что компиляторы умны, мне интересно, есть ли какая-то реальная, честная и значимая выгода от оптимизации constexpr
?
2 ответа
С единственной целью оптимизации невозможно построить constexpr
последовательность вызова функции / выражения, для которой компилятор не может оптимизировать constexpr
эквивалент. Это конечно потому что constexpr
имеет ряд требований для его использования. любой constexpr
код должен быть встроенным и видимым для компилятора этого модуля перевода. Рекурсивно, через все выражения, которые приводят к генерации constexpr
значение.
Так же, constexpr
функциям не разрешается делать такие вещи, как выделять память, выполнять низкоуровневые манипуляции с указателями на функции, вызывать constexpr
функции и другие подобные вещи, которые могут помешать компилятору выполнять их во время компиляции.
Таким образом, если у вас есть constexpr
построить, эквивалент не constexpr
Версия будет иметь все эти свойства. И так как компилятор должен быть в состоянии выполнить constexpr
код во время компиляции, он должен был бы по крайней мере теоретически быть в состоянии сделать то же самое для не constexpr
эквивалент.
Оптимизирует ли это его или нет в любом конкретном случае, не имеет значения; такие вещи меняются с каждой версией компилятора. Достаточно того, что он мог.
Ваша проблема в том, что вы считаете, что основная цель constexpr
это производительность. Это оптимизация, сделанная, чтобы позволить вещи, которые вы не могли бы сделать иначе.
Роль constexpr
в производительности в первую очередь это, помечая функцию или переменную как constexpr
компилятор не позволяет вам делать то, что реализации не смогут выполнить во время компиляции. Если вы хотите выполнение во время компиляции на разных платформах, вы должны оставаться в пределах стандартных границ выполнения во время компиляции.
Синтаксис означает, что компилятор активно запрещает вам constexpr
вещи. Вы не можете случайно написать код, который не может быть запущен во время компиляции.
То есть вопрос, на который вы должны смотреть, не constexpr
код может быть написан так же без него. Будет ли вы написать код в constexpr
путь без ключевого слова. И для любой системы сложности ответ все больше приближается к "нет", если только по какой-либо другой причине, кроме простой, случайно сделать что-то, что компилятор не может запустить во время компиляции.
Вы должны использовать его в выражении 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