使用递归模板函数是否会引入函数调用开销,或者编译器是否大部分时间都内联它(下面的示例)?

Does using recursive template functions introduce a function call overhead or does the compiler inline it most of the times (example below)?

本文关键字:是否 时间 大部分 编译器 函数 递归 或者 开销 函数调用      更新时间:2023-10-16

我一直在试图理解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的串联。

总是显式内联这种严重递归函数是一种好的做法吗?

是否仍然需要像我们多年来constexpr的那样使用 MTP 模板化此类函数?即使constexpr还不够,我们也会在 c++20 中consteval(希望如此)。

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

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

但与往常一样,关于优化: 1)尝试获得最佳算法 2)尝试实现使代码可维护 3) 测量 4) 测量 5) 测量

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

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

您的编译器比您相信的更好!通常!如果您不信任:测量!只有当你看到一个真正的问题时:手工优化。

如果使用constexpr,尤其是对于递归函数,大多数编译器都提供命令行标志,以提供更深入的编译时评估级别,如果您不强制编译器使用结果作为模板参数或任何其他"必须是编译时常量"(如数组的大小)在编译时获取结果。这取决于您使用的编译器,因此请阅读手册!

如果在递归/循环中使用std::cout,您将永远不会看到"单输出优化"。但实际上:如果你有足够的时间使用std::cout你就不必考虑它周围的几行组装。std::cout通常与生成数据的代码有关,应该写入控制台!

不要优化错误的东西!

附加组件: 如果您确实想从整数列表中生成编译时字符串,则可以将其作为示例的基础: C++编译时将整数转换为字符串

相关文章: