Top-down性能分析模型

长久以来,我们对计算机资源的理解一直都停留在cpu,内存容量,IO这类的大粒度的划分之上。一个简单的top或者vmstat命令就很方便的帮助我们得到某某计算机需要升级CPU或者加内存这类的结论,经验告诉我们,这一切似乎没有什么错。

如果你是一个发烧级别的PC游戏爱好者,自己配过计算机,或者玩过几个当下“硬件杀手”级别的游戏,你也许会对资源亲和度有个比较粗暴的了解——xx游戏专门针对xx硬件进行了优化;花同样的钱,为了玩爽xx游戏,你应该买个支持xx的卡……这类的话题其实反应了一个问题:应用程序对硬件之间是存在亲和性的!而从这个角度出发,经验中那种盲目的增加硬件的方式并不是一个经济的做法。对于一个台式机来说由于其价格相对不高,且用途非常广泛,一个“亲和力”最多只能在买电脑的时候提供参考。而到了相对功能更为单一的服务器领域,“亲和力”就变得很重要。比如你的应用是redis,第一反应就是找台大内存,高频的双核CPU,这是很合理的;而搞一个几十核心,只有几个G内存的机器那就是浪费钱的举动。何况严谨点的机房数据还有一个“性能功耗比”的指标。

毕竟redis是一个简单到甚至不需要做实验的例子,对于不同的黑盒应用(这里指的是无法大幅修改代码逻辑的应用)如何去找到对应亲和度更优的硬件,那就是另一命题了。

Topdown性能分析简介

首先,在系统级别上,我们可以直接通过iostat/vmstat/sar来判断IO或者network是否存在瓶颈,这也许就是传统的CPU/内存/IO三大块中比较容易辨别的IO瓶颈问题,遇上IO瓶颈大部分操作就是升级到更快的SSD硬盘或者更快的网卡。对于内存瓶颈来说,事实上的内存瓶颈有内存容量瓶颈和内存带宽瓶颈两大部分。而当下的x86架构通过多通道内存的方式将容量瓶颈和带宽瓶颈通过同一种简单粗暴加内存条的方式给统一到了一种处理方法上。唯独只有CPU瓶颈的表现成为了最为复杂的过程:轻指令还是重指令?换CPU还是升级版本以支持新功能?高频率还是多核心?大缓存还是大内存?……

前一段时间,读过一篇Ahmand Yasin的IEEE论文“A top-down method for performance analysis and counter architercture”。这篇论文通过称为top-down模型的方法(TMAM),将细粒度的CPU资源和指令操作联系到了一起,成为了一个兼具通用型和可操作型的应用程序评价系统。最终,任何一个应用程序会有4种不同的倾向性。而通过不同倾向性以及子类的权重我们可以很直观的对目前系统特别是CPU的瓶颈作出客观的评价。

  • Frontend bound(前端依赖)首先需要注意的是这里的前端并不是指UI的前端,这里的前端指的是通过CPU预加载、乱序执行技术获得的额外性能。
  • Backend bound(后端依赖)同样不同于其他“后端”的定义,这里指的是传统的CPU负责处理事务的能力。由于这一个部分相对其他部分来说,受程序指令的影响更为突出,这一块又划分出了两个分类。core bound(核心依赖)意味着系统将会更多的依赖于微指令的处理能力。memory bound(存储依赖)我这里不把memory翻译成内存的原因在于这里的memory包含了CPU L1~L3缓存的能力和传统的内存性能。
  • Bad speculation(错误的预测)这一部分指的是由于CPU乱序执行预测错误导致额外的系统开销。
  • Retiring(拆卸)字面理解是退休的意思,事实上这里指的是等待指令切换,模块重新初始化的开销。

由于以上所有的资源都是基于处理器微指令级别的,对于操作系统级别的CPU利用率之类的指标自然也就派不上用场。这里通常用CPI(cycle pre instructuon平均每指令花费的时钟周期数)或者IPC(Instruction pre cycle平均每时钟周期完成的指令数)。从表达上看这就是互为倒数的同一个指标的描述,而这个数字是经过了CPU的时钟周期的校准,事实上是跟CPU频率脱了钩。个人习惯上会采用越小越好的CPI,因为系统中缩写为IPC的指标实在太容易混淆?。PS: 尽管当下的CPU理论CPI已经可以接近甚至突破0.33。但低于1.0已经很难说有什么经济性可言,而低于0.8的CPI事实上已无优化的必要。

