使用递归模板函数是否会引入函数调用开销,或者编译器是否大部分时间都内联它(下面的示例)?
Does using recursive template functions introduce a function call overhead or does the compiler inline it most of the times (example below)?
我一直在试图理解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++编译时将整数转换为字符串
- 是否可以在编译时初始化数组,以便在运行时不会花费时间?
- 如何检查两个 std::向量在小于 O(n) 的时间复杂度内是否相等
- 如果返回 -1,时间() 的参数是否被修改?
- 查找数字是否为 2 的幂的时间复杂度
- 是否有可能实现O(N)时间和O(1)空间解决方案,以实现C++中的字符串循环移位
- 图问题:找出两个节点是否在每个节点的O(1)时间和O(2)存储中共享同一分支
- BRK(0) 花费的时间是否太多?
- 多次分配内存是否一次性需要更多时间?
- 如何检查 2 个 c++ 数组在 O(1) 或 O(log n) 时间复杂度中是否相同(所有元素都相同,顺序很重要)?
- 即使在多任务处理时,添加用户时间 + 系统时间(来自 shell 的时间命令)是否是一种可靠的措施?
- std::regex 是否保证了最坏情况下的时间复杂度?
- SVM 训练时间是否取决于输入数据的内容?
- std::unordered_set 是否对任何对象进行摊销常量查找时间,而不考虑谓词
- 使用 OpenMP 并行执行比串行执行 c++ 花费更长的时间,我计算执行时间是否正确?
- C 中是否有一个无法更改的时间戳?(Internet独立)
- 当字节在文件上写入0时,是否可以保持写作时间稳定
- 使用递归模板函数是否会引入函数调用开销,或者编译器是否大部分时间都内联它(下面的示例)?
- 通过引用传递基元类型(如 int、bool)是否比按值传递有任何加速?还是会恶化时间/空间的使用
- switch 语句结束和下一条语句之间的时间是否具有确定性(与路径无关)
- CLOCKS_PER_SEC是否意味着每台计算机都会有不同的时间概念