Linux调度器何时需触发抢占?—— 从hackbench谈起

简介: 作者:何惟禹 吴一昊一、背景:性能之战“不服跑个分”虽然已经沦为手机行业的调侃用语,但在操作系统领域仍然是最重要的评价方式之一。本文的故事也源于一次 Alinux3 与 CentOS8 的一次跑分的较量。当然比分较量并不是目的,更重要的是发现存在的回归缺陷并进行修复,最终让 Alinux3 全方位持平或超过 CentOS8。在本次较量中,我们使用 hackbench 作为跑分软件,我们在测试过程中

作者:何惟禹 吴一昊

一、背景:性能之战

“不服跑个分”虽然已经沦为手机行业的调侃用语,但在操作系统领域仍然是最重要的评价方式之一。本文的故事也源于一次 Alinux3 与 CentOS8 的一次跑分的较量。当然比分较量并不是目的,更重要的是发现存在的回归缺陷并进行修复,最终让 Alinux3 全方位持平或超过 CentOS8。

在本次较量中,我们使用 hackbench 作为跑分软件,我们在测试过程中发现 Alinux3 的回归缺陷出现在内核调度器上,我们通过解剖 hackbench 工作模型、深入分析内核调度器行为将回归缺陷修复,最终让 Alinux3 重新持平 CentOS8。

本文将从几个方面展开,并重点介绍黑体字部分:

  • 相关知识简介
  • hackbench 工作模式简介
  • hackbench 性能受损之源
  • 双参数优化
  • 思考与拓展

二、相关知识简介

2.1 CFS调度器

Linux 中大部分(可以粗略认为是实时任务之外的所有)线程/进程,都由一个叫 CFS(完全公平调度器)的调度器进行调度,它是 Linux 最核心的组件之一。在Linux中,线程和进程只有细微差别,下文统一用进程表述

CFS的核心是红黑树,用于管理系统中进程的运行时间,作为选择下一个将要运行的进程的依据。此外,它还支持优先级、组调度(基于我们熟知的cgroup实现)、限流等功能,满足各种高级需求。CFS 的详细介绍[1]

2.2 hackbench

hackbench 是一个针对 Linux 内核调度器的压力测试工具,它的主要工作是创建指定数量的调度实体对(线程/进程),并让它们通过 sockets/pipe 进行数据传输,最后统计整个运行过程的时间开销。

2.3 CFS 调度器参数

本文重点关注以下两个参数,这两个参数也是影响 hackbench 跑分性能的重要因素。系统管理员可以使用 sysctl 命令来进行设置。

  • 最小粒度时间:kernel.sched_min_granularity_ns         通过修改 kernel.sched_min_granularity_ns,可以影响 CFS 调度周期(sched period)的时间长短。例如:设置kernel.sched_min_granularity_ns = m,当系统中存在大量可运行进程时,m 越大,CFS 调度周期就越长。     如图 1 所示,每个进程都能够在 CPU 上运行且时间各有长短,sched_min_granularity_ns 保证了每个进程的最小运行时间(优先级相同的情况下),sched_min_granularity_ns 越大每个进程单次可运行的时间就越长。

 

图 1:sched_min_granularity_ns 示意图

  • 唤醒抢占粒度:kernel.sched_wakeup_granularity_ns     kernel.sched_wakeup_granularity_ns 保证了重新唤醒的进程不会频繁抢占正在运行的进程,kernel.sched_wakeup_granularity_ns 越大,唤醒进程进行抢占的频率就越小。     如图 2 所示,有 process-{1,2,3} 三个进程被唤醒,因为 process-3 的运行时间大于 curr(正在 CPU 上运行的进程)无法抢占运行,而 process-2 运行时间小于 curr 但其差值小于 sched_wakeup_granularity_ns 也无法抢占运行,只有 process-1 能够抢占 curr 运行,因此 sched_wakeup_granularity_ns 越小,进程被唤醒后的响应时间就越快(等待运行时间越短)。

 

图 2:sched_wakeup_granularity_ns 示意图

三、hackbench 工作模式简介

