LWN:用SCHED_EXT和IOCost改进性能!

关注了就能看到更多这么棒的文章哦~

Improving performance with SCHED_EXT and IOCost

By Daroc Alden
April 1, 2024
SCALE
ChatGLM translation
https://lwn.net/Articles/966618/

在今年的SCALE大会上,来自 Meta 的 Dan Schatzberg 和 Tejun Heo 先后连续进行了两个演讲,介绍了他们在该公司进行的一些性能工程(performance-engineering)工作。Schatzberg 介绍了可扩展的BPF调度器,该调度器在内核邮件列表上进行了广泛讨论。Heo 则介绍了 IOCost,一种针对固态硬盘(SSD)优化的控制组(cgroup)I/O 控制器,以及为了让它在不同型号的硬盘上运行良好而必需的基准测试套件。

使用 BPF 进行调度

多年来,Linux 一直使用完全公平调度器(CFS, Completely Fair Scheduler)来决定哪些任务应该在何时运行,以及在哪个 CPU 上运行。在 6.6 版内核中,默认调度器更改为最早可用的虚拟截止时间优先(EEVDF, Earliest Eligible Virtual Deadline First)调度器。Schatzberg 指出,这两种调度器都存在一个严重的问题:很难进行迭代改进,因为尝试新的调度器策略都需要重新构建内核并重启系统。他介绍了基于 BPF 的调度器来作为解决这一问题的一种方法——这是过去内核社区中的一个广泛争论的话题。

bb14c9d011e1d31d344ec3468f681b72.png

调度器的核心理念很简单——“调度器的职责就是共享 CPU 核心”,但实现高性能的调度器可以使事情“马上变得非常复杂”。Schatzberg 指出,异构计算系统的日益普及是复杂性的一个具体来源。人们可能关心的良好调度器的几个属性,例如公平性、优化、低开销和通用适用性。因此,为特定工作负载找到最佳的调度策略需要进行大量的性能测量和实验。一旦找到了合适的调度策略,要想使用它,则需要维护一个不在 kernel tree 里维护的调度器。Schatzberg 的雇主有将尽可能多的内核更改推送到上游的政策,希望能减少维护负担并使用最新的内核。将不断变化的工作负载相关的调度器的补丁推送到上游是很困难的。

因此,Schatzberg 和其他人一直在努力实现一个可扩展的调度器( SCHED_EXT )“允许你以 BPF 程序的形式在运行时实现调度策略”。使用 SCHED_EXT 进行调度策略实验涉及编写 BPF 程序、编译它,并将其加载到运行的内核中。当切换到新的调度器时,“甚至不需要停止正在运行的工作负载”,这使得实验变得更加快速。

Schatzberg 花了一些时间解释了为了使这种实验足够安全所做的努力。由于调度器是用 BPF 编写的,BPF 验证器确保它不会使内核崩溃或损坏。此外, SCHED_EXT 中有一个内核看门狗,如果 BPF 调度器在配置的时间内未能调度任务,它会自动将其踢出。因此,即使是一个会拒绝调度任何任务的恶意 BPF 调度器,也不会让系统永久挂起。

编写调度器

Schatzberg 随后解释了如何实际使用 SCHED_EXT 编写调度器。基本思想是,BPF 程序实现了一个操作结构,其中包含内核在调度时将调用的回调函数。这个 BPF 设置的结构还包含各种配置选项,例如调度器的名称和期望的看门狗超时时间。这三个回调包含调度器最关键的部分:

s32 (*select_cpu)(struct task_struct *p, s32 prev_cpu, u64 wake_flags);
void (*enqueue)(struct task_struct *p, u64 enq_flags);
void (*dispatch)(s32 cpu, struct task_struct *prev);

这三个都有默认实现,但“任何一个复杂的调度器都可能会需要重新实现这三个 API”。

要实际实现这些回调,您需要理解调度队列,这些队列是包含可运行任务列表的数据结构。默认情况下,每个 CPU 都有自己的调度队列,它会自动从自己的队列中取任务,但调度器可以创建任意数量的辅助调度队列。 SCHED_EXT 在对这些队列执行操作时会做好所有必须的加锁保护。

在理解这些内容之后,Schatzberg 展示了如何编写一个简单的全局性的先进先出(FIFO)调度器。这是一个简单的调度策略,但 Schatzberg 指出:“这在我们的某些生产工作流中实际上比 CFS 表现更好”,因为尽快将任务放入空闲核心中有一些好处。这个示例调度器将任务添加到全局调度队列中作为 enqueue 动作,当 CPU 在自己的队列中没有工作时,再从这个队列中选择任务来进行调度。

