65.9K
CodeProject 正在变化。 阅读更多。
Home

委托:C++11 与超高速比较 - 快速简易对比

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.83/5 (8投票s)

2013年7月4日

CPOL

4分钟阅读

viewsIcon

40452

C++11 lambda/委托方法与 Sergey Ryazanov 的“超高速委托”的快速比较。

历史

  • 2014年1月31日 - 已修正 Mikhail Semenov 方法的代码。

引言

以下内容适用于那些好奇 Sergey Ryazanov 的 超高速 C++ 委托 与 C++11 lambda/函数指针方法相比如何的人。这并非旨在贬低任何一方,甚至远非全面,这就是标题中包含“快速简易”的原因。

作为背景,我的 Windows API 包装器 集成了 Ryazanov 的委托来实现成员函数回调。我的好奇心战胜了我,我想试用新的 C++11 lambda 函数,看看用它们创建的委托在使用和性能方面与现有方法相比如何。

用法

为了在 DWinLib 3.01 中创建可用的回调,它必须具有以下函数签名:

void DwlWinObjectDerivedClass::someFunction(DwlWinObject * usableObjectPointer) { ... }

只允许一个特定签名的原因与历史因素有关,包括受到 Borland Builder 语法的影响,以及 Builder 4.0 无法编译 Ryazanov 的所有代码,因此这方面是硬编码的。

在使用 lambda 函数的 C++11 中,克服此限制相当容易。但由于这是一个快速简易的比较,因此不会这样做。现有方法将在 Microsoft Visual Studio Express 2012 中重新创建。

正如预期的那样,这两种方法之间存在语义差异。使用相同的函数签名,在现有的 DWinLib 解决方案中,必须像这样创建委托:

DwlDelegate del1(CreateDelegate(Controller, delegate1, this, this));

然后,该委托需要以某种方式存储在包含对象中:

DwlDelegate delegateC = del1;

最后,它作为对某些事件的响应从包含对象中执行:

delegateC();

总的来说还不错。现在让我们看看新的 lambda/回调解决方案。其中,包含类包含一个 std::function 对象而不是 DwlDelegate:

std::function<void (DwlWinObject*)> callbackFuncC;

然后通过调用在包含类中注册函数:

containingClass.setCallback( [&] (DwlWinObject*) { callbackFunc(this); } );

(感谢 Alex Allain 的 C++11 中的 Lambda 函数 - 权威指南 说明了正确的语法。)

在此,包含类像这样定义 'setCallback':

void setCallback(std::function<void (DwlWinObject*)> func) {
   callbackFuncC = func;
   }

此外,为了进行苹果与苹果的比较,我们必须向包含类添加一个 DwlWinObject 指针,并让调用者注册相应的值。请参阅代码了解详细信息。值得注意的是,在发布代码的此阶段,我们并没有直接进行苹果与苹果的比较,因为编译器可能会针对 lambda 方法优化后续对 dwlWinObject 的调用。在现实世界中,很少需要 DwlWinObject 指针,在大多数情况下应该可以消除它。

最后,包含类通过以下方式执行回调:

callbackFuncC(ptrC);

结果

在我的电脑上,在发布模式下,lambda/回调方法花费的时间大约是“超高速委托”技术的四倍。虽然就性能而言这并不令人印象深刻,但我将在 DWinLib 的下一个版本中将其作为一个选项添加。

* 2014年1月31日编辑:现在 Visual Studio 2013 Express 可用了,变参模板唾手可得,我能够让 Mikhail Semenov 的方法 工作。在本文的先前版本中对代码进行了猜测,但需要进行一些修改。有趣的是,它比 Ryazanov 的方法快约 35%。但是,在 'setSemenovDelegate' 例程中对模板参数进行硬编码是不可接受的,因为我为了从按钮代码中执行回调而求助于模板成员指针。如果您对以下方法有任何改进,请留下评论。

注释

  • 如前所述,这只是一个快速简易的比较。
  • lambda/回调例程比 DWinLib 中使用的硬编码方法更容易修改可变函数签名,尽管如果需要,Ryazanov 的完整“超高速委托”代码可以与可变签名一起使用。
  • 用于与 Mikhail Semenov 的 C++11 中委托的实现 进行比较的代码已包含在源代码中。如果您使用的编译器不支持变参模板,只需注释掉 #define VARIADIC_TEMPLATES 行。如上所述,我的技巧感觉远非最佳,因此请在评论中提出改进建议。
  • 即使 C++11 方法比超高速委托方法慢约 400%,但最终成本对于我的使用来说仍然是可以接受的。如果将其用于高性能例程的内部循环中,情况就不是这样了。
  • 选择 lambda/委托解决方案的主要原因是使用超高速委托时构造方法的笨拙性。如果没有宏的帮助,现有方法需要记住:create<ClassName, &ClassName::FunctionName>(Instance, WinObjectPtr)。哎呀!虽然 lambda 并没有好多少,但它们仍然有所改进。

代码清单

以下是用于我的测试的完整代码。您可以随意修改它。

#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:
//https://codeproject.org.cn/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;
   }
© . All rights reserved.