加入收藏 | 设为首页 | 会员中心 | 我要投稿 惠州站长网 (https://www.0752zz.com.cn/)- 办公协同、云通信、物联设备、操作系统、高性能计算!
当前位置: 首页 > 建站 > 正文

硬核!Rust异步编程方式重大升级:新版Tokio如何提升10倍性能详解

发布时间:2019-10-24 02:35:14 所属栏目:建站 来源:高可用架构
导读:副标题#e# 协程或者绿色线程是近年来经常讨论的话题。Tokio作为Rust上协程调度器实现的典型代表,其设计和实现都有其特色。本文是Tokio团队在新版本调度器发布后,对其设计和实现的经验做的总结,十分值得一读。 Tokio作为 Rust 语言的异步运行时,我们一直

通常来说,硬件不是通过提高速度(频率)而是为程序提供更多的 CPU 核来获取性能提升。每个核都可以在极短的时间内执行大量的计算,相较而言,访问内存之类的操作则需要更多时间。因此,为了使程序运行得更快,我们必须使每次内存访问的 CPU 指令数量最大化。尽管编译器可以帮助我们做很多事,但作为程序设计开发人员,我们需要谨慎地考虑数据在内存中的结构布局以及访问内存的模式。

当涉及到线程并发时,CPU 的缓存一致性机制就会发挥作用,它会确保每个 CPU 的缓存都保持最新状态。

所以显而易见,我们要尽可能地避免跨线程同步,因为它是性能杀手。

多处理器+多任务队列

与前面的模型对比,在这种方式下,我们使用多个单线程调度器,每个处理器都有自己独占的任务队列,这样完全避免了同步问题。由于 Rust 的任务模型要求任意线程都可以提交任务到队列,所以我们仍需要设计一种线程安全的方式。要么每个处理器的任务队列支持线程安全的插入操作(MPSC),要么就每个处理器有两个队列:非同步队列和线程安全队列。

硬核!Rust异步编程方式重大升级:新版Tokio如何提升10倍性能详解

这便是 Seastar 所使用的策略。因为几乎完全避免了同步,所以性能非常高。但需要注意的是,这并不是灵丹妙药,因为无法确保任务负载都是完全一致统一的,处理器可能出现严重的负载不均衡,使得资源利用率低下。这通常产生的场景是任务被粘到了固定的、特定的处理器上。

众所周知,真实世界的任务负载并不是一致统一的,所以在设计通用调度器时要避免使用此种模型。

“任务窃取”调度器

通常来说,任务窃取调度器是建立在分片调度模型之上的,主要为了解决资源利用率低的问题。每个处理器都具有自己独占的任务队列,处于“可运行的”任务会被插入到当前处理器的队列中,并且只会被当前处理器所消费(执行)。但巧妙的是,当一个处理器空闲时,它会检查同级的其他处理器的任务队列,看看是不是能“窃取”一些任务来执行。这也是这种模型的名称含义所在。最终,只有在无法从其他处理器的任务队列那里获得任务时该处理器就会进入休眠。

硬核!Rust异步编程方式重大升级:新版Tokio如何提升10倍性能详解

这几乎是“两全其美”的方法。处理器可以独立运行,避免了同步开销。而且如果任务负载在处理器间分布不均衡,调度器也能够重新分配负载。正是由于这样的特性,诸如 Go 语言、Erlang 语言、Java 语言等都采用了“任务窃取”调度器。

当然,它也是有缺点的,那就是它的复杂性。任务队列必须支持“窃取”操作,并且需要一些跨处理器同步操作。整个过程如果执行不正确,那“窃取”的开销就超过了模型本身的收益。

让我们来考虑一个场景:处理器 A 当前正在执行任务,并且此刻它的任务队列是空的;处理器 B 此时空闲,它尝试“窃取”任务但是失败了,因此进入休眠态。紧接着,处理器 A 所执行的任务产生出了20个(子)任务。目的是唤醒处理器 B。这进而就需要调度器在观察到任务队列中有新的任务时,向处于休眠态的处理器发出信号。显而易见,这样的场景下会需要额外的同步操作,但这恰恰是我们想要避免的。

(编辑:惠州站长网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

推荐文章
    热点阅读