History
- Jan 31, 2014 - Corrected code for Mikhail Semenov's approach.
Introduction
The following is for you who are curious about how Sergey Ryazanov's Impossibly Fast C++ Delegates compares to a C++11 lambda/function pointer approach. This is not meant to put one down, nor is it even close to comprehensive, which is why 'quick and dirty' is included in the title.
As a background my Windows API wrapper incorporated Ryazanov's delegates to implement the member function callbacks. My curiosity got the better of me, and I wanted to play around with the new C++11 lambda functions and see how delegates created with them compared to the existing approach in both usage and performance.
Usage
In order to create a usable callback in DWinLib 3.01 it had to have a function signature of:
void DwlWinObjectDerivedClass::someFunction(DwlWinObject * usableObjectPointer) { ... }
The reason for only allowing one specific signature had to do with historical items including being influenced by Borland Builder's syntax, and the fact that Builder 4.0 couldn't compile all of Ryazanov's code, so this aspect was hard coded.
In C++11 using lamdba functions, it is fairly easy to overcome this limitation. But because this is a quick and dirty comparison, such won't be done. The existing method will be recreated in Microsoft Visual Studio Express 2012.
As expected, semantic differences exist between the two methods. Using the same function signature for both approaches, in the existing DWinLib solution a delegate had to be created like so:
DwlDelegate del1(CreateDelegate(Controller, delegate1, this, this));
Then that delegate needed to be somehow stored in the containing object:
DwlDelegate delegateC = del1;
And finally it was executed from within the containing object as a response to some event:
delegateC();
Not too bad, all things considered. Now let's look at the new lambda/callback solution. In it the containing class contains a std::function object rather than a DwlDelegate:
std::function<void (DwlWinObject*)> callbackFuncC;
Then the function is registered in the containing class by calling:
containingClass.setCallback( [&] (DwlWinObject*) { callbackFunc(this); } );
(Thanks go to Alex Allain's Lambda Functions in C++11 - the Definitive Guide for illuminating the correct syntax.)
In this, the containing class defines 'setCallback' like so:
void setCallback(std::function<void (DwlWinObject*)> func) {
callbackFuncC = func;
}
Also, in order to compare apples to apples, we must add a DwlWinObject pointer to the containing class and have the caller register the appropriate value. See the code for the details. It is worth noting that we are not directly comparing apples to apples at this point in the release code because the subsequent calls to dwlWinObject
are probably optimized out by the compiler for the lambda approach. In the real world the DwlWinObject pointer is seldom needed, and it should be possible to eliminate this in most cases.
And finally, the containing class executes the callback via:
callbackFuncC(ptrC);
Results
On my computer, the lambda/callback approach took about four times as long as the 'Impossibly Fast Delegates' technique in release mode. Even though this not incredibly impressive as far as performance, I will add it as an option in the next iteration of DWinLib.
* Jan 31, 2014 edit: Now that Visual Studio 2013 Express is available, access to variadic templates is within reach and I was able to get Mikhail Semenov's approach working. A guess at the code was in the previous version of this article, but it required some modification. Interestingly, it is about 35% faster than Ryazanov's approach. But the hard coding of the template parameters in the 'setSemenovDelegate' routine is unacceptable, as it the template member pointer I resorted to in order to execute the callback from within the button code. If you have an improvement to the following method, please leave a comment.
Notes
- As stated earlier, this is simply a quick and dirty comparison.
- The lambda/callback routine is easier to modify variable function signatures than the hard-coded approach used in DWinLib, although Ryazanov's full 'Impossibly Fast Delegates' code can be used with variable signatures if desired.
- The code to compare with Mikhail Semenov's Implementation of Delegates in C++11 has been included in the source. If you are using a compiler without variadic template support, simply comment out the
#define VARIADIC_TEMPLATES
line. As noted above, my hacks feel far from optimum, so leave an improvement in the comments. - Even though the C++11 method is about 400% slower than the Impossibly Fast Delegate one, the end cost is still acceptable for my usage. If this was used in an inner loop of a high performance routine, such wouldn't be the case.
- The main reason for going with the lambda/delegate solution is the awkwardness of the constructing method when using the Impossibly Fast Delegates. Without macro help, the existing method requires remembering:
create<ClassName, &ClassName::FunctionName>(Instance, WinObjectPtr)
. Ungh! Although lambdas aren't much better, they are still an improvement.
The Code Listing
The following is the complete code used for my test. Feel free to modify it however you wish.
#include <functional>
#include <ctime>
#include <iostream>
class Timer {
private:
std::clock_t begC;
std::clock_t avgTotC;
std::clock_t diffC;
int numTimesC;
public:
Timer() : avgTotC(0), numTimesC(0) { }
void start() { begC = std::clock(); }
std::clock_t stop() {
diffC = std::clock() - begC;
avgTotC = avgTotC + diffC;
numTimesC++;
return diffC;
}
std::clock_t getAvg() {
if (numTimesC == 0) return 0;
return avgTotC / numTimesC;
}
void reset() {
numTimesC = 0;
avgTotC = 0;
}
std::clock_t getLapTime() { return std::clock() - begC; }
};
class DwlWinObject {
};
#define CreateDelegate(ClassName, FunctionName, Instance, WinObjectPtr) \
DwlDelegate::create<ClassName, &ClassName::FunctionName>(Instance, WinObjectPtr)
class DwlDelegate {
private:
typedef void(*stub_type)(void* object_ptr, DwlWinObject * arg);
stub_type stub_ptrC;
void * object_ptrC;
DwlWinObject * argC;
template <class T, void (T::*TMethod)(DwlWinObject*)>
static void method_stub(void* object_ptr, DwlWinObject * a1) {
T* p = static_cast<T*>(object_ptr);
return (p->*TMethod)(a1);
}
public:
DwlDelegate() : object_ptrC(0), argC(0), stub_ptrC(0) { }
template <class T, void (T::*Method)(DwlWinObject*)>
static DwlDelegate create(T* object_ptr, DwlWinObject * arg) {
DwlDelegate d;
d.stub_ptrC = &method_stub<T, Method>;
d.object_ptrC = object_ptr;
d.argC = arg;
return d;
}
void operator()() const {
return (*stub_ptrC)(object_ptrC, argC);
}
void object(DwlWinObject * p) { argC = p; }
};
#define VARIADIC_TEMPLATES
#ifdef VARIADIC_TEMPLATES
template <class F>
F* create_delegate(F* f) {
return f;
}
#define _MEM_DELEGATES(_Q,_NAME)\
template <class T, class R, class ... P>\
struct _mem_delegate ## _NAME {\
T* classC;\
R (T::*functionC)(P ...) _Q;\
_mem_delegate ## _NAME(T* t, R (T::*f)(P ...) _Q):classC(t),functionC(f) {}\
R operator()(P ... p) _Q {\
return (classC->*functionC)(std::forward<P>(p) ...);\
}\
};\
\
template <class T, class R, class ... P>\
_mem_delegate ## _NAME<T,R,P ...> create_delegate(T* t, R (T::*f)(P ...) _Q) {\
_mem_delegate ##_NAME<T,R,P ...> d(t,f);\
return d;\
}
_MEM_DELEGATES(,Z)
_MEM_DELEGATES(const,X)
_MEM_DELEGATES(volatile,Y)
_MEM_DELEGATES(const volatile,W)
#endif
class Controller;
class DwlButton : public DwlWinObject {
private:
DwlWinObject * ptrC;
std::function<void(DwlWinObject*)> callbackFuncC;
_mem_delegateZ<Controller, void, DwlWinObject *> * aC;
DwlDelegate buttonDelegateC;
public:
DwlButton() : aC(NULL) { }
void dwlWinObject(DwlWinObject * p) { ptrC = p; }
void setCallback1(std::function<void(DwlWinObject*)> func) {
callbackFuncC = func;
}
void doCallback1() {
callbackFuncC(ptrC);
}
void setDelegate(DwlDelegate del) {
buttonDelegateC = del;
}
void doDelegate() {
buttonDelegateC();
}
void setSemenovDelegate(_mem_delegateZ<Controller, void, DwlWinObject *> * a) {
aC = a;
}
void doSemenovDelegate() {
(*aC)(ptrC);
}
};
class Controller : public DwlWinObject {
private:
DwlButton buttonC;
int yC;
public:
void delegate1(DwlWinObject * obj) {
yC++;
}
void delegate2(DwlWinObject * obj) {
yC++;
}
void delegate3(DwlWinObject * obj) {
yC++;
}
void callbackFunc1(DwlWinObject * obj) {
yC++;
}
void callbackFunc2(DwlWinObject * obj) {
yC++;
}
void callbackFunc3(DwlWinObject * obj) {
yC++;
}
auto del1(DwlWinObject * obj)->void {
yC++;
}
auto del2(DwlWinObject * obj)->void {
yC++;
}
auto del3(DwlWinObject * obj)->void {
yC++;
}
Controller() : yC(0) {
Timer timerC;
timerC.start();
int numIterations = 100000000;
for (int i = 0; i<numIterations; ++i) {
buttonC.setCallback1([&](DwlWinObject*) { callbackFunc1(this); });
buttonC.dwlWinObject(this);
buttonC.doCallback1();
buttonC.setCallback1([&](DwlWinObject*) { callbackFunc2(this); });
buttonC.dwlWinObject(this);
buttonC.doCallback1();
buttonC.setCallback1([&](DwlWinObject*) { callbackFunc3(this); });
buttonC.dwlWinObject(this);
buttonC.doCallback1();
}
time_t time = timerC.stop();
std::cout << "Lambda approach:" << std::endl;
std::cout << "time: " << time << " y: " << yC << std::endl;
#ifdef VARIADIC_TEMPLATES
auto d1 = create_delegate(this, &Controller::del1);
auto d2 = create_delegate(this, &Controller::del2);
auto d3 = create_delegate(this, &Controller::del3);
timerC.reset();
timerC.start();
for (int i=0; i<numIterations; ++i) {
buttonC.dwlWinObject(this);
buttonC.setSemenovDelegate(&d1);
buttonC.doSemenovDelegate();
buttonC.dwlWinObject(this);
buttonC.setSemenovDelegate(&d2);
buttonC.doSemenovDelegate();
buttonC.dwlWinObject(this);
buttonC.setSemenovDelegate(&d3);
buttonC.doSemenovDelegate();
}
time = timerC.stop();
std::cout << "Semenov's implementation:" << std::endl;
std::cout << "time: " << time << " y: " << yC << std::endl;
#endif
timerC.reset();
timerC.start();
DwlDelegate del1(CreateDelegate(Controller, delegate1, this, this));
DwlDelegate del2(CreateDelegate(Controller, delegate2, this, this));
DwlDelegate del3(CreateDelegate(Controller, delegate3, this, this));
for (int i = 0; i<numIterations; ++i) {
buttonC.setDelegate(del1);
buttonC.doDelegate();
buttonC.setDelegate(del2);
buttonC.doDelegate();
buttonC.setDelegate(del3);
buttonC.doDelegate();
}
time = timerC.stop();
std::cout << "Impossibly Fast" << std::endl;
std::cout << "time: " << time << " y: " << yC << std::endl;
std::cout << "Press a key and 'Enter' to quit ";
std::cin >> time;
numIterations++;
}
};
void main() {
Controller control;
}