Вводит ли использование рекурсивных шаблонных функций накладные расходы при вызове функции или встроенный компилятор в большинстве случаев (пример ниже)? - PullRequest
0 голосов
/ 02 июля 2019

Я пытался понять практическое использование TMP.Я вижу много кода в следующих строках:

#ifndef LOOP2_HPP
#define LOOP2_HPP

// primary template
template <int DIM, typename T>
class DotProduct {
  public:
    static T result (T* a, T* b) {
        return *a * *b  +  DotProduct<DIM-1,T>::result(a+1,b+1);
    }
};

// partial specialization as end criteria
template <typename T>
class DotProduct<1,T> {
  public:
    static T result (T* a, T* b) {
        return *a * *b;
    }
};

// convenience function
template <int DIM, typename T>
inline T dot_product (T* a, T* b)
{
    return DotProduct<DIM,T>::result(a,b);
}

Является ли хорошей практикой всегда явно указывать такие сильно рекурсивные функции в явном виде?

РЕДАКТИРОВАТЬ:

Для более конкретного примера возьмем следующий код:

template <int N>
inline void f() {
    f<N-1>();
    std::cout << N << "\n";
}

template <>
void f<0>() {
    std::cout << 0 << "\n";
};

int main() {
      f<1>();
    return 0;
}

Я просто хочу использовать функцию f как способ развернуть несколько операторов cout, которые я не хочунаписать во время компиляции.Ниже приведена сборка, сгенерированная gcc-8.3, все оптимизации включены:

        void f<0>():
            push    rbp
            mov     rbp, rsp
            mov     esi, 0
            mov     edi, OFFSET FLAT:_ZSt4cout
            call    std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
            mov     esi, OFFSET FLAT:.LC0
            mov     rdi, rax
            call    std::basic_ostream<char, std::char_traits<char> >& s

td::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)
        nop
        pop     rbp
        ret
main:
        push    rbp
        mov     rbp, rsp
        call    void f<1>()
        mov     eax, 0
        pop     rbp
        ret
void f<1>():
        push    rbp
        mov     rbp, rsp
        call    void f<0>()
        mov     esi, 1
        mov     edi, OFFSET FLAT:_ZSt4cout
        call    std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
        mov     esi, OFFSET FLAT:.LC0
        mov     rdi, rax
        call    std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)
        nop
        pop     rbp
        ret

Кажется, что каждое развертывание приводит к выполнению инструкции call времени выполнения.Именно этой стоимости я хочу избежать.Я просто хочу, чтобы окончательно сгенерированный код был конкатенацией нескольких cout с.

1 Ответ

1 голос
/ 02 июля 2019

Является ли хорошей практикой всегда явно указывать такие сильно рекурсивные функции?

По-прежнему существует необходимость в шаблонировании таких функций с помощью MTP, поскольку у нас constexpr уже много лет? И даже если constexpr недостаточно, у нас будет consteval в c ++ 20 (надеюсь).

Встраивание дает компилятору только возможность оптимизировать код, но это не гарантия. Сделайте его рекурсивной функцией шаблона, дающей компилятору возможность тратить вашу память с не встроенными рекурсивными экземплярами шаблона, что противоположно тому, чего вы хотите достичь! Если вы скомпилируете с -O0, вы увидите много кода, сгенерированного из вашего примера.

Вы можете заставить компилятор генерировать результат во время компиляции, при условии, что вы можете использовать результирующее значение в качестве параметра шаблона, например.

Но как всегда в отношении оптимизации: 1) Постарайтесь получить лучший алгоритм 2) Попробуйте реализовать, чтобы сделать код поддерживаемым 3) Мера 4) Мера 5) Мера

И только если ваш код не соответствует вашим требованиям к скорости, начните оптимизацию вручную.

Фактически, ваш код может тратить много памяти, а также имеет возможность хорошо оптимизировать. Но вы должны перейти к constexpr функциям вместо того, чтобы использовать более или менее нечитаемый код MTP. Таким образом, «встроенный» является лишь очень маленькой частью проблемы.

Ваш компилятор лучше, как вы считаете! Обычно! Если не веришь: измеряй! И только если вы видите реальную проблему: оптимизация ручной работы.

При использовании constexpr, особенно с рекурсивными функциями, большинство компиляторов предоставляют флаги командной строки, чтобы дать уровень для более глубокой оценки времени компиляции, если вы НЕ заставляете компилятор получать результат во время компиляции с использованием результата в качестве параметра шаблона или любой другой «должен быть постоянной времени компиляции», такой как размер массива. Это зависит от используемого вами компилятора, поэтому, пожалуйста, прочитайте руководство!

Если вы используете std::cout внутри рекурсии / цикла, вы никогда не увидите «оптимизацию с одним выходом». Но вообще: если у вас достаточно времени для использования std::cout, вам не нужно думать о нескольких линиях сборки вокруг него. std::cout обычно медленно связан с кодом, генерирующим данные, которые вообще должны быть записаны на консоль!

Не оптимизируйте неправильные вещи!

Добавить в: Если вы действительно хотите сгенерировать строку времени компиляции из списка целых чисел, вы можете взять ее в качестве основы для своего примера: C ++ преобразовать целое число в строку во время компиляции

...