下载本文的PDF版本 PDF

扩展调度优先级的语义

日益增长的并行性需求新的范例。


Rafael Vanoni Polanczyk,Oracle Solaris内核组


应用程序性能直接受到应用程序所需的硬件资源、这些资源的可用程度以及操作系统如何解决其对系统中其他进程的需求的影响。理想情况下,应用程序可以访问其可以使用的所有资源,并被允许完成其工作,而无需与系统中的任何其他活动竞争。然而,在硬件资源高度共享和基于通用分时操作系统的世界中,无法保证应用程序的资源充足程度。

为了更好地利用共享硬件资源,可以做些什么来改进应用程序的开发方式以及软件堆栈底层的工作方式?扩展调度优先级的一些语义以包括对共享资源的优先级,可以允许应用程序的性能关键组件以更少的资源争用执行。

动态资源配置、高度共享的硬件组件

过去十年见证了多核处理器的兴起及其随后的快速商品化。无论是应用程序级别的软件开发人员,还是更广泛的系统设计人员,都不能忽视这种格局的巨大变化。虽然并非所有问题都需要并行实现作为解决方案,1 但必须比以往任何时候都更认真地考虑这种机会。学术界和工业界都顺应了这一趋势,既教育和倡导并发软件开发,又寻求利用硬件并行性的新技术。不幸的是,关于在现代处理器上开发并发应用程序的许多细节已经悄悄溜走,只能在白皮书、博客以及性能社区类似的聚集地中找到。

开发人员越来越频繁地发现,并行应用程序的规模调整并不像以前看起来那么简单。直到最近,单个处理器芯片中可用的一个或两个处理器造成的共享资源争用,最多不过是两个软件线程争夺共享缓存空间。如今,逻辑 CPU 之间不仅存在多个级别的共享(共享执行流水线、浮点单元、不同的缓存级别等),而且更多的 CPU 使共享问题变得更加复杂。

如果这种不断增长的多处理规模及其相关的微架构复杂性还不够,那么现代处理器还在根据当前的利用率动态调整其处理能力,以尝试为应用程序提供它们所需的资源。英特尔的睿频加速功能在活动内核较少且热条件允许的情况下,会提高处理器的运行频率。相比之下,SPARC T4 处理器会动态地将其核心资源分配给其活动的硬件线程,通过拥有更多不活动的线程来逐步使少数活动线程受益。这两种功能的本质都是通过提高单线程性能来实现异构应用程序。

这种新的格局对您作为开发人员和系统管理员都提出了新的问题:您的工作负载应该创建多少个线程?它们需要什么资源?所有线程都同等重要吗?您应该如何调整共享数据结构的大小?您应该告诉操作系统(或更一般地说,底层)关于您的应用程序的哪些信息?

在多线程应用程序中配置线程

虽然每个逻辑 CPU 一个软件线程的简单经典配方在某些情况下可能仍然有效,但它不再能被随意应用。2 并行应用程序必须知道程序中的哪些部分可能需要系统中不广泛可用的资源。凭借这些知识和对可能的部署平台的一些了解,应用程序或许可以通过将其硬件需求与底层可以提供的资源相匹配来调整自身大小。未能正确执行此操作会导致共享资源争用(因为太多线程争夺这些资源)或可用资源未得到充分利用。

对于同构多线程应用程序(其中所有线程执行相似的任务(因此具有相似的需求)),您可以简单地将可用资源划分为n个切片,具体取决于单个线程需要多少资源。大量使用浮点运算的科学应用程序可能会在系统中每个可用的 FPU(浮点单元)创建一个线程(或者两个或三个,如果它们可以在执行浮点运算部分时轮流使用)。另一方面,对于异构工作负载,为特定线程留出更多资源可能是有利的。例如,在具有单个生产者和各种消费者的生产者/消费者架构中,为生产者提供尽可能多的资源可能会有所帮助,并有助于使消费者尽可能地忙碌。生产者和消费者之间的这种依赖关系是应用程序中争用的主要点,使生产者成为其最关键的组件。

您可能还希望利用对共享关系的了解来利用最新处理器中的动态资源配置功能。换句话说,您可以手动创建条件,使这些功能发挥作用。在这种情况下,目标不仅仅是通过减少争夺某些必要组件的线程数量来防止性能下降;您希望应用程序利用可以从处理器获得的所有性能。在之前的生产者/消费者示例中,如果将生产者放置在专用核心上,并授予其独占访问该组件内所有硬件资源的权限,则应用程序的吞吐量可能会提高。

虚拟化的后果

