任何简单快速的回拨机制?

时间:2013-12-29 17:33:44

标签: c++ c++11 bind

我正在为速度敏感的应用程序实现事件驱动的消息处理逻辑。我有各种各样的业务逻辑,包含在很多Reactor类中:

class TwitterSentimentReactor{
    on_new_post(PostEvent&);
    on_new_comment(CommentEvent&);
};

class FacebookSentimentReactor{
    on_new_post(PostEvent&);
    on_new_comment(CommentEvent&);
};

class YoutubeSentimentReactor{
    on_new_post(PostEvent&);
    on_new_comment(CommentEvent&);
    on_new_plus_one(PlusOneEvent&);
};

让我们说,有8种这样的事件类型,每个Reactor都会响应它们的一部分。

核心程序的消息有8个“入口点”,它与一些低级套接字处理库挂钩,例如

on_new_post(PostEvent& pe){
    youtube_sentiment_reactor_instance->on_new_post(pe);
    twitter_sentiment_reactor_instance->on_new_post(pe);
    youtube_sentiment_reactor_instance->on_new_post(pe);
}

我正在考虑使用std::functionstd::bind来构建std::vector<std::function<>>,然后我遍历向量来调用每个回调函数。

然而,当我尝试时,std::function证明不够快 。这里有一个快速而简单的解决方案吗?正如我前面提到的,这是非常速度敏感的,所以我想避免使用虚函数和继承,来削减v-table查找

欢迎评论。感谢

6 个答案:

答案 0 :(得分:2)

我认为在你的情况下,更容易做一个接口,因为你知道要调用与预期参数完全匹配的简单成员函数:

struct IReactor {
    virtual void on_new_post(PostEvent&) =0;
    virtual void on_new_comment(CommentEvent&) =0;
    virtual void on_new_plus_one(PlusOneEvent&) =0;
};

然后让每个类继承并实现此接口。

您可以使用简单的std::vector<IReactor*>来管理回调。

请记住,在C ++中,接口只是普通的类,所以你甚至可以为部分或全部函数编写默认实现:

struct IReactor {
    virtual void on_new_post(PostEvent&) {}
    virtual void on_new_comment(CommentEvent&) {}
    virtual void on_new_plus_one(PlusOneEvent&) {}
};

答案 1 :(得分:2)

std::function主要性能问题是,无论何时需要存储一些上下文(例如绑定参数或lambda的状态),都需要内存,这通常会转换为内存分配。此外,存在的当前库实现可能尚未进行优化以避免此内存分配。

话虽如此:

  • 太慢了?您必须自己衡量,在您的上下文中
  • 有替代品吗?是的,很多!

作为一个例子,你有什么不使用基类Reactor,它具有所有必需的回调定义(默认情况下什么都不做),然后从它派生来实现所需的行为?然后,您可以轻松地使用std::vector<std::unique_ptr<Reactor>>进行迭代!

此外,根据反应堆是否需要状态(或不反应),您可以通过避免从那时分配对象而仅使用函数来获得很多收益。

实际上,这取决于项目的具体限制。

答案 2 :(得分:1)

如果您需要快速代表和事件系统,请查看Offirmo: 它与“Fastest possible delegates”一样快,但它有两个主要优点:

1)它已经准备好且经过良好测试的库(不需要从文章中编写自己的库)

2)不依赖于编译器黑客(完全符合C ++标准)

https://github.com/Offirmo/impossibly-fast-delegates

如果您需要托管信号/插槽系统,我已经开发了my own(c++11 only)

它不像Offirmo那么快,但对于任何真实场景都足够快,最重要的是比Qt或Boost信号快一个数量级并且使用简单。

  • 信号负责发射事件。
  • 老虎机负责保留回调。
  • 根据需要将多少个插槽连接到信号。
  • 不要担心终生(一切自动连接)

效果考虑因素:

std :: function的开销非常低(并且随着每个编译器的发布而改进)。实际上只比常规函数调用慢一点。我自己的信号/插槽库,能够在2Ghz处理器上实现2.5亿(我测量纯粹的开销)回调/秒,并使用std :: function。

由于您的代码与网络内容有关,因此您应该注意主要瓶颈是套接字。

第二个瓶颈是指令缓存的延迟。如果您使用Offirmo(少数汇编指令)或std :: function则无关紧要。大部分时间都来自L1缓存的fetchin指令。最好的优化是保持所有回调代码编译在同一个翻译单元(相同的.cpp文件)中,并且可能以相同的顺序调用回调(或者大多数是相同的顺序),在你这样做之后你将只看到一个使用Offirmo(严重的是,你不能比Offirmo快)对std :: function的改进很小。

