在多核硬件上评测应用性能

简介

从表面上看,在多核环境下进行应用性能指标评测与在单核环境下进行非常相似。但是在实际中前者往往更为复杂,尤其是对多核系统上性能指标评测结果进行认真分析,能够引导您发现需要解决的性能问题,从而取得理想的性能结果。

本文主要面向在多核系统上进行应用性能指标评测的工程师,以及使用性能指标评测结果调整线程应用性能的工程师。对于从事评测分析工作的人员而言,本文亦是一个好的背景材料。但是本文不能用作根据性能指标评测结果制定采购决策。阅读本文后,读者应该能够基本了解如何收集有意义的性能指标评测数据,以及如何调整多核系统上的应用。

先决条件

在对多核系统上的应用进行性能指标评测之前,我们需要具备一些基本条件。首先,需要确保我们了解被测应用的特性。其次,需要确保拥有特定的工作负载,以便准确地获得我们需要收集的性能数据。

定义

谈论多核系统时,我们需要先定义以下术语:

  • 并发。并发是一种计算方式,指多项任务同时处于活跃状态(但不一定同时执行)。并发在现代操作系统中可用于隐藏延迟,以及与冗余结合提供高可用性等。
  • 线程。线程是一种操作系统结构,主要支持单一流程同时执行多组指令。应用开发人员通常需要在应用中明确编码说明线程的用法。
  • 并行计算。并行计算是指在多个处理单元上执行多条线程,以便减少程序运行时间。并行计算也是多核系统的主要优势之一。
  • 吞吐量并行计算。此类并行计算旨在改进工作集或任务集的整体性能。通常使用监控一台或多台机器的软件来实现,一旦其中任何一台机器拥有空闲资源,就会从工作队列向这些机器分配工作。这种软件允许集合中的多项工作并行执行,通常比串行执行(一个接一个)花费更少的时间。
  • 转向(时延敏感型)并行计算。进行此类并行计算的目的,是支持并行完成一些操作,以减少单一任务或工作的运行时间。
  • 串行代码。串行代码指仅执行单线程的部分应用。这部分应用可能是指那些由于算法限制而不能被线程化的多线程应用代码。下文中我们将看到,随着内核数量的增加,串行执行最终会限制多线程应用的扩充。

需要考虑的问题

在试图对应用进行线程指标评测之前,我们还有一些重要的问题要为您解答:

您的应用使用线程吗?

这是一个非常重要的问题。目前,大部分应用都不支持线程化。如果应用不支持线程化,那么在主频、CPU 架构、内存子系统等所有其它条件相同的情况下,无论系统中有多少内核,该应用中单一例程的性能都无法得到提升。

如此一来,多核系统的优势便不是提高单一应用性能,而是增加能够同时运行于系统中、且不会发生冲突的串行应用数量。应用可以是被测应用的其它副本,也可能是病毒扫描、音乐回放、打印等其它完全不同的流程——换句话说,这就是一个“吞吐率并行计算”的范例。这种情况下的性能分析通常会尝试确定一个串行任务与另一个串行任务的操作干扰情况。

因此,理论上讲,如果应用支持多线程(所有其它条件均相同),那么随着系统中内核数量的增加,应用就能在提升性能的同时缩短运行时间(这就是“转向并行计算”理论)。应用性能的实际提升幅度会受到多种因素的影响,如系统运行的其它应用、应用执行线程的效率、基准工作负载所使用的线程化代码比例,以及硬件平台的属性等。

下文将假设您的应用支持多线程。

您的应用使用多少条线程?

如果您的应用支持多线程,您就需要定期检查并行性的实现情况。这里有一个重要的问题值得您思考:该应用是否旨在利用多核系统中的所有内核,或者是否对其创建的线程数量做出了一些必要或人为的限制。明确代码中是否存在上述的限制,不但能够帮助您预测应用在多核系统容纳内核数量增多时的扩充能力,还可以支持您在应用中识别线程实现可能需要检验的区域。

