2.6 建模
建立系统的分析模型有很多用途,特别是对于可扩展性分析:研究当负载或者资源扩展时性能会如何变化。这里的资源可以是硬件,如CPU 核,也可以是软件,如进程或者线程。
除对生产系统的观测(“测量”)和实验性测试(“仿真”)之外,分析建模可以被认为是第三类性能评估方法[Jain 91]。上述三者至少择其二可让性能研究最为透彻:分析建模和仿真,或者仿真和测量。
如果是对一个现有系统做分析,可以从测量开始:归纳负载特征和测量性能。实验性分析,如果系统没有生产环境负载或者要测试的工作负载在生产环境不可见,可以用工作负载仿真做测试。分析建模基于测试和仿真的结果,用于性能预测。
可扩展性分析可以揭示性能由于资源限制停止线性增长的点,即拐点。找到这些点是否存在,若存在,在哪里,这对研究阻碍系统扩展性的性能问题有指导意义,帮助我们在碰到这些问题之前就能将它们修复。
更多内容可参考2.5.10 节和 2.5.18 节。
2.6.1 企业vs.云
虽然建模可以让我们不用实际拥有一个大型的企业系统就可以对其进行仿真,但是大型环境的性能常常是复杂并且难以精确建模的。
利用云计算技术,任意规模的环境都可以短期租用——用于基准测试。不用建立模型来预测性能,工作负载可以在不同尺寸的云上进行特征归纳、仿真和测试。某些发现,如拐点,是一样的,但现在更多地是基于测试数据而非理论模型,在真实的环境中测试,你会发现一些制约性能的点并未收纳在你的模型里。
2.6.2 可视化识别
当通过实验收集到了足够多的数据结果,就可以把它们绘制成性能随规模变化的曲线,这样的曲线往往可以揭示一定的规律。
图2.15 显示了某一应用程序随着线程数增加而出现的吞吐量变化。从图中可以看出,在8个线程处像是存在一个拐点,此处曲线的斜度发生了变化。现在可以研究这个点,比如查看该点附近的应用程序和系统的各种配置信息。
图2.15 可扩展性测试结果
上例是一个八核系统,每一个核有两个硬件线程。为了进一步确定这与CPU 核数的关系,需要研究在少于八核和多于八核时CPU 产生的影响(例如,CPI,参见第6章)。或者,在一个不同核数的系统上重复相同的扩展性测试,验证拐点发生如预期般的变动。
下面是一系列性能扩展性曲线,没有严格的模型,但用视觉可以识别出各种类型,详见图2.16。
图2.16 性能扩展性曲线
对于每一条曲线,X 轴是扩展的维度,Y 轴是相应的性能(吞吐量、每秒事务数,等等)。曲线的类型如下。
● 线性扩展:性能随着资源的扩展成比例地增加。这种情况并非永久持续,但这可能是其他扩展情况的早期阶段。
● 竞争:架构的某些组件是共享的,而且只能串行使用,对这些共享资源的竞争会减少扩展的效益。
● 一致性:由于要维持数据的一致性,传播数据变化的代价会超过扩展带来的好处。
● 拐点:某个因素碰到了扩展的制约点,从而改变了扩展曲线。
● 扩展上限:到达了一个硬性的极限。该极限可能是设备瓶颈,诸如总线或互联器件到了吞吐量的最大值,或者是一个软件设置的限制(系统资源控制)。
虽然可视化识别曲线很简单且有效,但通过数学模型的方法你能更多地了解系统的扩展性。模型用意想不到的方式从数据中衍生出来,这对于研究很有用处:要么是模型里呈现的问题与你对系统的理解不一致,要么是这问题在系统扩展中是真实存在的。下一节我们会介绍Amdahl扩展定律、通用扩展定律和排队理论。
2.6.3 Amdahl 扩展定律
由计算机架构师Gene Amdahl [Amdahl 67]的名字命名,该定律对系统的扩展性进行了建模,所考虑的是串行构成的不能并行执行的工作负载。这个定律可以用于CPU、线程、工作负载等更多事物的扩展性研究。
Amdahl 扩展定律认为早期的扩展特性是竞争,主要是对串行的资源或工作负载的竞争。可以描述成为[Gunther 97]:
容量是C(N),N 是扩展的维度,如CPU 数目或用户负载。系数α(其中0<= α <= 1)代表着串行的程度,即偏离线性扩展的程度。Amdahl 扩展定律的应用步骤如下:
1.不论是观测现有系统,还是实验性地使用微基准测试,或者用负载生成器,收集N 范围内的数据。
2.执行回归分析来判断Amdahl 系数(α)的值,可以用统计软件做这件事情,如gnuplot或R。
3.将结果呈现出来用于分析。收集的数据点可以和预测扩展性的模型函数画在一起,看看数据和模型的差别。这件事也可以用gnuplot 或R 来完成。
下面是Amdahl 扩展定律回归分析的例子,看看这一步骤是怎样做到的。
用R 语言处理这个所需要的代码量也大致相同,使用nls()函数,利用非线性最小二乘拟合法来计算系数,然后用得到的系数绘图。详见本章最后性能扩展模型工具集参考,其中附有gnuplot 和R 语言的完整代码[2]。
在后面有一个Amdahl 扩展定律函数的例子。
2.6.4 通用扩展定律
通用扩展定律(Universal Scalability Law,USL),之前被称为超串行模型[Gunther 97],由Neil Gunther 博士开发并引入了一个系数处理一致性延时。这个定律用于描述一致性扩展的曲线,竞争的影响也包含在内。
USL 定义为:
C(N)、N 和α 与Amdahl 扩展定律是一致的。β 是一致性系数。当β = 0 时,该定律就变成了Amdahl 扩展定律。
USL 和Amdahl 扩展定律的示例曲线可见图2.17。
输入的数据集的方差较大,给扩展曲线的形状判断带来一定的困难。输入模型的起始一组的十个数据点用圆圈做标记,额外一组十个数据点用叉形做标记,这样能检验模型对于现实的预测能力。
关于USL 分析的更多内容,可参考[Gunther 97]和[Gunther 07]。
2.6.5 排队理论
排队理论是用数学方法研究带有队列的系统,提供了对队列长度、等待时间(延时)、和使用率(基于时间)的分析方法。在计算领域的许多组件,无论硬件还是软件,都能建模成为队列系统。多条队列系统的模型叫做队列网络。
图2.17 扩展性模型
本节会简单阐述排队理论的作用,还会给出一个示例以助理解。如有需要,这一领域有着大量的研究,可以参考其他文档([Jain 91]、[Gunther 97])。
排队理论是建立在数学和统计的很多领域之上的,包括概率分布、随机过程、Erlang 的C公式(是Agner Krarup Erlang 创立的排队理论)和Little's 定律。Little's 定律可以表述为:
系统请求的平均数目L 是由平均到达率λ 乘以平均服务时间W 得到的。利用排队系统可以回答各种各样的问题,也包括下面这些问题:
● 如果负载增加一倍,平均响应时间会怎样?
● 增加一个处理器会对平均响应时间有什么影响?
● 当负载增加一倍时,系统的90%响应时间能在100ms 以下吗?
除了响应时间之外,排队理论还研究其他因素,包括使用率、队列长度,以及系统内的任务数目。
一个简单的排队系统模型见图2.18。
图中有一个单点的服务中心在处理队列里的任务。排队系统可以有多个服务中心并行地处理工作。在排队理论中,服务中心通常称为服务器。
图2.18 排队模型
排队系统能用以下三个要素进行归纳。
● 到达过程:描述的是请求到达排队系统的间隔时间,这个时间间隔可能是随机的、固定的,或者是一个过程,如泊松过程(到达时间的指数分布)。
● 服务时间分布:描述的是服务中心的服务时间,可以是确定性分布、指数型分布,或者其他的分布类型。
● 服务中心数目:一个或者多个。
这些要素可以用Kendall 标记法表示。
Kendall 标记法
该标记法为每一个属性指定一个符号,格式如下:
到达过程A、服务时间分布S,以及服务中心数目m。Kendall 标记法还有扩展格式以囊括更多的要素:系统中缓冲数目、任务数目上限和服务规则。通常研究的排队系统如下。
● M/M/1:马尔科夫到达(指数分布到达),马尔科夫服务时间(指数分布),一个服务中心。
● M/M/c:和M/M/1 一样,但是服务中心有多个。
● M/G/1:马尔科夫到达,服务时间是一般分布,一个服务中心。
● M/D/1:马尔科夫到达,确定性的服务时间(固定时间),一个服务中心。
M/G/1 模型通常用于研究旋转的物理硬盘性能。
M/D/1 和60%使用率
作为排队理论的一个简单示例,假定磁盘响应工作负载的时间是固定的(这是一个简化)。响应的模型是M/D/1。
现在的问题是:随着使用率的增加,磁盘的响应时间是如何变化的?依据排队理论,M/D/1 的响应时间可以计算如下:
此处的响应时间r,由服务时间s 和使用率ρ 决定。
对于1ms 的服务时间,使用率为0%~100%,响应时间和使用率的关系如图2.19 所示。
使用率超过60%,平均响应时间会变成两倍。在80%时,平均响应时间变成了三倍。磁盘I/O 延时通常是应用程序的资源限制,两倍或更多的平均延时增加会给应用程序带来显著的负面影响。排队系统内的请求是不能被打断的(通常而言),必须等到轮到自己,这就是磁盘使用率在达到100%之前就会变成的问题的原因。CPU 资源与之不同,更高优先级的任务是可以抢占CPU 的。
图2.19 回答了之前的问题——如果负载增倍,平均响应时间会如何变化——此时使用率和负载是相关的。
这是一个简单的模型,在某些方面它显示出最佳的情况。服务时间的变化使得平均响应时间变高(例如,使用M/G/1 或M/M/1)。有一部分的响应时间分布在图2.19 中没有显示出来,例如90%~99%的性能下降要比使用率60%时快得多。
图2.19 M/D/1 模型平均响应时间随使用率变化的曲线
正如之前Amdahl 扩展定律的gnuplot 的例子一样,用实际的代码展示会更直观,看看涉及因素。这次我们用的是R 统计软件[3]。
之前说过的M/D/1 的等式被传到了plot()函数里。这段代码中的大部分是在定义图的边界、线条的属性,以及轴上的标签。