Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / VC10.0

Delegates: C++11 vs. Impossibly Fast - A Quick and Dirty Comparison

4.83/5 (8 votes)
31 Jan 2014CPOL4 min read 39.5K  
A quick comparison of the C++11 lambda/delegate approach vs. Sergey Ryazanov's 'Impossibly Fast Delegates.'

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>

//Utility class for the timing...
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; }
   };


//The full DwlWinObject incorporates more, but this is just for testing...
class DwlWinObject {
   };


//The following Delegate is derived from Sergey Ryazanov's CodeProject article:
//http://www.codeproject.com/cpp/ImpossiblyFastCppDelegate.asp
//
//First, a useful define for simplifying the call to DwlDelegate::create:
#define CreateDelegate(ClassName, FunctionName, Instance, WinObjectPtr) \
   DwlDelegate::create<ClassName, &ClassName::FunctionName>(Instance, WinObjectPtr)

//Example usage:
//          CreateDelegate(WinMainO, newWindow, gWinMain, parentC);
//This replaces the more awkward:
//          DwlDelegate::create<WinMainO, &WinMainO::newWindow>(gWinMain, parentC));

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
   //Mikhail Semenov's approach:
   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; //Forward declaration for testing Mikhail Semenov's approach


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;

         //First, test the new lambda function delegate approach:
         for (int i = 0; i<numIterations; ++i) {
            //The reason for sending 'this' is simply to try comparing apples to apples.
            //We don't need it for anything, but the method in DWinLib incorporated a
            //DwlWinObject * as a type of user variable to do with as wished, so it is
            //incorporated here to be comparable even if not used.
            buttonC.setCallback1([&](DwlWinObject*) { callbackFunc1(this); });
            //if the callback function was simply a standalone function, the call would be:
            //"buttonC.setCallback(func);"
            //Doing it this way, we must also set the DwlWinObject pointer manually:
            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
            //Now do the delegates as in Mikhail Semenov's "Implementation of Delegates in
            //C++11" article:
            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);
               //d1(NULL); //<-- You can use the delegate directly at this point if you wish
               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; //Total junk way to do this, but good enough for testing
         numIterations++;  //Just to put a breakpoint on for non-cin approach
         }
   };


void main() {
   Controller control;
   }

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)