图书馆代码:
class Resource
{
public:
typedef void (*func_sig)(int, char, double, void*);
//Registration
registerCallback(void* app_obj, func_sig func)
{
_app_obj = app_obj;
_func = func;
}
//Calling when the time comes
void call_app_code()
{
_func(231,'a',432.4234,app_obj);
}
//Other useful methods
private:
void* app_obj;
func_sig _func;
//Other members
};
申请代码:
class App
{
public:
void callme(int, char, double);
//other functions, members;
};
void callHelper(int i, char c, double d, void* app_obj)
{
static_cast<App*>(app_obj)->callme(i,c,d);
}
int main()
{
App a;
Resource r;
r.registercallback(&a, callHelper);
//Do something
}
以上是回调机制的最小实现。它更详细,不支持绑定,占位符等,如std :: function。如果我使用std::function
或boost::function
作为上述用例,是否会有任何性能缺陷?这个回调将成为实时应用程序的非常关键的路径。我听说boost :: function使用虚函数来进行实际的调度。如果没有涉及绑定/占位符,是否会优化?
更新
对于那些有兴趣检查最新编译器中的组件的人:https://gcc.godbolt.org/z/-6mQvt
我已经非常想知道自己,所以我开始编写一些非常小的基准测试,试图通过每个函数指针回调版本的循环原子计数器来模拟性能。
请记住,这些是对函数的简单调用,只执行一件事,原子地增加其计数器;
通过检查生成的汇编器输出,您可能会发现,一个裸C函数指针循环被编译成3个CPU指令;
一个C ++ 11的std::function
调用只增加了2个CPU指令,因此在我们的例子中有5个。作为结论:使用函数指针技术的方式绝对无关紧要,开销差异在任何情况下都非常小。
((但令人困惑的是,指定的lambda表达式似乎比其他表达式运行得更快,甚至比C-one还快。))
使用以下代码编译示例:clang++ -o tests/perftest-fncb tests/perftest-fncb.cpp -std=c++11 -pthread -lpthread -lrt -O3 -march=native -mtune=native
#include <functional>
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
typedef unsigned long long counter_t;
struct Counter {
volatile counter_t bare;
volatile counter_t cxx;
volatile counter_t cxo1;
volatile counter_t virt;
volatile counter_t lambda;
Counter() : bare(0), cxx(0), cxo1(0), virt(0), lambda(0) {}
} counter;
void bare(Counter* counter) { __sync_fetch_and_add(&counter->bare, 1); }
void cxx(Counter* counter) { __sync_fetch_and_add(&counter->cxx, 1); }
struct CXO1 {
void cxo1(Counter* counter) { __sync_fetch_and_add(&counter->cxo1, 1); }
virtual void virt(Counter* counter) { __sync_fetch_and_add(&counter->virt, 1); }
} cxo1;
void (*bare_cb)(Counter*) = nullptr;
std::function<void(Counter*)> cxx_cb;
std::function<void(Counter*)> cxo1_cb;
std::function<void(Counter*)> virt_cb;
std::function<void(Counter*)> lambda_cb;
void* bare_main(void* p) { while (true) { bare_cb(&counter); } }
void* cxx_main(void* p) { while (true) { cxx_cb(&counter); } }
void* cxo1_main(void* p) { while (true) { cxo1_cb(&counter); } }
void* virt_main(void* p) { while (true) { virt_cb(&counter); } }
void* lambda_main(void* p) { while (true) { lambda_cb(&counter); } }
int main()
{
pthread_t bare_thread;
pthread_t cxx_thread;
pthread_t cxo1_thread;
pthread_t virt_thread;
pthread_t lambda_thread;
bare_cb = &bare;
cxx_cb = std::bind(&cxx, std::placeholders::_1);
cxo1_cb = std::bind(&CXO1::cxo1, &cxo1, std::placeholders::_1);
virt_cb = std::bind(&CXO1::virt, &cxo1, std::placeholders::_1);
lambda_cb = [](Counter* counter) { __sync_fetch_and_add(&counter->lambda, 1); };
pthread_create(&bare_thread, nullptr, &bare_main, nullptr);
pthread_create(&cxx_thread, nullptr, &cxx_main, nullptr);
pthread_create(&cxo1_thread, nullptr, &cxo1_main, nullptr);
pthread_create(&virt_thread, nullptr, &virt_main, nullptr);
pthread_create(&lambda_thread, nullptr, &lambda_main, nullptr);
for (unsigned long long n = 1; true; ++n) {
sleep(1);
Counter c = counter;
printf(
"%15llu bare function pointer\n"
"%15llu C++11 function object to bare function\n"
"%15llu C++11 function object to object method\n"
"%15llu C++11 function object to object method (virtual)\n"
"%15llu C++11 function object to lambda expression %30llu-th second.\n\n",
c.bare, c.cxx, c.cxo1, c.virt, c.lambda, n
);
}
}
std::function
对函数类型执行类型擦除,并且有多种方法可以实现它,因此您可能应该添加使用哪个版本的编译器来获得确切的答案。
boost::function
在很大程度上与std::function
完全相同,并带有一个FAQ入场电话开销和一些general section on performance。这些提供了关于函数对象如何执行的一些提示。如果这适用于您的情况,取决于您的实施,但数字不应有显着差异。
我使用Google Benchmark运行快速基准测试结果如下:
Run on (4 X 2712 MHz CPU s)
----------------------------------------------------------
Benchmark Time CPU Iterations
----------------------------------------------------------
RawFunctionPointer 11 ns 11 ns 56000000
StdBind 12 ns 12 ns 64000000
StdFunction 11 ns 11 ns 56000000
Lambda 9 ns 9 ns 64000000
似乎最优化的解决方案是使用lambdas(就像这个线程中提到的用户christianparpart一样)。我用于基准测试的代码可以在下面找到。
#include <benchmark/benchmark.h>
#include <cstdlib>
#include <cstdio>
#include <functional>
static volatile int global_var = 0;
void my_int_func(int x)
{
global_var = x + x + 3;
benchmark::DoNotOptimize(global_var);
benchmark::DoNotOptimize(x);
}
static void RawFunctionPointer(benchmark::State &state)
{
void (*bar)(int) = &my_int_func;
srand (time(nullptr));
for (auto _ : state)
{
bar(rand());
benchmark::DoNotOptimize(my_int_func);
benchmark::DoNotOptimize(bar);
}
}
static void StdFunction(benchmark::State &state)
{
std::function<void(int)> bar = my_int_func;
srand (time(nullptr));
for (auto _ : state)
{
bar(rand());
benchmark::DoNotOptimize(my_int_func);
benchmark::DoNotOptimize(bar);
}
}
static void StdBind(benchmark::State &state)
{
auto bar = std::bind(my_int_func, std::placeholders::_1);
srand (time(nullptr));
for (auto _ : state)
{
bar(rand());
benchmark::DoNotOptimize(my_int_func);
benchmark::DoNotOptimize(bar);
}
}
static void Lambda(benchmark::State &state)
{
auto bar = [](int x) {
global_var = x + x + 3;
benchmark::DoNotOptimize(global_var);
benchmark::DoNotOptimize(x);
};
srand (time(nullptr));
for (auto _ : state)
{
bar(rand());
benchmark::DoNotOptimize(my_int_func);
benchmark::DoNotOptimize(bar);
}
}
BENCHMARK(RawFunctionPointer);
BENCHMARK(StdBind);
BENCHMARK(StdFunction);
BENCHMARK(Lambda);
BENCHMARK_MAIN();