1. 本转载文章对设计者在性能估算上非常有参考价值;
2. 负责技术运营、运维的同学前期早参与开发设计是非常有必要的,特别是一些经验丰富的同学在线上大流量下见多识广,对一些性能、瓶颈值比较熟悉;
3. 开发的系统适合运营生产环境是一个上线的准入条件,否则运营成本巨大,即使上线了也是问题服务,不如在上线前就把关解决掉。
给定一个问题,往往会有多种设计方案,而方案评估的一个重要指标就是性能,如何在系统设计时估算而不是在程序执行时测试得到性能数据是系统架构设计的重要技能。
性能估算有如下用途:
1) 多种设计方案选择;2) 评价程序实现是否足够优化;
3) 向框架/服务提供方提出性能要求的依据;
通过查看程序运行时CPU及网络的使用情况来评价程序是否足够优化,这也是一种很重要的方法。然而,这种方法掩盖了不优化的实现,如O(N)的算法被错误实现成O(N^2),网络收发冗余数据等。
性能评估需要假设程序的执行环境,如集群规模及机器配置,集群上其它服务占用资源的比例。首先,我们需要知道一些常见硬件的大致性能参数:
L1 cache reference 0.5ns
Branch mispredict 5ns
L2 cache reference 7ns
Mutex lock/unlock 100ns
Main memory reference 100ns
Send 1M bytes over 1Gbps network 10ms
Read 1M sequentially from memory 0.25ms
Round trip within data center 0.5ms
Disk seek 8~10ms
Read 1MB sequentially from disk 20~25ms
标记为红色性能参数比较常用,其中,磁盘的性能指标专指分布式平台专用的大容量磁盘,寻道时间为8~10ms,顺序读取速率为40~50MB。某些产品线使用SCSI磁盘或者Flash盘,性能较好,评估时需查看硬件的性能参数。磁盘和网络都有一个特征,一次读写的数据量越大性能越好,这是由硬件特征及底层软件算法决定的,如tcp慢连接和磁盘寻道时间长。
对硬件性能指标有了初步认识以后,我们可以做出一些简单的判断,如:
问:是否批量读取?
答:是,每批读取10个记录。
由于tcp Round trip时间为0.5ms,读取请求个数的理论极限为2000/s,而A的开发同学却说单客户同步读取可以达到18000/s,可以断定A开发同学指的是批量读取方式。且这已经是单机能够做到的极限值了。
1. 1GB的4字节整数,内存排序时间为多少?
拿到这个问题,假设用快速排序,快速排序的时间复杂度 T = O(N*log(N)),我们往往会计算CPU运算次数,如快排的运算次数为1.4 * N * log(N),其中1.4为快排的系数,再根据CPU的运算频率计算出排序耗时。不过这种方法很土也不是很准,Jeff Dean告诉我们可以这样估算:排序时间 = 比较时间(分支预测错误) + 内存访问时间。快排过程中会发生大量的分支预测错误,所以比较次数为2^28 * log (2^28) ≈ 2^33,其中约1/2的比较会发生分支预测错误,所以比较时间为1/2 * 2 ^ 32 * 5ns = 21s(这个结果是有疑问:用计算机计算过程 2^32=4294967296, 1/2 * 2^32=2147483648, 1/2 * 2^32 * (5/1000000000)s = 10.73741824s),另外,快排每次找到分割点都需要一遍内存移动操作,而内存顺序访问性能为4GB/s,所以内存访问时间为28 * 1GB / 4GB = 7s(疑问28 * 1GB是如何出来的?)。因此,单线程排序1GB 4字节整数总时间约为28s( 10.73741824s(比较时间分支预测错误) + 7s(内存访问时间) =17.73741824s)。
扩展问题: 1.1 1GB的4字节整数,内存查找时间为多少?
假设已经排好序的数据,考虑二分查找,二分查找的时间复杂度 T = O( logN ),
首先 求出N = 1GB/4 = 2^30/2^2 = 2^28 = 268435456个4字节整数≈2亿6千8百万个整数。
其次 二分查找时间复杂度 T = O( logN ) = O(log268435456) = 8.4288398785914734659846890522858(次) ≈ 9次
最后 假设二分查找时间= 比较时间(分支预测错误) + 内存访问时间
比较时间(分支预测错误)
= 1/2 * 9次 * 5ns = 22.5ns
内存访问时间 = ???
1.2 1GB的4字节整数,内存排序空间为多少?
快速排序S = O(logn)
1.3 1GB的4字节整数,内存查找空间为多少?
二分查找S = O()
DTS总体设计中给出的性能指标为:
系统配置:50台4核8GB内存12路SATA硬盘,同样数量的客户端;
Table:row name:16-byte,column:16-byte,value:1KB;64KB data block;no compression;
Random reads (in disk): 1KB/item*300item/s*50=15MB/s
Random reads (in memory):1KB/item*4000item/s*50=200MB/s
Random writes:1KB/item*2000item/s*50=100MB/s
Sequential reads(in disk):1KB/item*1000item/s*50=50MB/s
Sequential writes:1KB/item*2000item/s*50=100MB/s
先看磁盘中的随机读取性能,由于在DTS的设计中每个随机读写都要读取一个64KB的大块,而磁盘中读取64KB数据时间为:磁盘寻道时间 + 读取时间 = 8ms + 64KB / 50MB/s = 20ms。所以每秒读取300个记录要么是批量读取,要么是异步读取。由于每台机器有12个SATA大容量磁盘,随机读的理论值为12 * 50 = 600个/s。设计为每秒读取300个是考虑到有负载平衡等因素简单地打了个对折。
再看内存中的随机读取。一般来说,内存操作都是每秒1W~10W。由于网络发送小数据有较多overhead且DTS内存操作有较多的内存开销,所以保守设计为单机每秒读取4000个记录。
其它的可类似分析。性能分析可能会很复杂,因为不同的情况下决定性能的瓶颈不一样,有的时候是网络,有的时候是磁盘,有的时候甚至是机房的交换机。这种性能分析的经验是需要慢慢积累的。
Map处理时间 = 输入读取时间 + Map处理时间 + 输出中间结果时间
其中,输入读取时间 = 50G / 2.5G = 25s (50台机器,假设每台机器读取带宽为50M/s),
R应用的map处理时间 = 60s,
输出中间结果时间 = 300G / 15G = 20s (50台机器,每台机器12个磁盘,假设用满6个磁盘,带宽为6 * 50M = 300M)
所以,Map处理时间 = 25s + 60s + 20s = 105s
Shuffle和排序时间 = shuffle时间 + 排序时间
其中,shuffle时间 = 300G / 2G = 150s (50台机器,假设每台机器的读取和写入带宽均为40M,单机总带宽为80M)
排序时间 = 单机排序6G的时间,假设每条记录为1KB = 排序比较时间 + 访问时间,约为25s
所以,shuffle和排序的时间 = 150s + 25s = 175s
Reduce处理时间 = R应用的reduce处理时间 + 最终结果输出时间
其中,R应用的reduce处理时间 = 100s,
最终结果输出时间 = 600M / 500M (50台机器,单机写DFS假设时间为10M/s) = 1s (忽略)
所以,R一遍MapReduce的估算时间 = Map处理时间 + shuffle和排序时间 + Reduce处理时间 = 105s + 175s + 100s = 380s,当然,MapReduce过程中还有框架的开销和其它应用的影响,我们可以简单地认为影响为20%,所以总时间 = 380s + 380s * 20% = 456s,约为7~8 min。
当然,R实际的性能估算不会如此简单,实际估算时需要考虑每台机器上启动的Map和Reduce个数等因素,且需要根据实验的结果不断地验证和重新调整估算。但是,我们至少可以保证,估算的结果和实际不会相差一个数量级,估算结果可以用来指导初期的设计和Map/Reduce Worker的个数、Map/Reduce任务数选择,评估应用的可优化空间并作为向MapReduce框架提供小组提出需求的依据。
性能估算是大规模系统设计中较难掌握的技能,开始性能估算时可能估计得很不准,不过不要气馁,通过在项目中不断练习,大规模系统的分析和设计能力可以提升一个档次。