与使用模板函数相比,使用 `function` 对象的代码执行速度
Execution speed of code with `function` object as compared to using template functions
我知道 std::function
是用 类型擦除 习语实现的。类型擦除是一种方便的技术,但缺点是它需要在堆上存储底层对象的寄存器(某种数组)。
因此,当创建或复制一个 function
对象时,需要进行分配,因此该过程应该比简单地将函数作为模板类型进行操作要慢。
为了验证这个假设,我有一个测试函数 运行,它累加 n = cycles
个连续整数,然后将总和除以增量数 n
。
首先编码为模板:
#include <iostream>
#include <functional>
#include <chrono>
using std::cout;
using std::function;
using std::chrono::system_clock;
using std::chrono::duration_cast;
using std::chrono::milliseconds;
double computeMean(const double start, const int cycles) {
double tmp(start);
for (int i = 0; i < cycles; ++i) {
tmp += i;
}
return tmp / cycles;
}
template<class T>
double operate(const double a, const int b, T myFunc) {
return myFunc(a, b);
}
和 main.cpp
:
int main()
{
double init(1), result;
int increments(1E9);
// start clock
system_clock::time_point t1 = system_clock::now();
result = operate(init, increments, computeMean);
// stop clock
system_clock::time_point t2 = system_clock::now();
cout << "Input: " << init << ", " << increments << ", Output: " << result << '\n';
cout << "Time elapsed: " << duration_cast<milliseconds>(t2 - t1).count() << " ms\n";
return 0;
}
这是 运行 一百次,得到的平均结果是 10024.9 ms
。
然后我在main
中引入function
对象,加上operate
的模板特化,这样上面的代码就可以循环利用了:
\ as above, just add the template specialization
template<>
double operate(const double a, const int b, function<double (const double, const int)> myFunc) {
cout << "nontemplate called\n";
return myFunc(a, b);
}
\ and inside the main
int main()
{
//...
// start clock
system_clock::time_point t1 = system_clock::now();
// new lines
function<double (const double, const int)> computeMean =
[](const double init, const int increments) {
double tmp(init);
for (int i = 0; i < increments; ++i) {
tmp += i;
}
return tmp / increments;
};
// rest as before
// ...
}
我预计 function
版本会更快,但平均值大致相同,实际上甚至更慢,result = 9820.3 ms
。
检查标准偏差,它们大致相同,1233.77
对 1234.96
。
这有什么意义?我原以为带有 function
对象的第二个版本比模板版本慢。
Here整个测试可以在GDB上运行。
I know that std::function
is implemented with the type erasure idiom. Type erasure is a handy technique, but as a drawback it needs to store on the heap a register (some kind of array) of the underlying objects.
类型擦除不一定需要堆分配。在这种情况下,std::function
的实现很可能不必进行任何堆分配,因为 lambda 不捕获任何变量。因此,std::function
只需存储函数指针,它将在对象本身中执行,而不是在 heap-allocated 内存中。
除此之外,即使 std::function
确实进行了堆分配,一些编译器甚至可能 .
最后但同样重要的是,虽然堆分配比堆栈分配更昂贵,如果您只需要在整个程序持续时间内在堆上分配一次,您可能不会注意到时间上的任何差异到该分配。
我知道 std::function
是用 类型擦除 习语实现的。类型擦除是一种方便的技术,但缺点是它需要在堆上存储底层对象的寄存器(某种数组)。
因此,当创建或复制一个 function
对象时,需要进行分配,因此该过程应该比简单地将函数作为模板类型进行操作要慢。
为了验证这个假设,我有一个测试函数 运行,它累加 n = cycles
个连续整数,然后将总和除以增量数 n
。
首先编码为模板:
#include <iostream>
#include <functional>
#include <chrono>
using std::cout;
using std::function;
using std::chrono::system_clock;
using std::chrono::duration_cast;
using std::chrono::milliseconds;
double computeMean(const double start, const int cycles) {
double tmp(start);
for (int i = 0; i < cycles; ++i) {
tmp += i;
}
return tmp / cycles;
}
template<class T>
double operate(const double a, const int b, T myFunc) {
return myFunc(a, b);
}
和 main.cpp
:
int main()
{
double init(1), result;
int increments(1E9);
// start clock
system_clock::time_point t1 = system_clock::now();
result = operate(init, increments, computeMean);
// stop clock
system_clock::time_point t2 = system_clock::now();
cout << "Input: " << init << ", " << increments << ", Output: " << result << '\n';
cout << "Time elapsed: " << duration_cast<milliseconds>(t2 - t1).count() << " ms\n";
return 0;
}
这是 运行 一百次,得到的平均结果是 10024.9 ms
。
然后我在main
中引入function
对象,加上operate
的模板特化,这样上面的代码就可以循环利用了:
\ as above, just add the template specialization
template<>
double operate(const double a, const int b, function<double (const double, const int)> myFunc) {
cout << "nontemplate called\n";
return myFunc(a, b);
}
\ and inside the main
int main()
{
//...
// start clock
system_clock::time_point t1 = system_clock::now();
// new lines
function<double (const double, const int)> computeMean =
[](const double init, const int increments) {
double tmp(init);
for (int i = 0; i < increments; ++i) {
tmp += i;
}
return tmp / increments;
};
// rest as before
// ...
}
我预计 function
版本会更快,但平均值大致相同,实际上甚至更慢,result = 9820.3 ms
。
检查标准偏差,它们大致相同,1233.77
对 1234.96
。
这有什么意义?我原以为带有 function
对象的第二个版本比模板版本慢。
Here整个测试可以在GDB上运行。
I know that
std::function
is implemented with the type erasure idiom. Type erasure is a handy technique, but as a drawback it needs to store on the heap a register (some kind of array) of the underlying objects.
类型擦除不一定需要堆分配。在这种情况下,std::function
的实现很可能不必进行任何堆分配,因为 lambda 不捕获任何变量。因此,std::function
只需存储函数指针,它将在对象本身中执行,而不是在 heap-allocated 内存中。
除此之外,即使 std::function
确实进行了堆分配,一些编译器甚至可能
最后但同样重要的是,虽然堆分配比堆栈分配更昂贵,如果您只需要在整个程序持续时间内在堆上分配一次,您可能不会注意到时间上的任何差异到该分配。