c++ - 使用递归模板函数是否会引入函数调用开销,或者编译器是否在大多数情况下内联它(下面的示例)?

标签 c++ recursion c++14 template-meta-programming

我一直在努力了解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 的串联。

最佳答案

Is it a good practice to always explicitly inline such heavily recursive functions?

既然我们已经使用了多年的 constexpr,是否仍然需要使用 MTP 来模板化这些函数?即使 constexpr 还不够,我们将在 c++20 中拥有 consteval(希望如此)。

内联只是给了编译器优化代码的机会,但不是保证。使它成为递归模板函数使编译器有机会使用非内联递归模板实例浪费您的内存,这与您想要实现的目标相反!如果您使用 -O0 进行编译,您会看到您的示例生成了大量代码。

您可以强制编译器在编译时生成结果,例如,只要您可以将结果值用作模板参数即可。

但一如既往地关于优化: 1)尝试得到最好的算法 2)尝试实现使代码可维护 3) 测量 4) 测量 5)测量

只有当您的代码没有满足您的速度要求时,才开始手动优化。

事实上,你的代码有机会浪费大量内存,也有机会优化得很好。但是您应该转向 constexpr 函数,而不是使用或多或少不可读的 MTP 代码。所以“内联”只是问题的一小部分。

如您所信,您的编译器更好!通常情况下!如果您不信任:测量!并且只有当您看到真正的问题时:手工优化。

如果使用 constexpr,尤其是对于递归函数,如果您不强制编译器在编译时使用结果作为模板参数或任何其他“必须是编译时间常量”,如数组的大小。这取决于您使用的编译器,因此请阅读手册!

如果您在递归/循环中使用 std::cout,您永远不会看到“单一输出优化”。但总而言之:如果您有足够的时间使用 std::cout,您就不必考虑它周围的几行汇编代码。 std::cout 通常与生成应该写入控制台的数据的代码有关!

不要优化错误的东西!

补充: 如果你真的想从一个整数列表生成一个编译时字符串,你可以把它作为你的例子的基础:C++ convert integer to string at compile time

关于c++ - 使用递归模板函数是否会引入函数调用开销,或者编译器是否在大多数情况下内联它(下面的示例)?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56854333/

相关文章:

c++ - 缓存模拟中的位移位

javascript - 递归地实现 getElementsByClassName

polymorphism - decltype 和 typeid 返回不同的类型

c++ - 如何有效地使用 std::async 对指针数组执行操作

c++ - 如何让 NetBeans 在执行语法检查时使用 C++14?

c++ - QueryPerformanceCounter 抛出不正确的数字

c++ - 直接显示过滤器中的调用约定

c++ - MessageBox 不打印 UNICODE 字符

javascript - NodeJS - 递归函数中的异步结果求和

java - 调试递归方法来查找单词是否存在于滑板中