虚拟化机制是一个令人困惑的因素,因为它们通常隐藏了底层架构的细节及其各种组件的当前利用率水平(例如,模糊了对处理器上物理性能计数器的直接访问)。这可能会阻止应用程序确定可用的物理资源,因此使其无法通过在可用资源之间分配其需求来正确调整自身大小。这也可能阻止应用程序监控自身行为的后果并适应系统利用率的变化。值得庆幸的是,如果应用程序使用自己的机制来评估性能和容量,则可以规避这些限制。例如,应用程序可以在启动期间和/或在运行时定期运行微基准测试,以根据其自身的指标评估系统的性能。然后,它可以使用该信息来适应当前条件。

最终,即使是正确调整大小的并行应用程序仍然依赖操作系统(或底层运行时环境)来提供线程配置机制,以及用于线程放置的适当调度决策。不幸的是,操作系统传统上只提供非常简单的机制来配置特定线程。例如,在 Solaris 中使用处理器集为应用程序中的某些线程提供整个核心(及其其他共享组件)是现场工程师和专业客户使用的一种相当知名的调整方法。进程绑定也用于手动放置进程和线程,以确保所需的行为。然而,这些机制过于静态。它们需要人工干预,并且通常为此目的而言过于昂贵。更可取的解决方案是,在线程变为可运行状态时,更准确地为线程配置它们所需的资源,而无需用户干预,利用开发人员对其应用程序的了解,并减少操作系统或系统管理员所需的工作量(或干扰)。

共享资源的优先级

当前调度优先级的实现和语义可以追溯到单处理器系统是常态的时期。资源非常有限,必须通过允许线程根据其优先级在确定的时间段内运行来在系统中正确分配资源。最近大量处理器的系统的出现从根本上改变了这种情况。鉴于可用资源数量巨大,线程不再仅仅争夺处理器时间,还争夺共享硬件资源。

这种调度模型未能认识到当今处理器的共享方面,从而产生了一些难以解决的性能异常。例如,考虑一个高优先级线程与一组“饥饿”的低优先级线程争夺特定资源的情况。在这种情况下,希望扩展优先级的实现以包括共享资源的优先级。然后,操作系统可以选择将低优先级线程从高优先级线程正在运行的位置移开,或者为高优先级线程找到一个更合适的位置来执行,从而减少资源争用。

这种扩展提供了一种新方法,通过该方法,开发人员和系统管理员可以指定应用程序的哪些组件应该或多或少地进行配置。这是一种动态、不引人注目的机制,它为操作系统提供了必要的信息,使其能够更有效地配置线程,减少共享资源的争用,并利用前面讨论的新硬件功能。此外,新行为可能使那些已经在其应用程序中识别出不同重要性级别的线程的用户受益(这是这项工作的一个重要方面,出于实际原因)。

此外,优先级的其他几个方面也对我们有利。由于提议的“空间”语义将决定分配给线程的资源数量,因此至关重要的是,此机制应仅限于具有适当权限的用户使用——这已经是所有 Unix 操作系统中优先级的标准方面。优先级也可以应用于不同的级别:进程、线程或函数级别,从而允许在非常精细的粒度上进行优化。

负载均衡和优先级

负载均衡是调度中的经典概念。除了实现细节之外,基本思想是在执行单元之间均衡工作,以尝试使系统中利用率均匀分布。这个基本假设是正确的,但传统的负载均衡实现在异构场景中表现不佳,除非调度程序能够识别每个线程的不同需求以及每个线程在应用程序中的重要性。

几年前,Solaris 调度程序进行了扩展,以实现在共享硬件组件之间进行负载均衡,从而减少资源争用。我们发现,仅仅将负载分散到所有逻辑 CPU 上是不够的:还需要在共享性能相关组件的处理器组之间进行负载均衡。为了实现此策略,Solaris 建立了处理器组抽象。它以分层方式识别和表示共享资源,其中表示最共享组件(例如管道到内存)的组位于顶部,而表示最少共享组件(例如执行流水线)的组位于底部。图 1 说明了两种不同处理器的处理器组拓扑:SPARC T4 和 Intel Xeon 处理器,以及每个硬件组件及其包含的 CPU。

每个处理器组都维护其容量和利用率的度量,定义为组中 CPU 和正在运行的线程的数量。调度程序会合并此信息,并在决定将软件线程放置在何处时使用它,优先考虑利用率:容量比率允许线程取得最大进展的组。

性能关键型线程

处理器组抽象和相关的多核、多线程处理器的负载均衡机制成功地减少了拓扑结构每个级别的争用,方法是在系统中的组件之间平均分配负载。然而,仅凭这一点并不能解释异构应用程序或工作负载中每个线程的不同特性和资源需求。

为了解决这个问题,Solaris 最近扩展了其负载均衡机制,以便线程的利用率(或所需资源)概念与其调度优先级成正比。这允许调度程序将低优先级线程从高优先级线程正在运行的位置进行负载均衡,从而自动减少资源争用。使用一些简单的启发式方法,您可以安全地假设,如果高优先级线程有足够的 CPU 利用率来利用现有的硬件资源,那么应该尽可能多地授予其对这些资源的访问权限。

