重新审视wake_wide启发式搜索算法

简介: 作者:张金利 马涛 云巅论剑

英文链接:https://lwn.net/Articles/728942/

在多核系统上,内核的调度子系统不仅仅要去决策下一个任务跑啥,还得决策下一个任务跑在哪个核上。通常情况下,这种决策被称为“启发式搜索算法”,即一种基于经验法则的最佳实践模式。2015年,有一个关键的“Task-placement heuristic(任务放置启发式搜索算法)”出现了,不过最近的一场讨论让人们开始重新审视它的合理性。

调度器唤醒这事儿总是在发生:任务经常会等待一个事件(比如:定时器过期,POSIX信号,futex()系统调用等等);这类事件发生的时候,任务被唤醒并继续执行。调度器的任务是找到最适合这个刚被唤醒的任务运行的CPU,所谓核心选得好,任务回家早,选对正确的核心对应用的性能极其关键。一些消息传递型的负载,如果运行在同一个CPU上,性能会更好。比如说 pipetest,它有一对收发任务轮流执行,这对任务永远不用也不会并行执行,这样如果他们的数据都保存在一个CPU的cache里,性能就会好得多。

不过在现实生活中,应用程序不会有这么密集的通信,而大多数负载也不会这么轮流发消息,所以一般的高并发的应用都是在响应外部输入后发出消息,而发出消息的时机都是随机的。这里一个非常典型的例子是生产者消费者模型,一个主任务唤醒多个从任务。这种类型的负载,如果其任务同时运行于多个CPU上,性能就会好很多。不过现在机器上CPU很多,怎么挑选一个最好的CPU来跑我们的任务变得非常重要。

另一方面,调度时对CPU的选择也会影响功耗,如果把任务堆在少部分CPU上运行,那么剩下的CPU就可以进入节能模式,如果一个 Socket 上的CPU都空闲了,那么可以更加省电。如果一个节能模式下的空闲CPU被调度上来一个任务,由于CPU从低功耗转变为耗能模式,成本就相应增加了。

所以综上所述,调度器需要根据唤醒模式来猜测负载是什么类型的,并以此来决策上面的任务是要集中运行于少部分CPU上以增加 cache 命中率并节能,还是要全打散来获得更高的CPU利用率。这便是当初 wake_wide() 函数被发明的背景。这个函数是让调度器变得更加错综复杂的众多启发式搜索算法之一,它决定一个刚被唤醒的任务是应该团结在唤醒它的那个任务所运行的CPU附近,还是应该志在远方,跑到另一个 NUMA 节点上去运行。这里面会有个 tradeoff,把任务打包在一起的方式可以提高本地缓存命中率,做过头了却也容易让CPU忙不过来,增加runq的调度延迟。

历史
下面让我们回到过去,wake_wide() 的功能是 Mike Galbraith 在 2015 年引入的。当时的背景是基于 Facebook 的 Josef Bacik 的一段问题描述:Facebook有一堆对延迟敏感的重度多线程应用,使用的是生产者-消费者模型,其中每个NUMA节点都有一个主任务。如果调度器尝试把任务集中到临近的CPU,那么任务唤醒时很可能会被丢到忙的CPU上,导致应用性能急剧下降。对于此应用,共享本地Cache的需求并没有那么迫切,更加重要的是要尽快找到一个空闲的CPU来运行任务。

所以 Galbraith 发明了一个切换算法,用"flips"来描述任务之间唤醒的次数,以动态的区分主从关系。这种启发式算法在 wakewide() 里实现,输入给 selecttaskrqfair() 并且指导其找到最合适的放置刚唤醒的任务的CPU。这个函数可简单了,下面就是全部的代码:

1. static int wake_wide(struct task_struct *p)
2. {
3.    unsigned int master = current->wakee_flips;
4.    unsigned int slave = p->wakee_flips;
5.    int factor = this_cpu_read(sd_llc_size);
6.    if (master < slave)
7.        swap(master, slave);
8.    if (slave < factor || master < slave * factor)
9.        return 0;
10.    return 1;
}

如果 slave 任务少于共享一个 LLC 的CPU数(按:一般来说是一个 Socket 下的所有核共享一个LLC), wakewide 会返回0来表示这个任务不应该在另一个LLC的domain上唤醒,这个时候,selecttaskrqfair() 会相应地把任务打包,只在一个LLC domain里找空闲的CPU。

如果任务比CPU多,或者没找到主从对应关系,任务就会允许在另一个LLC domain上运行,更耗时的全局搜索空闲CPU的逻辑就会被执行。如果另一个LLC domain上的空闲CPU被选中,当前的功耗状态会影响调度选择,从一个更低功耗的状态中退出进入高功耗执行状态会耗费一段时间,所以调度器会倾向于选择功耗状态相对最高的空闲CPU,这样唤醒延迟会尽可能最小。

新方向?
历史从来不是一成不变的。最近Joel Fernandes 就 wakewide() 的设计提出了一些问题,他说:“我不明白为啥要把 slave 的 flips 乘以 LLCsize”(即代码中的 master < slave * factor)。Bacik也回应说,现在的代码或许打包任务打包得太激进了,特别是当大家共享一个LLC却没什么收益的时候,他还说:“我在怀疑 slave < factor 这个判断条件,我想当本地缓存没那么要紧的时候,这个标准定得太高了。”他建议把这个判断表达式去掉可能可以修复激进打包的问题。