s32 BPF_STRUCT_OPS_SLEEPABLE(mysched_init)
{
  scx_bpf_create_dsq(/* 队列id = */ 0, -1);
}

mysched_init() 创建了调度器使用的全局调度队列。

void BPF_STRUCT_OPS(mysched_enqueue, struct task_struct *p, u64 enq_flags)
{
  scx_bpf_dispatch(p, /* 队列id = */ 0, SCX_SLICE_DFL, enq_flags);
}

mysched_enqueue() 从内核接收任务,并直接将其放置到全局调度队列的末尾。

void BPF_STRUCT_OPS(mysched_dispatch, s32 cpu, struct task_struct *prev)
{
  scx_bpf_consume(/* 队列id = */ 0);
}

mysched_dispatch() 在 CPU 变为空闲并需要另一个任务时来运行。它从全局调度队列的前端取任务并立即开始运行。

Schatzberg 指出,调度器的完整实现比这个示例稍长,因为它需要处理一些任务,比如当有更多工作时唤醒已进入睡眠状态的 CPU,但他强调一个完整的调度器可以在 150 行代码(包括注释)内编写完成。

然后,他给出了一个稍微不那么简单的调度器的例子,该调度器为每个 L3 缓存维护了一个单独的队列。他指出,即使是这种简单的优化也引发了许多问题——CPU 核心何时从其他 L3 缓存中获取 job?何时应该主动在不同的队列之间进行负载均衡?Schatzberg 说,真正的答案取决于工作负载,开发者应该使用 SCHED_EXT 进行实验。“整个想法是 SCHED_EXT 让我们能够非常快速地实验这些类型的策略”。

他结束演讲时,给出了一些适用于不同目的的各种调度器的示例。这些调度器以及各种支持工具,使编写基于 BPF 的调度器变得更加容易,它们都可以在GitHub上找到。对于未来的计划,他将 SCHED_EXT 推送到内核中作为最高优先级任务——这可能是一项艰巨的任务,因为调度器维护者 Peter Zijlstra不喜欢这个补丁系列。Schatzberg 认为 SCHED_EXT 对用户可能是有价值的:“我们希望围绕它建立一个社区”。他说,有许多功能可以添加上来,使得 SCHED_EXT 更易于使用,但这些功能主要是 BPF 特性,而不是 SCHED_EXT 特性本身。

IOCost

在 Schatzberg 的演讲之后,Heo 描述了他自己在性能相关工作的另一个领域,即 cgroup I/O 控制器。cgroups是进程的层次化分组,可以给层次中的不同节点配置不同的资源限制。Heo 将它们比作包含系统上运行进程的文件系统。cgroup 控制器负责为 cgroup 内的进程分配资源,并在它们超出限制时做出反应。已经有针对内存使用、CPU 使用、进程创建、块设备 I/O 等的 cgroup 控制器。

e609b044a085999b6565254b5e7428bd.png

Heo 一直在研究一个名为IOCost的块设备 I/O 控制器。该控制器旨在将固态硬盘(SSD)的块操作分发到不同的 cgroup,确保应用程序无法独占磁盘带宽。

Heo 首先解释了为什么编写 I/O 控制器比编写内存或 CPU 控制器更具挑战性。首先,通常用于衡量块设备性能的指标“并不好”。例如,如果把应用程序的预算配置成每秒多少字节数或多少个 I/O 请求数,就可能使得这个限制即过低同时又过高了,因为固态硬盘(SSD)每秒可以服务的实际字节数会因多种因素而大幅波动。这使得基于这些数字的单一指标控制方式变得非常具有挑战性。

更严重的是,SSD 可以非常快,如果 cgroup 控制器的开销过高,它可能会显著影响应用程序的性能。这不像机械硬盘,在那里,为了优化磁盘读取,经常值得牺牲一些 CPU 时间,这是因为磁盘延迟很高。这意味着为旋转硬盘设计的现有 I/O 控制器在 SSD 上表现不佳。

最后,块设备是整个系统需要使用的共享资源,这使得很容易意外导致优先级反转。例如,带着入日志文件系统进行写入会需要对整个文件系统所共享的日志进行写入。如果 I/O 控制器延迟一个低优先级进程的日志写入,那么所有需要写入日志的高优先级进程也会等待。优先级反转不是块设备独有的问题,但在那里尤其明显,因为系统上的每个进程都争夺相同的资源。