在实际过程中,一旦系统能够通过应用程序级别的压力测试达到或者接近CPI的最低,我们可以认为系统由于某种原因出现了性能瓶颈。这时就可以通过性能调试工具(比如:vtune)很容易的获得一系列上面描述的4个指标的组成部分的百分比数,将各个指标不断的汇总为4个倾向性之后再对系统的性能瓶颈进行评估,这就是top-down的含义。除此以外的数据汇总过程相信根本上就是一个算数问题。

一旦我们得到了4个倾向性占比之后,在黑盒层面上我们已经可以知道该应用程序的亲和性了。剩下的几乎就是套路了。

  • 前端依赖型:很少出现,如果你发现了,大多都跟程序的微指令指令复杂导致的指令不能及时加载相关。除了等待CPU升级之外似乎也没有更好的方法优化。
  • 错误的预测型:多半是由于系统采用的编译器和编译配置不佳,尝试从编译器方向解决问题。“换版本”也是解决方法之一。
  • 拆卸:对不起,对于黑盒应用来说无解。这已经是系统最优结果了。
  • 后端核心依赖型:升级CPU频率,使用更多重指令优化,尽可能的提升子项中port3+的利用率。此外如果程序支持,只要过程中此项占比没有太大变化,采用更多的核心往往也会有非常线性的性能提升。
  • 后端存储依赖型:根据子项中不同的cache level依赖度,找出该应用程序更多的倾向于哪个级别的cache,如果可能就找对应cache更大的产品或者针对cache的bound尝试对上一级别的cache做cache block优化。如果是内存的话就可以通过升级内存带宽或者更高频、更低潜伏期的内存来获得性能提升。

需要注意的是,以上的所谓套路都是在理论环境下基于一个均一化程度很高的应用程序得出的结论。在不同的硬件平台、不同的业务压力和业务状态下,倾向性也会有显著的差异。而top-down模型的重点在于它还可以通过一个在硬件或者软件配置的连续变化过程中倾向性占比的变化反应来预估应用程序的弹性、最大容量和最优容量。

总结下我自己对topdown分析方式的理解:topdown分析实质上是对应用程序占据的所有CPU running time(CPU运行时间)的分类汇总,从这些数据上得到应用程序到底在哪些项目中耗费了较多的时间,从而对其进行有针对性的优化。

Top-down性能分析实战

在同一个系统上进行SPECCPU2006浮点运算性能测试,发现在系统启用了NUMA之后,以410.bwaves(+25%)为代表的大多数测试项目都有了性能提升;但以459.GemsFDTD为代表,有部分测试项目的性能下降了约20%;此外,还有以454.calculix为代表,性能的变化在5%以内,基本可以视作测试偏差。

对于NUMA特性不甚了解的朋友不妨移步这里补课。

问题,在只做“黑盒分析”的前提下,确认为什么同样的浮点测试会有如此不同的性能变化。

首先是对系统在两种不同的NUMA设定基础上做一个top-down分析(这里直接一步到位,对于backend bound直接细分到了memory bound和core bound):

很明显的是,对于GemsFDTD和bwaves测试来说,他们都属于memory bound类型的应用,简而言之,就是这类的应用对于内存、CPU缓存方面的变化有着非常敏感的反馈。而NUMA设定本质上属于内存访问方式的变化,对这类应用影响自然很大。而属于retiring类型的calculix来说,内存的变化自然不会引起它过多的反馈。

问题来了,同属memory bound的GemsFDTD和bwaves,为什么会有性能提升和性能下降两种截然不同的反馈。继续对memory bound分类进行细分。

很明显的一点,对于橙色部分“ext memory latency”,GemsFDTD占据了超过60%而bwaves只占有20%左右。也就是说GemsFDTD比bwaves对于内存的延时更加敏感。回头记录了两种状态下GemsFDTD的内存延时相关的指标“RPQ latency”(字面翻译:内存读取队列返回时间)发现NUMA功能的开启会导致读取RPQ latency急速上升至原值2倍左右。这个过程直接导致对于延时极度敏感的GemsFDTD出现大幅的性能下降。

  1. “bwaves”受内存延时影响小,GemsFDTD受延时影响大。
  2. 开启NUMA,RPQ latency增加,直接导致内存延时增加。

既然分析出了结果,不妨做个回溯实验。不过当下我们根本没有任何手段可以直接操作内存延时。只能简单化的将所有的内存访问都转移到远程的NUMA节点,强制增加内存的访问延时。

结果是bwave执行时间超出了正常水平的164%,而GemsFDTD超出正常水平221%。GemsFDTD对于内存时延的增加反馈更加剧烈。回溯结果与预测结果一致!

推荐阅读:
继续在NUMA和性能差异的路上
首先列出本站之前相关的几篇帖子
熟悉Linux内核内存管理机制

发表评论

电子邮件地址不会被公开。 必填项已用*标注

请补全下列算式: *

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据