您已经了解了单核平台的性能,那么您知道拥有 N 枚内核的系统拥有怎样的性能吗?

这是一个关键问题。以下性能指标评测流程将帮助您回答这个问题,但是既然已经了解了应用架构和阿姆达尔定律等粗略预测机制的用途,您便应尝试提前进行预测。(如下所述,随着内核数量的增加,阿姆达尔定律可基于应用中串行数量和并行工作预测出并行应用性能变化的基本情况。)调查的重点应落在测出的并行应用性能和预测性能之间的差距上。此外,您还需进行应用分析,以找出性能扩充能力随内核数量增加而下降的真正原因。

在拥有 2N 枚内核的系统上,何时可以完成工作?

此问题与上一问题类似,但有一点重要区别。如果您的客户正在使用基于 N 枚内核的系统,那么您应该使用现有的数据对具有两倍内核数量的系统应用进行预测和实际测试。这项实验的目的,是支持您赶在您的客户升级至带有 2N 枚内核的全新硬件之前,确定并解决应用中出现的所有扩充问题。最重要的是,在您的客户从采用 N 枚内核升级到采用 2N 枚内核的系统时,您绝不希望看到应用性能呈现下降趋势。

因此,如有可能的话,您应该经常对内核数量多于常规客户配置的系统上的多线程应用进行性能指标评测。如下所述,您需要对那些拥有 1、2、4、……2N 枚活跃内核的应用性能进行测试,以便获得准确的应用扩充信息。

应用需要使用特定的系统资源,还是可适应其所运行的系统?

让我们举个例子来回答这个问题。如果您的应用已经实现了线程化,您的开发团队很可能会使用一种名为“处理器亲和(processor affinity)”的方法,以确保应用的每条线程总是运行于特定内核之上,而不是根据操作系统的安排运行于任意空闲内核上。这样做的理由之一,就是试图确保对于线程而言十分重要的数据能够保存在指定内核的高速缓存中,以实现性能的提升。为何提起这个话题?因为您需要确定您的开发人员已经使应用具有了足够的适应性,能够适应某个指定硬件平台上的内核数量、处理器架构等情况。错误的假设会导致意料之外的性能指标评测结果,甚至可能造成性能下降。

基准工作负载

任何性能测量取得成功的关键,都在于在评测过程中使用合适的工作负载。这些工作负载必须具有:

  1. 代表性。请确保您的工作负载包括能够反映客户对应用的实际使用情况的数据集(例如:如果您的客户在遇到特殊任务时通常运行 100 MB 的数据,那么就不要创建仅用 5 MB 数据集即可测试相同任务的工作负载)。面对“哪一部分代码对应用的整体性能最重要”这一问题时,使用没有代表性的数据集(无论在数量上还是在内容上)进行测试会导致您得出错误的结论。
  2. 全面性。工作负载必须涉及到客户通常使用的所有主要应用使用模式和功能区域。在所有对客户而言至关重要的领域中设置用于评测性能的基准工作负载非常重要,尤其是您开始对寄希望能提升性能的变动效果进行测量时(您不希望缺失的代码变化会在提升一种工作负载速度的同时,降低另一个工作负载的速度)。
  3. 可重复性。如果工作负载不可重复,那么性能指标评测将变得毫无意义。可重复性意味着在相同系统上反复运行时,工作负载每次运行的耗时相同,且执行相同应用功能的方式也是相同的。尤其面向多层应用时,此举颇具挑战性,因为它可能在工作负载每次运行时将一个数据库推回到指定状态,或者要求带有相同数据的联网客户机一次又一次地驱动服务器应用。因此,您可能需要使用自动化软件,以便不断地对复杂系统进行性能指标评测。

对于单元测试,您的基准工作负载只需具有可重复性。对于任何类型的整体性能测试、分析和调整,它们还需具有代表性和全面性。无论硬件(多核、单核)或应用(线程、多层、串行)的属性如何,这些要求对任何性能指标评测活动均适用。

基准工作负载应可生成一些性能标准(FLOP、MIP 等)。简而言之,您应该使用一些自动化机制,自始至终地对其运行时间进行准确测量。