hackbench 工作模式分为 process mode 和 thread mode 主要区别就是以创建 process 还是 thread 为基础来进行测试,下面以 thread 来进行介绍。

  1. hackbench 会创建若干线程(偶数),均分为两类线程:sender 和 receiver
  2. 并将其划分为 n 个 group,每个 group 包含 m 对 sender 和 receiver。
  3. 每个 sender 的任务就是给其所在 group 的所有 receiver 轮流发送 loop 次大小为 datasize 的数据包
  4. receiver 则只负责接收数据包即可。
  5. 同一个 group 中的sender 和 receiver 有两种方式进行通信:pipe 和 local socket(一次测试中只能都是 pipe 或者 socket),不同 group 之间的线程没有交互关系。

通过上面 hackbench 模型分析,可以得知同一个 group 中的 thread/process 主要是 I/O 密集型,不同 group 之间的 thread/process 主要是 CPU 密集型。

 

图 3: hackbench 工作模式

主动上下文切换:

  • 对于 receiver,当 buffer 中没有数据时,receiver 会被阻塞并主动让出 CPU 进入睡眠。
  • 对于 sender,如果 buffer 中没有足够空间写入数据时, sender 也会被阻塞且主动让出 CPU。

因此,系统中"主动上下文切换"是很多的,但同时也存在“被动上下文切换”。后者会受到接下来我们将要介绍的参数影响。

四、hackbench性能受损之源

在 Alinux3. VS CentOS8. 的 hackbench-socket 测试中,Alinux3 的性能均逊于 CentOS8,通过排查锁定了 CFS 中的 sched_min_granularity_ns 和 sched_wakeup_granularity_ns 两个参数,具体如下:

sched_min_granularity_ns

sched_wakeup_granularity_ns

性能

Alinux3

2.25ms

3ms

CentOS8

10ms

15ms

接下来我们调整这两个调度参数来进行进一步的深入分析。

五、双参数优化

为了简介表达下面会以 m 表示 kernel.sched_min_granularity_ns,w 表示 kernel.sched_wakeup_granularity_ns

为了探索双参数对于调度器的影响,我们选择每次固定一个参数,研究另一个参数变化对于性能的影响,并使用系统知识来解释这种现象背后的原理。

5.1 固定 sched_wakeup_granularity_ns

图 4: 固定 w,调整m

在上图中我们固定了参数 w 并根据参数 m 变化趋势其划分为三个部分:区域A(1ms~4ms),区域B(4ms~17ms),区域C(17ms~30ms)。在区域A中四条曲线均呈现一个极速下降的趋势,而在区域B中四条曲线都处于一种震荡状态,波动较大,最后在区域C中四条曲线都趋于稳定

在第二节相关知识中可以知道 m 影响着进程的运行时间,同时也意味着它影响着进程的“被动上下文切换”。

  • 对于区域A而言,抢占过于频繁,而大部分抢占都是无意义的,因为对端无数据可写/无缓冲区可用,导致大量冗余的“主动上下文切换“。此时较大的 w 能让 sender/receiver 有更多的时间来写入数据/消耗数据来减少对端进程无意义的“主动上下文切换“。
  • 对于区域B而言,随着 m 的增加渐渐满足 sender/receiver 执行任务的时间需求能够在缓冲区写入/读出足够的数据,因此需要较小的 w 来增加唤醒进程的抢占几率,让对端进程能够更快的响应处理数据,减少下一轮调度时的“主动上下文切换”。
  • 对于区域C而言,m已经足够大,已经几乎不会有“被动上下文切换”发生,进程会在执行完任务之后进行“主动上下文切换”等待对端进程进行处理,此时 m 对性能的影响就很小了。

5.2 固定 sched_min_granularity_ns

图 5: 固定 m,调整w

在上图中我们固定了参数 m,同样划分了三个区域:

  • 在区域A中,同样存在图 4 中的现象,较大 m 受 w 的影响较小,而较小的 m 随着 w 的增大性能会越来越好。
  • 在区域B中,中等大小的 m(8ms/12ms)进程还是存在较多“被动上下文切换”,并且其中的进程已经处理了相当一部分数据期望对端进程能够尽快的响应处理,因此较大 w 会严重影响中等大小 m 的性能。
  • 在区域C中图5和图4表现一致都是趋于稳定,因为 w 过大时几乎不会发生唤醒抢占,因此这时单纯 w 值的变化对性能的影响并不大,但是过大的 w 对于中等大小的 m 则会造成性能问题(原因同上条)。 