请记住,任何执行某些功能的函数至少会有几十条指令(特别是在处理套接字时:你必须等待完成系统调用和处理器上下文切换..)所以回调的开销系统将是可以忽略不计的。

答案 3 :(得分:0)

我不能评论你正在使用的方法的实际速度,除了说:

  1. Premature optimization does not usually give you what you expect
  2. 您应该在开始切片和切块之前测量性能贡献。如果您知道它之前无法正常工作,那么您现在可以搜索更好的内容或者现在进行“次优”但是将其封装以便可以替换它。
  3. 如果您正在寻找不使用std :: function(但使用虚拟方法)的一般事件系统,您可以试试这个:

    Notifier.h

    /* 
     The Notifier is a singleton implementation of the Subject/Observer design
     pattern.  Any class/instance which wishes to participate as an observer
     of an event can derive from the Notified base class and register itself
     with the Notiifer for enumerated events.
    
     Notifier derived classes implement variants of the Notify function:
    
     bool Notify(const NOTIFIED_EVENT_TYPE_T& event, variants ....)
    
     There are many variants possible.  Register for the message 
     and create the interface to receive the data you expect from 
     it (for type safety).  
    
     All the variants return true if they process the event, and false
     if they do not.  Returning false will be considered an exception/
     assertion condition in debug builds.
    
     Classes derived from Notified do not need to deregister (though it may 
     be a good idea to do so) as the base class destrctor will attempt to
     remove itself from the Notifier system automatically.
    
     The event type is an enumeration and not a string as it is in many 
     "generic" notification systems.  In practical use, this is for a closed
     application where the messages will be known at compile time.  This allows
     us to increase the speed of the delivery by NOT having a 
     dictionary keyed lookup mechanism.  Some loss of generality is implied 
     by this.
    
     This class/system is NOT thread safe, but could be made so with some
     mutex wrappers.  It is safe to call Attach/Detach as a consequence 
     of calling Notify(...).  
    
     */
    
    
    /* This is the base class for anything that can receive notifications.
     */
    
    typedef enum
    {
       NE_MIN = 0,
       NE_SETTINGS_CHANGED,
       NE_UPDATE_COUNTDOWN,
       NE_UDPATE_MESSAGE,
       NE_RESTORE_FROM_BACKGROUND,
       NE_MAX,
    } NOTIFIED_EVENT_TYPE_T;
    
    class Notified
    {
    public:
       virtual bool Notify(NOTIFIED_EVENT_TYPE_T eventType, const uint32& value)
       { return false; };
       virtual bool Notify(NOTIFIED_EVENT_TYPE_T eventType, const bool& value)
       { return false; };
       virtual bool Notify(NOTIFIED_EVENT_TYPE_T eventType, const string& value)
       { return false; };
       virtual bool Notify(NOTIFIED_EVENT_TYPE_T eventType, const double& value)
       { return false; };
       virtual ~Notified();   
    };
    
    class Notifier : public SingletonDynamic<Notifier>
    {
    public:
    
    private:
       typedef vector<NOTIFIED_EVENT_TYPE_T> NOTIFIED_EVENT_TYPE_VECTOR_T;
    
       typedef map<Notified*,NOTIFIED_EVENT_TYPE_VECTOR_T> NOTIFIED_MAP_T;
       typedef map<Notified*,NOTIFIED_EVENT_TYPE_VECTOR_T>::iterator NOTIFIED_MAP_ITER_T;
    
       typedef vector<Notified*> NOTIFIED_VECTOR_T;
       typedef vector<NOTIFIED_VECTOR_T> NOTIFIED_VECTOR_VECTOR_T;
    
       NOTIFIED_MAP_T _notifiedMap;
       NOTIFIED_VECTOR_VECTOR_T _notifiedVector;
       NOTIFIED_MAP_ITER_T _mapIter;
    
    
       // This vector keeps a temporary list of observers that have completely
       // detached since the current "Notify(...)" operation began.  This is
       // to handle the problem where a Notified instance has called Detach(...)
       // because of a Notify(...) call.  The removed instance could be a dead
       // pointer, so don't try to talk to it.
       vector<Notified*> _detached;
       int32 _notifyDepth;
    
       void RemoveEvent(NOTIFIED_EVENT_TYPE_VECTOR_T& orgEventTypes, NOTIFIED_EVENT_TYPE_T eventType);
       void RemoveNotified(NOTIFIED_VECTOR_T& orgNotified, Notified* observer);
    
    public:
    
       virtual void Reset();
       virtual bool Init() { Reset(); return true; }
       virtual void Shutdown() { Reset(); }
    
       void Attach(Notified* observer, NOTIFIED_EVENT_TYPE_T eventType);
       // Detach for a specific event
       void Detach(Notified* observer, NOTIFIED_EVENT_TYPE_T eventType);
       // Detach for ALL events
       void Detach(Notified* observer);
    
       // This template function (defined in the header file) allows you to
       // add interfaces to Notified easily and call them as needed.  Variants
       // will be generated at compile time by this template.
       template <typename T>
       bool Notify(NOTIFIED_EVENT_TYPE_T eventType, const T& value)
       {
          if(eventType < NE_MIN || eventType >= NE_MAX)
          {
             throw std::out_of_range("eventType out of range");
          }
    
          // Keep a copy of the list.  If it changes while iterating over it because of a
          // deletion, we may miss an object to update.  Instead, we keep track of Detach(...)
          // calls during the Notify(...) cycle and ignore anything detached because it may
          // have been deleted.
          NOTIFIED_VECTOR_T notified = _notifiedVector[eventType];
    
          // If a call to Notify leads to a call to Notify, we need to keep track of
          // the depth so that we can clear the detached list when we get to the end
          // of the chain of Notify calls.
          _notifyDepth++;
    
          // Loop over all the observers for this event.
          // NOTE that the the size of the notified vector may change if
          // a call to Notify(...) adds/removes observers.  This should not be a
          // problem because the list is a simple vector.
          bool result = true;
          for(int idx = 0; idx < notified.size(); idx++)
          {
             Notified* observer = notified[idx];
             if(_detached.size() > 0)
             {  // Instead of doing the search for all cases, let's try to speed it up a little
                // by only doing the search if more than one observer dropped off during the call.
                // This may be overkill or unnecessary optimization.
                switch(_detached.size())
                {
                   case 0:
                      break;
                   case 1:
                      if(_detached[0] == observer)
                         continue;
                      break;
                   default:
                      if(std::find(_detached.begin(), _detached.end(), observer) != _detached.end())
                         continue;
                      break;
                }
             }
             result = result && observer->Notify(eventType,value);
             assert(result == true);
          }
          // Decrement this each time we exit.
          _notifyDepth--;
          if(_notifyDepth == 0 && _detached.size() > 0)
          {  // We reached the end of the Notify call chain.  Remove the temporary list
             // of anything that detached while we were Notifying.
             _detached.clear();
          }
          assert(_notifyDepth >= 0);
          return result;
       }
       /* Used for CPPUnit.  Could create a Mock...maybe...but this seems
        * like it will get the job done with minimal fuss.  For now.
        */
       // Return all events that this object is registered for.
       vector<NOTIFIED_EVENT_TYPE_T> GetEvents(Notified* observer);
       // Return all objects registered for this event.
       vector<Notified*> GetNotified(NOTIFIED_EVENT_TYPE_T event);
    };
    

    Notifier.cpp

    #include "Notifier.h"
    
    void Notifier::Reset()
    {
       _notifiedMap.clear();
       _notifiedVector.clear();
       _notifiedVector.resize(NE_MAX);
       _detached.clear();
       _notifyDepth = 0;
    }
    
    void Notifier::Attach(Notified* observer, NOTIFIED_EVENT_TYPE_T eventType)
    {
       if(observer == NULL)
       {
          throw std::out_of_range("observer == NULL");
       }
       if(eventType < NE_MIN || eventType >= NE_MAX)
       {
          throw std::out_of_range("eventType out of range");
       }
    
       _mapIter = _notifiedMap.find(observer);
       if(_mapIter == _notifiedMap.end())
       {  // Registering for the first time.
          NOTIFIED_EVENT_TYPE_VECTOR_T eventTypes;
          eventTypes.push_back(eventType);
          // Register it with this observer.
          _notifiedMap[observer] = eventTypes;
          // Register the observer for this type of event.
          _notifiedVector[eventType].push_back(observer);
       }
       else
       {
          NOTIFIED_EVENT_TYPE_VECTOR_T& events = _mapIter->second;
          bool found = false;
          for(int idx = 0; idx < events.size() && !found; idx++)
          {
             if(events[idx] == eventType)
             {
                found = true;
                break;
             }
          }
          if(!found)
          {
             events.push_back(eventType);
             _notifiedVector[eventType].push_back(observer);
          }
       }
    }
    
    void Notifier::RemoveEvent(NOTIFIED_EVENT_TYPE_VECTOR_T& eventTypes, NOTIFIED_EVENT_TYPE_T eventType)
    {
       int foundAt = -1;
    
       for(int idx = 0; idx < eventTypes.size(); idx++)
       {
          if(eventTypes[idx] == eventType)
          {
             foundAt = idx;
             break;
          }
       }
       if(foundAt >= 0)
       {
          eventTypes.erase(eventTypes.begin()+foundAt);
       }
    }
    
    void Notifier::RemoveNotified(NOTIFIED_VECTOR_T& notified, Notified* observer)
    {
       int foundAt = -1;
    
       for(int idx = 0; idx < notified.size(); idx++)
       {
          if(notified[idx] == observer)
          {
             foundAt = idx;
             break;
          }
       }
       if(foundAt >= 0)
       {
          notified.erase(notified.begin()+foundAt);
       }
    }
    
    
    void Notifier::Detach(Notified* observer, NOTIFIED_EVENT_TYPE_T eventType)
    {
       if(observer == NULL)
       {
          throw std::out_of_range("observer == NULL");
       }
       if(eventType < NE_MIN || eventType >= NE_MAX)
       {
          throw std::out_of_range("eventType out of range");
       }
    
       _mapIter = _notifiedMap.find(observer);
       if(_mapIter != _notifiedMap.end())
       {  // Was registered
          // Remove it from the map.
          RemoveEvent(_mapIter->second, eventType);
          // Remove it from the vector
          RemoveNotified(_notifiedVector[eventType], observer);
          // If there are no events left, remove this observer completely.
          if(_mapIter->second.size() == 0)
          {
             _notifiedMap.erase(_mapIter);
             // If this observer was being removed during a chain of operations,
             // cache them temporarily so we know the pointer is "dead".
             _detached.push_back(observer);
          }
       }
    }
    
    void Notifier::Detach(Notified* observer)
    {
       if(observer == NULL)
       {
          throw std::out_of_range("observer == NULL");
       }
    
       _mapIter = _notifiedMap.find(observer);
       if(_mapIter != _notifiedMap.end())
       {
          // These are all the event types this observer was registered for.
          NOTIFIED_EVENT_TYPE_VECTOR_T& eventTypes = _mapIter->second;
          for(int idx = 0; idx < eventTypes.size();idx++)
          {
             NOTIFIED_EVENT_TYPE_T eventType = eventTypes[idx];
             // Remove this observer from the Notified list for this event type.
             RemoveNotified(_notifiedVector[eventType], observer);
          }
          _notifiedMap.erase(_mapIter);
       }
       // If this observer was being removed during a chain of operations,
       // cache them temporarily so we know the pointer is "dead".
       _detached.push_back(observer);
    }
    
    
    Notified::~Notified()
    {
       Notifier::Instance().Detach(this);
    }
    
    // Return all events that this object is registered for.
    vector<NOTIFIED_EVENT_TYPE_T> Notifier::GetEvents(Notified* observer)
    {
       vector<NOTIFIED_EVENT_TYPE_T> result;
    
       _mapIter = _notifiedMap.find(observer);
       if(_mapIter != _notifiedMap.end())
       {
          // These are all the event types this observer was registered for.
          result = _mapIter->second;
       }
    
       return result;
    }
    
    // Return all objects registered for this event.
    vector<Notified*> Notifier::GetNotified(NOTIFIED_EVENT_TYPE_T event)
    {
       return _notifiedVector[event];
    }
    

    备注:

    1. 在使用之前,您必须在类上调用init()。
    2. 您不必将其用作单身人士,也不必使用我在此处使用的单身人士模板。这只是为了获得一个引用/初始化/关闭机制。
    3. 这是来自更大的代码库。您可以在github上找到一些其他示例here

答案 4 :(得分:0)

在SO上有一个主题,其中几乎所有C ++中可用的机制都被枚举,但找不到它。

它有一个这样的列表:

快速代表和boost::function效果比较文章:link

哦,顺便说一下,过早优化......,然后优化,然后优化,80/20规则,等等等等,等等等等等等。

快乐的编码!

答案 5 :(得分:0)

除非您可以静态地参数化处理程序并获得内联,否则std::function<...>是您的最佳选择。当需要擦除类型精确类型或者你需要调用运行时指定的函数时,你将有一个间接,因此,实际的函数调用没有内联的能力。 std::function<...>完成了这一点,你就不会变得更好。