性能指标评测

在多核系统上运行性能指标评测应用时,您不仅要注意应用的整体性能,还应关注应用性能会随着内核数量的增加发生怎样的变化。后者将帮助您预测在类似多核系统上的性能,并帮助您识别可能需要调整性能的区域。在多核系统的多线程内核上实现最佳性能,比在单核系统上对串行应用进行简单性能优化要复杂得多。此外,了解应用性能如何随着内核数量的变化而改变,还能够帮助您更准确地比较两个不同系统的性能。

串行基准
如果您的应用支持线程化,那么在应用运行于单核系统上时运行基准工作负载就显得很有价值。如有可能,您还可在多核系统上运行基准工作负载,并且仅引导操作系统使用一个内核。在 Windows* 中,使用 BOOT.INI 文件中的“/NUMPROC=1”重新启动系统即可完成;在 Linux* 中则使用“maxcpus=1”bootparam。上述操作均要求您的操作系统在最初便已安装 SMP 核心。

您可以利用这些单核基准的数据计算应用性能如何随着内核数量的增加而变化,或者确定并解决应用中的并行性能问题。

收集扩充数据
确定单核基准之后,请在测试系统上重新运行基准工作负载,从而使活跃内核数量呈双倍增加(2、4、8、……),直至达到系统最大内核数量为止(理想状态是典型客户配置的 2 倍)。最后生成的性能数据将表明,通过部署并行计算,应用在平台上缩短了多少运行时间,以及应用运行时间会随着内核数量的增加发生怎样的改变。

收集操作系统和平台性能数据
对多核系统上的应用进行性能指标评测时,另一个需要收集的重要数据集便是操作系统性能数据。使用您最得心应手的专用操作系统工具,收集各种系统性能标准,如整体处理器占用率、用户级运行时间、系统级/核心运行时间、每秒中断次数、每秒环境切换次数、I/O 级别,以及内存流量等。请在每次运行性能指标评测期间,尽可能多地收集上述详细信息。收集这些数据的目的,是帮助您了解您到底有多少应用正在并行运行,以及您的系统会因应用对系统资源越来越高的使用率而承受何种压力、达到何种极限(如果存在的话)。

如果您更改了应用代码或性能指标评测,请重新运行
如果您对应用代码或基准工作负载进行了更改,那么重新收集性能数据(特别是单核基准)便显得非常重要,尤其是在应用正使用所有可用内核的情况下。您所做的更改可能改变应用的串行性能,也可能在使用系统所有的内核时缩短应用的运行时间(希望如此)。如果这样,您就需要重新收集数据,以便对更改所造成的应用扩充能力变化做出正确的结论。

分析
凭借现在掌握的充足数据,您可以了解应用如何随处理器内核数量的增加而扩充、它在单核系统上运行时间的变化程度,以及各种操作系统的性能数据(提示您系统在多线程基准工作负载下的整体执行情况)。

警告:此时,您可能急于将您的数据与其它系统上收集的数据进行比较。但是,从技术层面上说,只有当两个系统间仅存在一个变量时(如主频、已安装的内存数量等),这种比较才具有实际意义。特别是对于多核系统上的多线程代码来说,由于两个平台之间巨大的差异,为何一个系统“快于”另一个系统几乎成了一个无法解答的问题……如果您希望开发人员提升速度较慢系统的性能,这确实是一个问题。

预测基准工作负载的串行部分
您需要使用收集到的性能数据,查看随执行线程和内核数量的增加所产生的较差或意外的扩充指示。为此,请使用您的操作系统性能数据来大致预测每个基准工作负载需要完成的串行(非线程)工作量。通常,只有 2 条线程运行在 2 枚内核上时,您就能从收集到的性能数据中轻松得到近似的数据。

 

图 1

 

在图 1 中,您可以看到约有 75% 的运行时间均用于串行代码,而其余 25% 的运行时间用于(较差的)并行执行。如果所有工作都由单内核上的单线程完成,那么基准工作负载需要运行多长时间?在串行(非并发)编码上实际花费了多少运行时间?