5.3 性能趋势总览

下面是一个实验数据的热力总览图,来直观展示 m 和 w 之间的制约关系,以供需要的同学参考分析。三个区域和图 4、图 5中的划分方法略有不同。

图 6:总览图

5.4 最优双参数(参考)

  1. 从上面两节的分析可知对于 hackbench 这样带有“主动上下文切换”的场景可以选择较大的 m(例如:15~20ms),
  2. 在pipe/socket 双向通信的场景中,对端的响应时间会对影响进程的下一次处理,为了让对端进程能够及时响应可以选择一个中等大小的 w(例如:6~8ms)来获取较高的性能。

六、思考与扩展

  1. 在桌面场景中,应用更偏向于交互型,应用的服务质量也更多的体现在应用对于用户操作的响应时间,因此可以选择较小的 sched_wakeup_granularity_ns 来提高应用的交互性。
  2. 在服务器场景中,应用更偏向于计算处理,应用需要更多的运行时间来进行密集计算,因此可以选择较大的 sched_min_granularity_ns,但是为了防止单个进程独占 CPU 过久同时也为了能够及时处理客户端请求响应,应该选择一个中等大小的 sched_wakeup_granularity_ns。
  3. 在 Linux 原生内核中 m 和 w 的默认参数被设置为适配桌面场景 [2],因此默认参数并不适合 Alinux。

参考资料

[1] http://www.wowotech.net/process_management/451.html

[2] https://www.kernel.org/doc/Documentation/scheduler/sched-design-CFS.txt

相关文章
|
22天前
|
负载均衡 算法 Linux
深度解析:Linux内核调度器的演变与优化策略
【4月更文挑战第5天】 在本文中,我们将深入探讨Linux操作系统的核心组成部分——内核调度器。文章将首先回顾Linux内核调度器的发展历程,从早期的简单轮转调度(Round Robin)到现代的完全公平调度器(Completely Fair Scheduler, CFS)。接着,分析当前CFS面临的挑战以及社区提出的各种优化方案,最后提出未来可能的发展趋势和研究方向。通过本文,读者将对Linux调度器的原理、实现及其优化有一个全面的认识。
|
2月前
|
负载均衡 Linux 调度
Linux 进程调度器入门
Linux 进程调度器入门
29 0
|
负载均衡 Linux 调度
Linux 进程调度器入门
Linux 进程调度器入门
99 0
|
Linux 调度 开发工具
Plugsched 实战解读:如何在不中断业务时对 Linux 内核调度器热升级? | 龙蜥技术
plugsched 如何使用?每一步的操作如何进行以及背后的工作是什么?
Plugsched 实战解读:如何在不中断业务时对 Linux 内核调度器热升级? | 龙蜥技术
|
存储 弹性计算 运维
龙蜥开源 Plugsched:首次实现 Linux kernel 调度器热升级
Plugsched 是 Linux 内核调度器子系统热升级的 SDK,它可以实现在不重启系统、应用的情况下动态替换调度器子系统,毫秒级 downtime。Plugsched 可以对生产环境中的内核调度特性动态地进行增、删、改,以满足不同场景或应用的需求,且支持回滚。
297 0
龙蜥开源 Plugsched:首次实现 Linux kernel 调度器热升级
|
弹性计算 运维 安全
龙蜥开源Plugsched:首次实现 Linux kernel 调度器热升级 | 龙蜥技术
对于plugsched而言,无论是 bugfix,还是性能优化,甚至是特性的增、删、改,都可胜任。
龙蜥开源Plugsched:首次实现 Linux kernel 调度器热升级 | 龙蜥技术
|
存储 算法 Linux
如何更改 Linux 的 I/O 调度器
Linux 的 I/O 调度器是一个以块式 I/O 访问存储卷的进程,有时也叫磁盘调度器。Linux I/O 调度器的工作机制是控制块设备的请求队列:确定队列中哪些 I/O 的优先级更高以及何时下发 I/O 到块设备,以此来减少磁盘寻道时间,从而提高系统的吞吐量。
2005 0