原作者(Matt Fleming)提供了一些数据来证明去掉这个判断条件可以提升 hackbench 的性能,原因和前面 Bacik 说的那个例子有关系,当任务被打包得太密集的时候,就会有这个现象。 hackbench 里的任务不是一对对简单的读写关系,而是所有任务在整体内部相互通信。如果 hackbench fork 出更多这种适合在一个LLC domain里运行的任务,那么最终 benchmark 开始的时候,任务就会平均分配到不同的LLC domain里,接下来任务间的通信就有可能会跨LLC domain来回往复进行,这样就会引起极差的cache使用率,性能也就相应差了。

Galbraith 迅速回应并警告说,对 wake_wide() 函数草率地改动是不明智的:“如果你有想法去改进或者替换当前的启发式算法,去做就是了,不过一定要保证成本要足够的低。启发式算法不是这里糟糕就是那里糟糕,问题就在于人们总是想过度优化他”。不过 Bacik 还是想全局利用整个系统的CPU资源,调节调度器不要那么激进地把任务打包到一个LLC domain中去。他怀疑当时 Facebook 的那个 case 中,如果一个 LLC domain 内部没有空闲CPU的时候执行系统全局搜索空闲CPU,那么Facebook当时的负载情况下延迟能进一步降下来。

遗憾的是,在这些讨论中我们并没有听到来自那些把功耗视作第一要务,而把性能排在第二位的开发者的观点,而他们的意见对于这个讨论非常重要。因为如果把 wake_wide() 的打包模式改得不那么激进,那么很可能会有潜在的功耗相关的 Regression,到时候又有人要跳了。

回到原点
讨论到最后也没有任何一方占据上风。Bacik也承认这个修改建议或许有点太重了。也许大家最终想看到的是一个权衡居中的方案。所以基于这个修改的测试和分析还会继续。不过即便如此,我们悲观的认为最后很可能也不会有一个完美的解决方案。理由么,其实很简单;用benchmark和分析工具来量化现本文中提到的行为是很简单的,但是如果要发明一个普适性的,可以同时提升所有负载类型的性能的方案,这是一个真正巨大的挑战。不过借用马老师的一段话,”梦想还是要有的,万一实现了呢?” 所以让我们拭目以待吧。

相关实践学习
CentOS 7迁移Anolis OS 7
龙蜥操作系统Anolis OS的体验。Anolis OS 7生态上和依赖管理上保持跟CentOS 7.x兼容,一键式迁移脚本centos2anolis.py。本文为您介绍如何通过AOMS迁移工具实现CentOS 7.x到Anolis OS 7的迁移。
相关文章
|
6天前
|
算法
【算法】二分算法——搜索插入位置
【算法】二分算法——搜索插入位置
|
11天前
|
机器学习/深度学习 算法 文件存储
【博士每天一篇文献-算法】 PNN网络启发的神经网络结构搜索算法Progressive neural architecture search
本文提出了一种名为渐进式神经架构搜索(Progressive Neural Architecture Search, PNAS)的方法,它使用顺序模型优化策略和替代模型来逐步搜索并优化卷积神经网络结构,从而提高了搜索效率并减少了训练成本。
20 9
|
6天前
|
算法
【算法】递归、搜索与回溯——汉诺塔
【算法】递归、搜索与回溯——汉诺塔
|
12天前
|
存储 机器学习/深度学习 算法
【博士每天一篇文献-综述】基于脑启发的连续学习算法有哪些?附思维导图
这篇博客文章总结了连续学习的分类,包括经典方法(重放、正则化和稀疏化方法)和脑启发方法(突触启发、双系统启发、睡眠启发和模块化启发方法),并讨论了它们在解决灾难性遗忘问题上的优势和局限性。
14 2
|
15天前
|
存储 算法 调度
基于和声搜索算法(Harmony Search,HS)的机器设备工作最优调度方案求解matlab仿真
通过和声搜索算法(HS)实现多机器并行工作调度,以最小化任务完成时间。在MATLAB2022a环境下,不仅输出了工作调度甘特图,还展示了算法适应度值的收敛曲线。HS算法模拟音乐家即兴创作过程,随机生成初始解(和声库),并通过选择、微调生成新解,不断迭代直至获得最优调度方案。参数包括和声库大小、记忆考虑率、音调微调率及带宽。编码策略将任务与设备分配映射为和声,目标是最小化完成时间,同时确保满足各种约束条件。
|
6天前
|
算法
【算法】递归、搜索与回溯——简介
【算法】递归、搜索与回溯——简介
|
1月前
|
大数据 UED 开发者
实战演练:利用Python的Trie树优化搜索算法,性能飙升不是梦!
【7月更文挑战第19天】Trie树,又称前缀树,是优化字符串搜索的高效数据结构。通过利用公共前缀,Trie树能快速插入、删除和查找字符串。
48 2
|
1月前
|
机器学习/深度学习 数据采集 算法
Python实现GBDT(梯度提升树)分类模型(GradientBoostingClassifier算法)并应用网格搜索算法寻找最优参数项目实战
Python实现GBDT(梯度提升树)分类模型(GradientBoostingClassifier算法)并应用网格搜索算法寻找最优参数项目实战
|
1月前
|
机器学习/深度学习 数据采集 算法
Python实现SSA智能麻雀搜索算法优化支持向量机回归模型(SVR算法)项目实战
Python实现SSA智能麻雀搜索算法优化支持向量机回归模型(SVR算法)项目实战
|
1月前
|
机器学习/深度学习 数据采集 算法
Python实现SSA智能麻雀搜索算法优化支持向量机分类模型(SVC算法)项目实战
Python实现SSA智能麻雀搜索算法优化支持向量机分类模型(SVC算法)项目实战

热门文章

最新文章