自动识别应用程序的哪些线程或部分应该被分配更高的优先级并非易事。没有单一的特征可以让我们为各种各样的工作负载做出这样的决定——人们可以指出几种情况,其中资源需求差异很大的线程在其应用程序的上下文中被认为是关键的。然而,大多数关键线程或组件都处于异构环境中的依赖关系的最顶端。从应用程序的启动组件到生产者/消费者场景,任何应用程序的其他部分所依赖的组件都可以被认为是关键的,并且应该被分配适当的高优先级。然而,如果不事先了解应用程序架构,这些依赖关系是不容易观察到的。

在 Solaris 11 中,一旦识别出性能关键型组件或线程,开发人员或系统管理员只需将其置于优先级为 60 的固定优先级调度类或任何实时优先级即可。然后,调度程序将根据底层平台人为地夸大其负载,尝试通过允许其以更独占地访问硬件资源来提高其性能。重要的是要注意,此优化是为了利用系统中可用的资源而实现的。换句话说,如果需要系统的所有逻辑 CPU,则不会强制任何单个线程等待单个高优先级线程的利益。

此实现还根据底层硬件架构以不同的方式进行优化。在 sun4v 系统上,调度程序将尝试为性能关键型线程配置共享执行流水线的所有 CPU,以及 x86 系统上共享物理芯片的四分之一的 CPU。这些策略针对其各自平台中已知的争用源和动态资源配置功能进行了优化。所需行为的一个简单示例是在 SPARC T4 系统上拥有一个专用于单个高优先级线程的完整核心,而所有其他低优先级线程共享剩余资源(再次,只要系统中存在足够的空闲资源)。

结论

日益增长的并行性的当代格局需要新的范例。这些范例将影响开发人员和系统管理员在开发新应用程序和系统时的多个层面。有些人专注于硬件、虚拟化和操作系统层面的机制的考虑。应用程序开发人员必须拥有合适的手段来指定其应用程序的关键元素,并与底层系统软件交互,以确保这些元素获得其所需的特殊资源配置。

致谢

我感谢 Eric Saxe、Jonathan Chew 和 Steve Sistare,他们是 Solaris 内核组中更大团队的成员,在本文提出的想法的开发中特别有帮助。

参考文献

1. Cantrill, B., Bonwick, J. 2008. 真实世界的并发性 6 (5).

2. Smaalders, B. 2006. 性能反模式 4 (1).

喜欢还是讨厌?请告诉我们

[email protected]

RAFAEL VANONI POLANCZYK 是 Oracle Solaris 内核组的软件开发人员,他在那里花费大部分时间从事调度程序/分派器子系统的工作。Rafael 住在旧金山,最初来自巴西南部的阿雷格里港,他在那里获得了南里奥格兰德联邦大学 (UFRGS) 的计算机科学学士学位。

© 2012 1542-7730/12/0600 $10.00

acmqueue

最初发表于 Queue vol. 10, no. 6
数字图书馆 中评论本文





更多相关文章

David Collier-Brown - 您对应用程序性能一窍不通
您无需在每次遇到性能或容量规划问题时都进行全面的基准测试。一个简单的测量将提供您系统的瓶颈点:这个示例程序在每个 CPU 每秒处理八个请求后会明显变慢。这通常足以告诉您最重要的事情:您是否会失败。


Peter Ward, Paul Wankadia, Kavita Guliani - 在 Google 改造后端子集
后端子集对于降低成本非常有用,甚至对于在系统限制内运行是必要的。十多年来,Google 使用确定性子集作为其默认后端子集算法,但尽管此算法平衡了每个后端任务的连接数,但确定性子集具有高水平的连接流失。我们在 Google 的目标是设计一种连接流失减少的算法,该算法可以取代确定性子集作为默认后端子集算法。


Noor Mubeen - 工作负载频率缩放定律 - 推导与验证
本文介绍了与每个 DVFS 子系统级别的工作负载利用率缩放相关的方程。建立了频率、利用率和缩放因子(本身随频率变化)之间的关系。这些方程的验证结果证明是棘手的,因为工作负载固有的利用率也在治理样本的粒度上以看似未指定的方式变化。因此,应用了一种称为直方图脊迹的新方法。在将 DVFS 视为构建块时,量化缩放影响至关重要。典型的应用包括 DVFS 管理器和/或其他影响系统利用率、功耗和性能的层。


Theo Schlossnagle - DevOps 世界中的监控
监控看起来可能非常令人难以承受。最重要的是要记住,完美永远不应该是更好的敌人。DevOps 使组织内部能够进行高度迭代的改进。如果您没有监控,请获取一些;获取任何东西。有总比没有好,如果您已经接受了 DevOps,那么您已经注册了随着时间的推移使其变得更好。





© 保留所有权利。

© . All rights reserved.