IOCost 通过多种方式解决这些挑战,首先使用“一个相当简单的线性模型”来衡量块设备操作的成本,这个模型“比任何单一指标都要好”。它也不尝试限制应用程序到一个数值限制。Heo 指出,如果你问应用程序开发者他们需要每秒多少 I/O 操作或多少 MB,你不会得到一个好的答案。“没有人真正知道。”因此,IOCost 施加相对限制——允许管理员说每个 cgroup 应该按比例接收多少 I/O。

为了在不会妨碍 SSD 性能的情况下快速做出访问决策,IOCost 将其逻辑分为两部分:一个会每隔几毫秒运行於一次的计划路径逻辑(planning path),以及一个在每个请求上运行的执行路径(execution path)。计划路径处理所有复杂的计算,然后将配置推送到执行路径,这样执行路径就不需要做太多工作来确定如何处理具体的某一个请求。这有助于保持控制器的延迟开销较低。

最后,IOCost 与内核中的文件系统和内存管理设施集成起来,以避免优先级反转。预期会引发优先级反转的被限流了的 I/O 请求会立即运行,但随后会向超出限制的过程来找补回来。Heo 将这种方法总结为“先做,后付费”。

IOCost 在实践中对于其设计用途表现良好。Heo 展示了某些内部测试的结果,这些结果显示 IOCost 的延迟开销可以忽略不计,并且完美地保持了两个基准工作负载之间配置的比例。IOCost“在 Meta 的几乎所有机器上都得到了全面部署”。他总结这一部分时,展示了一张幻灯片,上面写着“只要配置得好,IOCost 就能很好地工作”。

但这个陈述掩盖了许多复杂性,因为配置 IOCost 并不容易。IOCost 使用的线性成本模型来估计特定 I/O 请求的成本需要为每个型号的 SSD 进行调整,因为它们的行为各不相同。为了解决这个问题,Heo 和他的合作者创建了resctl-bench:一个基于场景的基准测试工具,观察 SSD 的端到端行为。它是一个更大的名为 resctl-demo 的项目的一部分,该项目包括 Linux 上各种不同类型的资源控制的相关材料。 resctl-bench 模拟了“类似于我们在生产环境中看到的”工作负载。Heo 还表示,与其他基准相比,这是一个主要的改进,因为:“这里的标准相当低。我们没有标准,因为没有什么能很好地工作”。

resctl-bench 并不容易设置和运行,所以 Heo 还制作了一个可安装的 Linux 镜像,该镜像运行基准测试并基于结果自动生成报告(以及 IOCost 的配置)。他提到:“我不想做现场演示,因为需要八个小时”,但他确实展示了一系列幻灯片,解释了如何使用该工具来表征一个 SSD,然后将测量结果提交到数据库,他正在收集不同 SSD 型号的统计数据。他呼吁感兴趣的人在自己的系统上运行这个基准测试,因为更多的数据可以使 IOCost 对每个人性能都更好。

一位听众问,让 IOCost 在运行时学习它需要的参数,这是否可行。Heo 解释说,这不可行,因为从 SSD 正常运行中获取所需反馈的难度很高。“如果可以做到的话,那会非常好”,他评论道。另一位听众问,基准测试是否考虑了 SSD 的过预配置(over-provisioning)——即在 SSD 声明的容量之外放入更多的存储空间,以便磁盘固件有备用扇区来替换故障扇区。只按照硬盘声明容量来进行写入的基准测试往往会对于过预配置硬盘的长期性能给出不准确的结果。Heo 确认 resctl-bench 确实考虑了过预配置,并且它最终在基准测试过程中写入的数据是硬盘声明容量的许多倍。

我询问了 IOCost 的计划;Heo 说:“在我们的环境中中,它似乎工作得很好”,并且他希望看到更多的人使用它。他认为 IOCost 相当完整,唯一的显著例外是配置起来非常痛苦。他希望在以后能改进这一点。

IOCost 已经是内核的一部分,可以通过遵循文档进行配置。 resctl-bench 的文档解释了如何在自己的设备上尝试这个基准测试。

[我想要感谢 LWN 的旅行赞助商,Linux 基金会,为我前往 Pasadena 参加 SCALE 提供旅行协助。]

全文完
LWN 文章遵循 CC BY-SA 4.0 许可协议。

欢迎分享、转载及基于现有协议再创作~

长按下面二维码关注,关注 LWN 深度文章以及开源社区的各种新近言论~

1ca668ff29247766d16a390c2b50797f.jpeg

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值