答案显而易见。串行运行时间将保持稳定,而理想情况下,并行工作仍需要多一倍的时间。

由此,总体运行时间为:0.75 + (0.25*2) = 1.25
串行部分运行时间 = 0.75 / 1.25 = 0.6 或约 60%
“并行”部分运行时间 = (0.25*2) / 1.25 = 0.4 或约 40%

计算 2..N 枚内核的理想扩充能力
此处的关键是,当内核数量(相应的活跃线程数量)增加时,串行代码所需的运行时间保持不变,而并发代码所需的运行时间就是串行运行代码所需要的时间除以线程的数量 N (理想情况)。这样,在我们的举例中,拥有 N 枚内核和 N 条线程的系统总体运行时间约为:
总体运行时间 = 0.75 + (0.25*2)/N = 0.75 + 0.5/N

或者更为常见的是:

总体运行时间 = “串行运行时间” + (“串行运行代码时处于并行区域的时间”/ N)

这基本上是对阿姆达尔定律的重述,定律内容为:

Tpara = {(1-P) + P/N}Tserial
N = 处理器的数量
P = 在代码并发区域所花费的时间比例
1-P = 在代码串行区域所花费的时间
Tpara = 并行运行时间
Tserial = 串行运行时间

请注意,阿姆达尔定律过于乐观:它忽略了消耗,假设计算资源能够在线程和内核之间平均分配,并假设在并行处理代码时处理器占用率能够达到100% (在上面的举例中,我们可以清楚地了解事实并非如此)。

通过以下公式,我们可以预测在指定数量的线程及相等数量的内核上,工作负载的加速度(也称为扩充因数):

加速度 = Tserial / Tpara

 

从这些等式中,您可以得出图 2 所示的内容:当您增加线程和内核的数量时,具有不同等级串行代码的应用加速度曲线:

 

图 2

 

您可以看到,在拥有 2 枚以上内核的系统上,应用必须拥有非常少量的串行代码才能在系统上很好地进行扩充。

将预测结果与实际结果进行比较

收集完所有的性能指标评测数据,并利用性能数据预测了 N 颗处理器的应用运行时间(和加速度)后,现在我们将 2 项结果进行比较,看看能否有意外的结果出现。图 3 显示了在采用几项不同工作负载(具有 2 枚和 4 枚活跃内核)的应用上所测量到的加速度。

 

图 3

 

此图(根据您的数据绘制而成——如图 3 所示范例)非常有用,为您揭示了很多信息:

只看测量的数据,您便会发现哪些基准工作负载在 4 核系统上的多线程性能不佳,哪些执行得很好。对于任何一种极端情况的出现,我们都应进行更详细的检查,以找出其中的原因。

看看我们根据阿姆达尔定律得出的加速度预测(绿色柱——非常理想化,也非常粗略),我们可以识别预测与实际加速度正在发生意外情况的工作负载。如果您希望改进多核系统上的应用的性能,那么您应对这些工作负载进行进一步的分析和性能优化。

在上面的举例中,您会注意到工作负载 1 和 2 的性能(扩充因数)随着线程数量(和相应的内核数量)的增加而提高,而预测和测量的 4 核性能数量也非常吻合。对于工作负载 11,预测的情况是在 4 核上运行时,工作负载性能应几乎没有提升(此工作负载几乎没有执行并行区域,无论您的多核系统中有多少内核,它几乎都全部运行于单线程)。但是我们发现,相比运行于双核的情况,应用运行于 4 核的实际速度有所下降(图 3 中的黄色箭头)。这应该就是产生一些问题的根源所在,但是根据对应用用户运行类似工作负载的频率预测,我们无需花时间去调查和解决这些问题。

