乐趣区

关于workflow:C-Workflow异步调度框架-性能优化上篇

最近在致力同步保护 SegmentFault 的文章积攒~不便后续继续更新~
原文是 2019 年 7 月底开源后陆续 po 的,这里对近况进行了调整和补充。
心愿本人和我的项目都能够继续提高 (๑╹ヮ╹๑)ノ 欢送多多交换!!

搜狗 C ++ workflow 异步调度框架 github 地址:
GitHub – sogou/workflow: C++ Parallel Computing and Asynchronous Networking Engine

先来和大家 update 一下,这一周以来 workflow 又有哪些成长呢:

  • 新版更简洁的 README.md
  • server 默认应用 ipv4 启动(为了兼容 windows 与 unix 的行为
  • 加了全局配置项的文档 about-config.md(正好和明天的话题相干!

开源了两周,备受小伙伴们关注,真的很开心 >_< 特别感谢各位关注和反对咱们的大神们小朋友们,心愿可能继续和大家交换,一起提高~

(下图更新于 2022 年 3 月,开源一年多留个留念~)

我也在整顿开源我的项目规范化相干的事件,如果有哪里做得不到位,心愿能帮我指出~

明天这个话题是十分通用的入门话题:写完代码咱们须要做什么最根本的零碎性能优化。

因为 workflow 是个异步调度引擎,workflow 的职责就是 让零碎各资源尽可能地利用起来,所以我的日常工作,除了写 bug 之外,还要配合开发小伙伴现场 debug、剖析用了 workflow 之后的各项指标是否还能进一步晋升。

我还是联合具体几类资源为线索来介绍:

  1. CPU:多路复用相干的线程数、计算相干线程数、多过程
  2. 网络:长连贯短连贯、连接数管制、超时配置、压缩
  3. 计时器:timerfd 的优化
  4. 计数器:命名计数器与匿名计数器
  5. 文件 IO:理论场景用得少,先不写了
  6. GPU:目前我只做了 demo 版,所以没有放进去,也先不写了

其中计时器和计数器绝对简略一些,我会这里介绍下外部实现,其余的外部实现做了很多优化,每个话题都值得当前独自写一下。

一、CPU

先来看看咱们的配置项:

static constexpr struct WFGlobalSettings GLOBAL_SETTINGS_DEFAULT =
{
    .endpoint_params    =   ENDPOINT_PARAMS_DEFAULT,
    .dns_ttl_default    =   12 * 3600,
    .dns_ttl_min        =   180,
    .dns_threads        =   4,
    .poller_threads     =   4,
    .handler_threads    =   20,
    .compute_threads    =   -1,
};

1. 根本网络线程

个别用 epoll 的框架都须要对其进行相似 proactor 式的封装,那么就要负责做以下事件,以及决定具体哪个线程去分工:

  1. 对 epoll 具体 某个 fd 进行读写
  2. 读写时 把残缺数据包切下来
  3. 数据包切完之后的解析(即 反序列化
  4. 执行 用户的操作

Workflow 以后的做法,poller_threads线程是去操作 epoll 读写和做 fd 读的切音讯的事件,而 handler_threads 是做根本用户操作的,比方 callback 和作为 server 的话,咱们的 process 函数所在的线程。

brpc 是不须要辨别的,我集体了解有几个起因,比方:

  • 它套了一层 bthread 做换线程的调度;
  • fd 上拉了写链表:没人在写你就写,有人在写你就把数据扔下就行了,这个人会帮你写,不存在相似 handler 线程还要回去管 poller 线程的异步写的事件;

Workflow 没有做这样的优化,次要还是因为一个过程内网络读写和业务操作压力比例根本是差不多确定的,业务上线前的调优调整一下 poller 和 handler 线程比例根本足够了,而且纯探讨性能的话,业内的解决方案根本是纯异步会比用户态协程模式快一点点,目前 C ++20 的 coroutine 曾经进去了,心愿后续能更多业界成熟且高效的用法~Workflow 目前才 1 岁多,很多优化可能都会往后放。

这里也顺带说一句,对于把数据包切下来和切完之后的解析,其实有些协定是不太能分得开的。

我鶸鶸地给大家列一下,从协定设计上,能够分以下三类:

  • 收到音讯就能晓得我怎么残缺地切一条音讯进去;
  • 收一点之后判断一下能力晓得我怎么切一条音讯进去;
  • 一边收数据流一边解析,不到最初一刻都不晓得是不是收完。

第 1 种就很简略,个别做 RPC 协定 咱们都会敌对地在头部通知你大略多长。

第 2 种有点相似 HTTP 这样,大略收完头部你就晓得后边还有多少了,这个时候你收 header,是要本人边收边 parse 的。

第 3 种比方 MySQL 这种吐血的协定,大略它在设计的时候就没有想过你要多线程去操作一个 fd 读音讯,你得依据以后哪种包的状态再判断,这种必须写个状态机去残缺收完了能力交给用户。而这个收的期间,我曾经把每个 field 和每个 ResultSet 给解析进去了,收完根本等于数据反序列化也做完了。
所以第 2 种、第 3 种,对于切残缺音讯和解析音讯的反序列化操作其实并不会太分得开,workflow 都会在 poller_threads 里做。

2. 计算线程

咱们外部会有 独立的计算线程池,默认是和零碎 cpu 数统一的数量,这个是根本能够缩小线程数过多而频繁切换的问题,当然如果用不到计算工作,此线程池不会创立。

和 cpu 数统一,那么不同期间不同类型的计算工作占比不同,这个 workflow 怎么解决呢?咱们外部用了一个谢爷创造的 多维队列调度模型,曾经申请专利,当前有机会让谢爷写一篇给大家讲讲 >_<

简略来说,workflow 的计算工作都是带名字的,对于业务开发来说,根本只须要把同一类工作以同一个名字去创立,那么 start 之后是根本能够保障 不同名字的工作被偏心调度 ,并且 整体尽可能用满计算线程数,这是一种比优先级和固定队列要灵便得多的做法。

P.S. 咱们也有独立的 DNS 线程池,然而 DNS 目前的路由模式我感觉要并发去更新真的十分粗犷十分不喜爱,有空了路由机制是我第一个要动刀改良的中央!(认真立 flag 中 o(~▽~)o

3. 多过程

一般来说咱们不太须要多过程,然而不可避免的状况下,先前有个场景的确须要小伙伴拆多过程:应用 Intel QAT 加速卡多线程会卡 spinlock,这个前几篇文章有个系列曾经提到过。

通用点说多过程,一般来说咱们作为 server 的做法是先 bind 再 listen,而后 fork 多个过程,而后,重点是在于,你这个时候再去 epoll_create,那么操作系统来保障连进来同一个端口的连贯不会惊群 accept。

这个我集体的了解是:

  1. 首先咱们 bind 并 listen,是保障多个过程拿到同一个 listen_fd。
  2. 而后先 fork 再 epoll_create,意思是由多个 epoll 去 listen 同一个 listen_fd。因为 epoll 不是用户态的,操作系统来保障同一个 listen_fd 的 accpet 只会被一个 epoll 来响应,所以不会有惊群。

说回 workflow~workflow 的 server 想做成多过程就很简略了:用 WFServer::serve()接口,做以上 fd 本人 bind、listen,再 fork 屡次的事件就能够了。

也给大家列一下 demo 测试中多过程操作加速卡的性能。绿色的点是 nginx(只能打到 8w),nginx 自身就是多过程单线程的,然而因为 QAT 只以多过程纬度来解决并发,因而咱们只以过程数比照,根本轻松上 10w 了。并且说一下,QAT 加速卡如果只做 RSA 计算,极限 QPS 也就是 10w 左右。

以及短连贯、长连贯状况下多过程、多线程在咱们小伙伴调用 QAT 加速卡每个申请做 2 次 RSA 解压时的 QPS 比照状况:

这里的短连贯长连贯,就当作给大家抛砖引玉网络调优话题,但明天来不及,今天持续写下篇~~~

本系列的前两篇(站内):
C++ Workflow 异步调度框架 – 根本介绍篇
C++ workflow 异步调度框架 – 架构设计篇

下一篇重磅(这两天会更新到 segmentfault):
C++ Workflow 异步调度框架 – 性能优化网络篇

本系列其余文章:
C++ Workflow 异步调度框架 – Kafka 客户端
pyworkflow 带你详解,那些 Python 调 C ++ 的大坑
SRPC 架构介绍 – Sogou 基于 Workflow 的自研 RPC 框架

退出移动版