提升ASIO IO_SERVICE实施?

问题描述 投票:8回答:3

我正在编写一个异步日志记录框架,我有多个线程转储数据。我开始玩Boost asio,因为它提供了一些简单的方法来强制执行序列化和排序。由于我是初学者,我开始使用线程安全(使用boost::mutexboost:condition_variable)圆形bounded_buffer(实际上是矢量)进行设计。

我写了一个简单的小基准来测量性能。基准测试只是记录一百万条消息的单个线程(将其推入缓冲区),我的工作线程只是从队列中获取消息以记录到文件/控制台/记录器列表。 (P.S.互斥和C.V的使用是正确的,并且指向消息的指针正在被移动,因此从这个角度看,一切都很好/有效)。

当我改变我的实现而不是使用boost::asio::io_service并且让单个线程执行run()时,性能确实提高了(实际上它在增加记录的消息数量方面非常好,而不是在初始简单模型中降低性能)

以下是我想澄清的几个问题。

  1. 为什么性能提升? (我认为boost::asio::io_service内部实现具有处理程序的线程安全队列,这使得它比我自己的初始简单线程安全队列设计更有效)。请注意我的设计经过了很好的审核,并且没有任何缺陷(骨架代码基于经过验证的示例),有人可以更多地了解io_service如何实现这一点的内部细节。
  2. 第二个有趣的观察是,在增加线程时,我的初始实现性能得到了改善,但是以丢失序列化/排序为代价,但是使用boost :: asio降低了性能(非常轻微)(我认为这是因为我的处理程序执行的任务非常简单)并且上下文切换开销正在降低,我将尝试提出更复杂的任务并在稍后发布我的观察结果)。
  3. 我真的想知道boost::asio是否仅适用于i / o和网络操作,或者我使用它来通过线程池执行并发任务(并行)是一种很好的设计方法。 io_service对象是否只是用于i / o对象(如文档中所述),但我发现这是一种非常有趣的方法,可以帮助我以序列化方式解决并发任务(不仅仅是i / o或网络相关)(有时强制执行)使用股线订购)。我是新手,并且非常好奇为什么基本模型没有像我使用boost asio那样执行/扩展。

结果:(我只有1个工作线程)

  • 1000任务:两种情况下都是10微秒/任务
  • 10000任务:80微秒(有界缓冲),10微秒增强asio
  • 100000任务:250微秒(bounde缓冲区),10微秒增强asio

知道boost如何解决io_service线程安全队列中的处理程序中的线程安全问题会很有趣(我一直认为在某个实现级别它们也必须使用锁和c.v)。

multithreading boost thread-safety boost-asio threadpool
3个回答
4
投票

我担心我对(1)无能为力,但对于其他两个问题:

(2)我发现boost::asio体系结构中存在一些非确定性的开销,即进入(或发送到IO服务对象)的数据之间的延迟可能会从几乎即时响应变化到达到几百毫秒。我试图量化这个作为我试图解决的另一个问题的一部分,关于日志和时间戳RS232数据,但没有得到任何结论性的结果或方法来稳定延迟。我不会惊讶地发现上下文切换组件存在类似的问题。

(3)至于将boost::asio用于异步I / O以外的任务,它现在是我的大多数异步操作的标准工具。我一直使用boost::asio计时器进行异步处理,并为其他任务生成超时。将多个工作线程添加到池中的能力意味着您可以很好地扩展解决方案以用于其他异步高负载任务。我在去年编写的最简单,最喜欢的类是boost::asio IO服务的小工作线程类(道歉,如果有任何错别字,这是从内存转录而不是剪切和粘贴):

class AsioWorker
{
public:
  AsioWorker(boost::asio::io_service * service):
  m_ioService(service), m_terminate(false), m_serviceThread(NULL)
  {
    m_serviceThread = new boost::thread( boost::bind( &AsioWorker::Run, this ) )
  }
  void Run( void )
  {
    while(!m_terminate)
      m_ioService->poll_one();
      mySleep(5); // My own macro for cross-platform millisecond sleep
  }
  ~AsioWorker( void )
  {
    m_terminate = true;
    m_serviceThread->join();
  }
private:
  bool m_terminate;
  boost::asio::io_service *m_ioService;
  boost::thread *m_serviceThread;
}

这个类是一个很棒的小玩具,只需要根据需要添加new,并且当你完成它们时加上一些delete。将std::vector<AsioWorker*> m_workerPool粘贴到使用boost::asio的设备类中,您可以进一步包装线程池管理的东西。我总是试图根据时间来编写一个智能池自动管理器来适当增长线程池,但我还没有一个必要的项目。

在满足您对线程安全的好奇心方面,有可能深入了解提升的内容,以确切了解他们如何做他们正在做的事情。就个人而言,我总是把大部分提升的东西都用在面值上,并且根据过去的经验假设它在引擎盖下非常优化。


0
投票

我还发现boost::asio是一般多核处理引擎的优秀基础设施。我通过大量同步来测量它在细粒度任务上的性能,并发现它优于我使用C ++ 11线程和条件变量编写的“经典”实现。

它的表现也优于TBB,但没有那么多。我挖出他们的代码试图找到“秘密”。我能看到的唯一想法是他们的队列是经典链表,而不是stl容器。

尽管如此,我不确定asio在像Xeon Phi这样的大规模线程架构上的扩展程度。似乎缺少的两件事是:

  1. 优先队列和
  2. 工作窃取队列。

我怀疑添加这些功能会降低TBB的性能水平。


0
投票

@ user179156。非常有趣的问题。我也想知道你的帖子中列出的类似问题(1)。这是我发现的

以下是增强文档的摘录。

线程安全通常,并行使用不同的对象是安全的,但同时使用单个对象是不安全的。但是,诸如io_service之类的类型提供了更强的保证,即同时使用单个对象是安全的

问题是Boost ASIO如此受欢迎以及如何在内部实施以确保效率。

以下摘自文档,深入了解内部如何实现它。

Linux Kernel 2.6解复用机制:•使用epoll进行解复用。线程:•使用epoll进行多路分解是在一个调用io_service :: run(),io_service :: run_one(),io_service :: poll()或io_service :: poll_one()的线程中执行的。 •每个io_service的附加线程用于模拟异步主机解析

现在让我们看看如何使boost的内部线程和解复用器线程之间的通信更加高效。如果我从头开始编写boost asio。我就是这样做的

使用带管道的epoll

因此,为了在线程之间传递消息,我将简单地将指针传递给我在boost :: post中传递的对象。这是一个有趣的link,关于如何有效地在线程之间传递消息

© www.soinside.com 2019 - 2024. All rights reserved.