文章目录
Linux性能监控之CPU
1. CPU介绍
内核调度器将负责调度2种资源种类:线程(单一或者多路)和中断。
调度器去定义不同资源的不同优先权。
优先级:Interrupts(中断) > Kernel(System) Processes(内核处理/系统进程) > User Processes(用户进程)
**Interrupts(中断) **: 设备通知内核,他们完成一次数据处理的过程。例如,当一块网卡设备递送网络数据包或者一块硬件提供了一次IO请求。
Kernel(System) Processes(内核处理过程) :所有内核处理过程就是控制优先级别。
User Processes(用户进程):所有软件程序都运行在user space中。这块在内核调度机制中处于低优先级。
CPU我们重点关注以下3个部分:
- utilization(利用率)
- context(上下文切换)
- run queues(运行队列)
2. CPU利用率
CPU 利用率是定义CPU使用的百分比,主要依赖于是什么资源在试图存取CPU。评估系统最重要的一个度量方式就是查看CPU的利用率。要使应用的性能或扩展性达到最高,就必须充分利用分配给它的CPU周期,不能有丝毫浪费。
应用消耗很多CPU并不意味着性能或者扩展性达到了最高,要想找出应用如何使用CPU周期,需要监控系统的CPU使用率。如果一个CPU被充分使用,利用率分类之间均衡的比例应该是:
- 65% - 70% User Time
- 30% - 35% System Time
- 0% - 5% Idle Time
CPU利用率的分类如下:
User Time(用户进程时间): 关于在user space中被执行进程在CPU 开销时间百分比。
用户态CPU使用率是指执行应用程序代码的时间占总CPU时间的百分比。
System Time(内核线程以及中断时间) : 关于在kernel space中线程和中断在CPU开销时间百分比。
系统态CPU使用率是指应用执行操作系统调用的时间占总CPU时间的百分比。
系统态CPU使用率高意味着共享资源有竞争或I/O设备之间有大量的交互。
理想情况下,应用达到最高性能和扩展性时,它的系统态CPU使用率为0%。所以要尽可能降低系统态CPU使用率。
Wait IO(IO 请求等待时间) :所有进程线程被阻塞等待完成一次IO请求所占CPU开销idle的时间百分比。
Idle(空闲) : 一个完整空闲状态的进程在CPU 处理器中开销的时间百分比。
3. 监控CPU
3.1. 使用top监控CPU使用率
上半部分为整个系统的统计信息,下半部分为每个进程的统计信息,默认按CPU使用率从高到低排列。
3.2. 使用vmstat监控CPU使用率
内容 | 说明 |
---|---|
us | 用户态CPU使用率 |
sy | 系统态( 内核和中断)CPU使用率 |
id | 空闲率或CPU可用率 |
wa | 所有可运行状态线程被阻塞在请求等待IO的百分比 |
us+sy+id=100
3.3. 使用mpstat监控CPU使用率
Linux 内核将双核处理器认为是2个CPU,因此一个双核处理器的双内核会报告有4个CPU可用。
mpstat 命令给出的CPU利用率的统计值大致和vmstat 一致,但是mpstat可以监控每个虚拟处理器的CPU使用率,有助于发现应用中是一些线程比其他线程消耗量更多的CPU周期,还是应用的所有线程基本平分CPU周期。如果是后者,意味着应用的扩展性比较好。
如果不指定mpstat的报告间隔,则输出系统最近一次启动以来所有mpstat数据的总和。
内容 | 说明 |
---|---|
usr | 执行用户代码时所用CPU时间的百分比 |
sys | 执行内核代码时所用CPU时间的百分比 |
iowait | 表示IO等待时间 |
idle | 表示CPU空闲时间的百分比 |
使用mpstat监控每个虚拟处理器的CPU使用率:
-P 0 表示指定查看第一颗CPU的使用情况(下标从0开始)
3.4. 使用pidstat监控单个CPU使用率
示例:通过执行WhileTrue.py脚本使CPU占用率飙高并查看该进程的CPU使用率。命令:pidstat -u 1 -p 3336
4. CPU空闲的原因
- 应用被同步原语阻塞,直至锁释放才能继续执行。
- 应用在等待某些东西,例如数据库调用所返回的响应。
- 应用的确是无所事事。
5. 运行队列(Run Queues)
每个CPU 都维护一个线程的运行队列。理论上,调度器应该不断的运行和执行线程。进程线程不是在sleep 状态中(阻塞和等待IO中)就是在可运行状态中。
如果CPU子系统处于高负荷下,那就意味着内核调度将无法及时响应系统请求。导致可运行状态进程拥塞在运行队列里。当运行队列越来越巨大,进程线程将花费更多的时间获取被执行。
注:每个处理器应该运行队列不超过1-3个线程。例如,一个双核处理器应该运行队列不要超过6 个线程。
运行队列中是那些已准备好运行、正等待可用CPU的轻量级进程。如果准备运行的轻量级进程数超过系统所能处理的上限,运行队列就会很长。运行队列长表名系统负载可能已饱和。
**系统运行队列长度等于虚拟处理器的个数时,用户不会明显感觉到性能下降。**此处虚拟处理器的个数就是系统硬件线程的个数。即Java API Runtime.availableProcessors()的返回值。当运行队列长度达到虚拟处理的4倍或更多时,系统的响应就非常迟缓了。
一般性的指导原则是:如果在很长一段时间里,运行队列的长度一直都超过虚拟处理器个数的1倍,就需要关注了,如果3~4倍,需要立刻引起注意或采取行动。
5.1. 解决运行队列长的两种方法
-
增加CPU以分担负载或减少处理器的负载量,从根本上减少了每个虚拟处理器上的活动线程数,从而减少了运行队列中的轻量级进程数。
-
研究可以减少应用运行所需CPU周期的方法,如减少垃圾收集的频度或采用完成同样任务但CPU指令更少的算法。
5.2. 使用top查看运行队列的详细状态
负载是指在CPU 队列中有多少数目的线程,以及当前有多少进程线程数目被执行的组合。
负载 = 正在运行的轻量进程数 + 运行队列中的轻量进程数
例如:一个双核系统执行了2个线程,还有4个在运行队列中,则 load 应该为 6。
top中显示的load averages(平均负载) 分别是指1分钟、5分钟、15 分钟以内的负载情况,如果 CPU 是单核的,则这个数值超过 1 就是高负载,如果 CPU 是四核的,则这个数值超过 4 就是高负载。
经验之谈:如果一个总核数=8核心的CPU,理论上平均负载达到16,也还可以坚持很长一段时间。
5.3. 使用vmstat查看运行队列长度
vmstat输出的第一列是运行队列长度,值是运行队列中轻量级进程的实际数量。
6. 上下文切换(Context Switches)
即使是单核CPU也能支持多线程执行代码,CPU通过给每个线程分配CPU时间片来实现这个机制。时间片是CPU分配给各个线程的时间,因为时间片非常短,所以CPU通过不停的切换线程执行,让我们感觉多个线程是同时执行的,时间片一般是几十毫秒。
CPU通过时间片分配算法来循环执行任务,当前任务执行一个时间片后会切换到下一个任务,但是,在切换前保存上一个任务的状态,以便下次切换回这个任务时,可以再加载这个任务的状态。所以任务从保存到再加载的过程就是一次上下文切换,线程上下文切换通常消耗数百个时钟周期。
如何减少上下文切换
- 无锁并发编程,多线程竞争锁时,会引起上下文切换。
- CAS算法,因为不需要加锁。
- 使用最少线程。
- 协程:在单线程里实现多任务的调度,并在单线程里维持多个任务间的切换。
多数现代处理器都能够运行一个进程(单一线程)或者线程。多路超线程处理器有能力运行多个线程。然而,Linux 内核还是把每个处理器核心的双核心芯片作为独立的处理器。比如,以Linux 内核的系统在一个双核心处理器上,报告显示为两个独立的处理器。
一个标准的Linux 内核可以运行50 至 50,000 的处理线程。在只有一个CPU时,内核将调度并均衡每个进程线程。每个线程都分配一个在处理器中被开销的时间额度。一个线程要么就是获得时间额度或已抢先获得一些具有较高优先级(比如硬件中断),其中较高优先级的线程将从区域重新放置回处理器的队列中。这种线程的转换关系就是我们提到的上下文切换。
每次内核的上下文切换,资源被用于关闭在CPU寄存器中的线程和放置在队列中。系统中越多的上下文切换,在处理器的调度管理下,内核将得到更多的工作。
上下文切换的数目直接关系到CPU 的使用率,如果CPU 利用率保持在均衡状态时( 65% - 70% User Time、30% - 35% System Time、0% - 5% Idle Time),大量的上下文切换是正常的。
上下文切换分为:
- 抢占式上下文切换
- 让步式上下文切换
让步式上下文切换是指执行线程主动释放CPU,抢占式上下文切换是指线程因为分配的时间片用尽而被迫放弃CPU或者被其他优先级更高的线程所抢占。
抢占式上下文切换率高表明预备运行的线程数多于可用的虚拟处理器,此时用vmstat通常就能看到很长的运行队列、很高的CPU使用率、很大的迁移数以及大量与之相关的抢占式上下文切换。
7. 监控上下文切换
vmstat监控:
pidstat -w监控:
wt查看线程级别的上下文切换:
pidstat -w报告的是每秒而不是每个测量间隔的让步式上下文切换。
pidstat -w是所有处理器的上下文切换。
cswch/s是让步式上下文切换
nvcswch/s是抢占式上下文切换
一般性准则:如果让步式上下文切换占去它5%或更多的时钟周期,说明Java应用正面临锁竞争,即便占到了3%~5%也值得进一步调查。
Java5以前的版本中,HotSpotVM几乎将所有的锁逻辑都委托给操作系统锁原语,这使得操作系统工具在察看系统态CPU使用率以及smtx(Spin on Mutex互斥量上的自旋次数)的同时,可以很容易地监控Java应用中的锁竞争。
而在Java5中,HotSpotVM以用户代码的方式实现了许多锁优化逻辑、Java同步方法及同步块。所以以前用mpstat查看smtx和sys系统态CPU使用率的方法无法奏效了,需要寻找新的替代方法。
Java5以上,如果若干次忙循环自旋之后没有获取到锁,则挂起该线程,等待被唤醒后再次尝试获取该锁,挂起和唤醒线程会导致操作系统的让步式上下文切换,因此锁竞争严重的应用会表现出大量的让步式上下文切换。
让步式上下文切换耗费的时钟周期代价非常高,通常高达80000个时钟周期(这些时钟周期原本可以用来执行程序指令)。
让步式上下文切换浪费的时钟周期,可以由pidstat -w的让步式上下文切换数除以虚拟处理器的数目而得出。注意:pidstat -w是所有虚拟处理器的让步式上下文切换。让步式上下文切换数乘80000,除以CPU每秒的时钟周期,可以得出让步式上下文切换所耗费的CPU时钟周期百分比。
让步式上下文切换所耗费的时钟周期估算:
例如:让步式上下文切换次数是8000/s,8000*80000(一般上下文切换的时钟周期代价)= 640 000 000个时钟周期,3.0GHz的处理器每秒能执行3000 000 000(30亿)个时钟周期,所以640 000 000/3 000 000 000=21.33%的可用时钟周期。说明Java应用正面临锁竞争,锁竞争可能是因为多个线程正在访问同一个同步方法或同步块,也可能是因为代码被诸如java.util.concurrent.locks.Lock这样的Java锁结构所保护。
例如:cswch/s=3500,有2个处理器,则每个处理器的上下文切换为3500/2=1750,耗费的时钟周期为1750*80000=140 000 000。3GHzCPU每秒的时钟周期数为3 000 000 000,因此上下文切换所浪费的时钟周期为140 000 000/3 000 000 000=4.7%,说明Java应用正面临锁竞争。
8. 如何查看进程占用了哪个CPU
while :; do ps -eo pid,ni,pri,pcpu,psr,comm | grep 'python'; sleep 1;done
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eXe2rYzA-1657590066014)(/Users/zy/Desktop/笔记/性能测试/image-20220712092414388.png)]
如图所示,psr这列表示进程占用了哪个CPU,如图所示,python3的进程,一部分占用了第一个CPU,一部分占用了第二个CPU。
9. 查看CPU信息
[root@iZbp1aqn43v9f9y956pim1Z huyan]# cat /proc/cpuinfo
processor : 0 //逻辑核数量
vendor_id : GenuineIntel
cpu family : 6
model : 85
model name : Intel(R) Xeon(R) Platinum 8269CY CPU @ 2.50GHz //cpu的名称型号和主频
stepping : 7
microcode : 0x1
cpu MHz : 2500.000 //实际主频
cache size : 36608 KB
physical id : 0 //单个cpu的标号
siblings : 2 //单个cpu的逻辑核数
core id : 0
cpu cores : 1 //逻辑核所处cpu的物理核
apicid : 0
initial apicid : 0
fpu : yes
fpu_exception : yes
cpuid level : 22
wp : yes
flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ss ht syscall nx pdpe1gb rdtscp lm constant_tsc rep_good nopl xtopology nonstop_tsc eagerfpu pni pclmulqdq ssse3 fma cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand hypervisor lahf_lm abm 3dnowprefetch invpcid_single fsgsbase tsc_adjust bmi1 hle avx2 smep bmi2 erms invpcid rtm mpx avx512f avx512dq rdseed adx smap clflushopt clwb avx512cd avx512bw avx512vl xsaveopt xsavec xgetbv1 arat avx512_vnni
bogomips : 5000.00
clflush size : 64
cache_alignment : 64
address sizes : 46 bits physical, 48 bits virtual
power management:
processor : 1
vendor_id : GenuineIntel
cpu family : 6
model : 85
model name : Intel(R) Xeon(R) Platinum 8269CY CPU @ 2.50GHz
stepping : 7
microcode : 0x1
cpu MHz : 2500.000
cache size : 36608 KB
physical id : 0
siblings : 2
core id : 0
cpu cores : 1
apicid : 1
initial apicid : 1
fpu : yes
fpu_exception : yes
cpuid level : 22
wp : yes
flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ss ht syscall nx pdpe1gb rdtscp lm constant_tsc rep_good nopl xtopology nonstop_tsc eagerfpu pni pclmulqdq ssse3 fma cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand hypervisor lahf_lm abm 3dnowprefetch invpcid_single fsgsbase tsc_adjust bmi1 hle avx2 smep bmi2 erms invpcid rtm mpx avx512f avx512dq rdseed adx smap clflushopt clwb avx512cd avx512bw avx512vl xsaveopt xsavec xgetbv1 arat avx512_vnni
bogomips : 5000.00
clflush size : 64
cache_alignment : 64
address sizes : 46 bits physical, 48 bits virtual
power management:
查看机器一共几个CPU:
[root@iZbp1aqn43v9f9y956pim1Z huyan]# cat /proc/cpuinfo| grep "physical id"| sort| uniq| wc -l
1
查看单个CPU的物理核数:
[root@iZbp1aqn43v9f9y956pim1Z huyan]# cat /proc/cpuinfo| grep "cpu cores"| uniq
cpu cores : 1
查看总逻辑核数:
[root@iZbp1aqn43v9f9y956pim1Z huyan]# cat /proc/cpuinfo| grep "processor"| wc -l
2