然而,工作负载 5 和 12 却显示了两种最令人担心的情况:预测与现实严重不符(图 3 中的红色箭头)。在工作负载 5 中,工作负载性能在从 1 条线程(扩充因数 1)到 2 条线程时产生了巨大的加速度,但是从 2 条线程到 4 条线程时却保持稳定。这与我们根据阿姆达尔定律进行的预测相悖;根据我们预测,工作负载在四核系统上应具有更高的性能。工作负载 12 情况很糟糕,因为该工作负载在四核系统上的性能竟然远远低于在单核系统上的性能!这也不符合我们根据阿姆达尔定律进行的预测。

您可以看到,图形的数值(如图 3 中的数值)可帮助您组织大量数据,支持您优先进行更多的分析和调整工作。此外,它还可以使您全面了解应用在多核系统上的性能。这就是要在测量多核系统的应用性能之前,确定一套有代表性的、可重复的、全面的基准工作负载的原因所在。

调试

识别基准工作负载反映出的性能问题(如上文的图 3 所示)原因,过程非常复杂,在此不再详述。为此,我们可以使用操作系统提供的简单系统监控工具,来了解应用如何在多核系统上运行。

然而,纠正多线程代码中的性能问题需要更复杂的工具和诸多技巧。使用传统的调试器很难找到多线程代码中的性能问题,因为:

  • 实际上没有任何东西遭到破坏,因此即使您设法在引发性能问题的代码上设定一个突破点,也很难了解造成问题的原因。
  • 在多线程代码上运行调试器(或者任何重量级性能分析工具)可完全改变代码的行为。尽管如此,通常我们仍然需要这样做。

从上文您可以看出,在多线程应用中实现更多性能的关键,在于最大限度减少每个基准工作负载执行期间串行代码的数量(如果可能)。总体来说,我们可以执行以下步骤:

  • 在多核系统上运行您希望对其性能进行改进的基准工作负载,同时运行英特尔® VTune™ 性能分析器等性能分析工具。此工具将告诉您在运行该基准工作负载期间,哪些部分的代码正在运行。
  • 将该工作负载的性能分析数据与您的应用架构知识联系起来。这可能是您看到一些代码能够串行却不能并行运行的合理原因。如果您希望在多核系统上获得最佳应用性能,那么可能需要对这些决策进行重新评估。
  • 如果性能分析显示,当您的大部分应用都以并行方式运行,却依然无法得到很好的扩充时,那么应用性能很可能受到了一些同步问题的限制。同步问题可使您的应用实现“串行化”,就像编写串行算法一样有效。在这种情况下,您需要使用专业工具(如英特尔® 线程调节器)来确定多线程代码中的性能问题。
  • 一旦了解了阻碍多核系统上应用性能的同步问题,您将需要再次返回应用架构中,尝试找出最大限度减少同步问题的方法。
  • 进行必要的改动,在使用一枚内核和使用全部内核(线程数与系统上的内核数相同)的情况下再次重新运行应用,看看整体性能和扩充能力是否有所提升。
  • 请注意,如果您更改了其它基准工作负载所使用的代码,您将需要重新收集这些工作负载的数据——您需要确保,为改进某项基准工作负载所作的任何修改都没有影响到其它工作负载的性能。

可能正如您所料,性能调试流程具有迭代性。调试流程在单核系统上的串行应用与多核系统上的多线程应用之间的区别,在于您需要更加细心,因为线程增加了更多需要优化的方式,并引入了新的方式(其中用心良苦的优化工作可能会适得其反)。

结论

使用基准工作负载测量多核系统上的性能,并非完全不同于您在单核系统上测量性能的方法。您需要确定哪些性能指标评测标准是重要的,然后使用基准工作负载来测试应用,从而提供有意义的性能指标评测结果。

在多核系统上进行性能指标评测也推出了“加速度”这一全新概念,我们可将其归结为“询问应用的性能如何随着内核数量的增加而变化”,以及“您认为这些结果是否可以接受”。总体来说,您希望应用性能随着内核数量的增加而提高,并且您需要了解(并解决)性能水平下降的所有问题。

* 文中涉及的其它名称及商标属于各自所有者资产。

 

有关编译器优化的更完整信息,请参阅优化通知
类别: