Я не наблюдаю значимого преимущества оптимизации 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
Другие вопросы по тегам