老李分享:《Java Performance》笔记1——性能分析基础

1.性能分析两种方法:

(1).自顶向下:

应用开发人员通过着眼于软件栈顶层的应用,从上往下寻找性能优化的机会。

(2).自底向上:

性能专家从软件栈底层的CPU统计数据(例如CPU高速缓存未命中率、CPU指令效率)开始,逐渐上升到应用自身的结构或应用常见的使用方式。

2.CPU使用率:

大多数操作系统的CPU使用率分为用户态CPU使用率和系统态CPU使用率。

用户态CPU使用率:执行应用程序代码的时间占总CPU时间的百分比。

系统态CPU使用率:应用执行操作系统调用的时间占总CPU时间的百分比。

系统态CPU使用率高意味着共享资源有竞争或者I/O设备之间有大量交互。既然原本用于执行操作系统调用的CPU周期也可以用来执行应用程序代码,所以理想情况下,应用达到最高性能和扩展性时,它的系统态CPU使用率为0%,所以提供应用性能的和扩展性的一个目标是尽可能降低系统态CPU使用率。

对于计算密集型应用来说,不仅要监控用户态和系统态CPU使用率,还要进一步监控每时钟指令数(Instructions Per Clock, IPC)或每指令时钟周期(Cycles Per Instruction, CPI)等指标,提高计算密集型应用的常用策略是减少停滞(现代操作系统自带的CPU使用率监控工具只能报告CPU使用率,不能报告CPU执行指令占用CPU时钟周期的百分比,因此即便CPU在等待内存中的数据,操作系统工具仍然会报告CPU繁忙,这种情况通常被称为停滞)或者改善CPU高速缓存使用率,从而减少CPU在等待内存数据时浪费的时钟周期。

(1).Windows CPU使用率监控:

Windows最常用的CPU使用率监控工具是任务管理器和性能监视器,这两个图形化工具不做介绍,重点介绍一下命令行工具typeperf.

在命令行中监控系统态和用户态CPU使用率的命令行如下:

typeperf “\Processor(_Total)\% Privileged Time” “\Processor(_Total)\% User Time”

也可以将性能计数器列表写入文件,然后将文件名传给typeperf来使用,例如将系统态和用户态CPU使用率性能计数器写入如下的cpu-util.txt文件中:

\Processor(_Total)\% Privileged Time

\Processor(_Total)\% User Time

然后使用typeperf调用,如下:

typeperf -cf cpu-util.txt

下面的命令使用typeperf统计用户态CPU使用率、系统态CPU使用率和总CPU使用率:

typeperf “\Processor(_Total)\% User Time” “\Processor(_Total)\% Privileged Time” “\Processor(_Total)\% Processor Time”

(2).Linux CPU使用率监控:

Linux上可以使用图形化工具GNOME System Monitor(GNOME系统监视器,通过gnome-system-monitor命令启动)或者xosview监控CPU使用率,这里重点介绍命令行工具。

A. vmstat:

vmstat可以设定报告的时间间隔(秒级),如果不指定报告间隔,则输出自系统最近一次启动以来的总CPU使用率。vmstat输出的us是用户态CPU使用率,sy是系统态CPU使用率,id是空闲率或CPU可用率。

B. mpstat:

mpstat可以监控每个虚拟处理器的CPU使用率,有助于发现应用中是一些线程比其他线程消耗了更多CPU周期,还是应用的所有线程基本平分CPU周期,如果是后者表明应用的扩展性比较好,注意大部分的Linux需要安装sysstat包才能使用mpstat.

mpstat输出的usr是执行用户代码时所用CPU时间的百分比,sys是执行内核代码时所用CPU时间的百分比,wt是等待时间的百分比(不再计入,一直为0),idl是CPU空闲时间的百分比。

C. top:

Linux top命令不仅包括CPU使用率,也包括进程统计数据和内存使用率

3.CPU调度程序运行队列:

CPU调度程序运行队列中存放的是那些已经准备好运行、整等待可用CPU的轻量级进程,如果准备运行的轻量级进程数超过系统所能处理的上限,运行队列就会很长,运行队列长表明系统负载可能已经饱和。

系统运行队列长度等于虚拟处理器的个数时,用户不会明显感觉到性能下降,当运行队列长度达到虚拟处理器的4倍或更多时,系统的响应就非常迟缓了。

CPU调度程序运行队列性能调优的一般原则:如果在很长一段时间里,运行队列的长度一致都超过虚拟处理器个数的1倍,就需要关注了,只是暂时不需要立即采取行动。如果在很长一段时间里,运行队列的长度达到虚拟处理器个数的3~4倍或更高,则需要立即采取行动。

解决CPU调用程序运行队列过长有以下两个方法:

a.增加CPU以分担负载或减少处理器的负载量,从根本上减少了每个虚拟处理器上的活动线程数,从而减少运行队列中的轻量级进程数。

b.分析系统中运行的应用,改进CPU使用率。程序员可以通过更有效的算法和数据结构来实现更好的性能,性能专家通过减少代码路径长度或完成同样任务更少CPU指令的算法来提高性能。

(1).Windows CPU调度程序运行队列监控:

typeperf可以监控运行队列长度,监控命令如下:

typeperf “\System\Processor Queue Length”

下面是typeperf每5秒(默认是每1秒)监控输出运行队列长度的命令:

typeperf -si 5 “\System\Processor Queue Length”

(2).Linux CPU调度程序运行队列监控:

Linux中可以使用vmstat命令监控运行队列长度,vmstat输出的第一列即r为运行队列长度,值是运行队列中轻量级进程的实际数量。

4.内存使用率:

内存使用率的相关属性包括页面调度或页面交换、加锁、线程迁移中的让步式和抢占式上下文切换。

当应用运行所需的内存超过可用物理内存时,就会发生内存页面交换,系统在进行页面交换或使用虚拟内存时,应用会表现出明显的性能问题。为了应对这种可能出现的情况,通常要为系统配置swap空间,swap空间一般会在一个独立的磁盘分区上,当应用耗尽物理内存时,操作系统会将应用中最少运行的部分置换到磁盘上的swap空间,当访问应用被置换出去的数据时,就必须将它从磁盘置换进物理内存,而这种置换活动会对性能产生很大的影响,尤其是应用的响应性和吞吐量。

让步式上下文切换是指执行线程主动释放CPU,抢占式上下文切换是指线程因为分配的时间片用尽而被迫放弃CPU或被其他优先级更高的线程所抢占。

Java5之后的JVM增加了自旋锁优化机制,即线程通过忙循环自旋尝试获得锁,如果若干次忙循环自旋之后仍然没有成功,则挂起该线程,等待被唤醒再次尝试获取该锁。挂起和唤醒线程会导致操作系统的让步式上下文切换,因此锁竞争激烈的应用会表现出大量的让步式上下文切换,让步式上下文切换耗费的CPU时钟周期代价非常高(通常高达大约80000个时钟周期)。锁竞争监控一般性原则是,如果让步式上下文切换占去5%或更多可用时钟周期时,说明它遇到了锁竞争。

线程迁移是指待运行的线程在处理器之间迁移,大多数操作系统的CPU调度程序会将待运行线程分配给上次它运行的虚拟处理器,如果这个虚拟处理器忙,则调度程序就会将待运行线程迁移到其他可用虚拟处理器。线程迁移会因为新的虚拟处理器缓存中可能没有待运行线程所需的数据或状态信息而导致应用性能下降。多核系统上运行应用可能会发生大量的线程迁移,减少迁移的策略是创建处理器组并将应用分配给这些处理器组。

(1).Windows 内存使用率监控:

typeperf可以监控内存使用率,下面命令每隔5秒钟输出可用内存和页面调度:

typeperf -si 5 “\Memory\Available Mbytes” “\Memory\Pages/sec”

Windows内置的工具难以监控锁竞争,Windows的性能计数器可用监控上下文切换,但无法区分让步式和抢占式上下文切换,因此需要外部工具,如Intel VTune或AMD CodeAnalyst。

(2).Linux内存使用率监控:

Linux中可以使用vmstat命令监控内存使用率,vmstat输出的si表示内存页面换入量,so表示内存页面换出量,free表示可用的空闲内存。

Linux中可以使用sysstat包中的pidstat命令监控锁竞争,使用如下命令可以监控所有虚拟处理器的让步式上下文锁竞争:

pidstat -w

pidstat -w命令输出的cswch/s是让步式上下文切换。

若处理器为3.0GHz的CPU,通过pidstat -w命令监控发现共发生了1750次让步式上下文切换,则让步式上下文切换所浪费的时钟周期比例=1750*80000/3000000000=4.7%.

5.网络I/O使用率:

分布式应用的性能和扩展性受限于网络带宽或网络的I/O,例如若发送到系统网络接口硬件的消息量超过了它的处理能力,消息就会进入操作系统的缓冲区,从而导致应用延迟等。

(1).Linux网络I/O监控:

A.netstat或sysstat:

可以提供每秒发送和接收的包数,包括错误和冲突的包,但是不能提供网络使用率。

B.nicstat:

源码可以从http://sourceforge.net/projects/nicstat/files/下载,使用前需要编译。命令格式如下:

nicstat [-hnsz] [-i interface[,.....]] | [interval] [count]

其中-h是显示帮助信息,-n仅显示非本地接口,-s显示概要信息,-z跳过0值,-i interface是网络接口设备名,interval是报告输出的频率,count是报告的采样数。输出的%Util列就是网络使用率。

(2).Windows网络I/O监控:

Windows上监控网络I/O需要知道被监控网络接口的带宽和网络接口传递的数据量。

网络接口每秒传递的字节数可以通过typeperf -si 5 “\Network Interface(*)\Bytes Total/sec” 获得,单位是bytes/s。

网络带宽可以通过typeperf -si 5 “\Network Interface(*)\Current Bandwidth” 获得,单位是bit/s。

网络使用率=Bytes Total/sec/(Current Bandwidth / 8) * 100

网络使用率=(Bytes Total/sec * 8)/Current Bandwidth * 100

网络使用率提高的策略是使用非阻塞的网络I/O代替阻塞的网络I/O,对于非阻塞网络I/O在读请求时尽可能地多读取数据,在写请求时尽可能地多写入数据。

6.磁盘I/O使用率

对于频繁进行磁盘I/O的应用,如数据库、日志等,磁盘I/O对性能有着至关重要的影响。

Linux磁盘I/O监控:

Linux上安装sysstat包之后可以使用iostat命令监控磁盘,命令为:iostat -xm,输出的%util列就是磁盘I/O使用率。

改善磁盘I/O使用率策略:

(1).使用更快的存储设备。

(2).文件系统扩展到多个磁盘。

(3).使用操作系统缓存,开启磁盘缓存。