基本概念
CPU缓存 & 缓存行 & 缓存一致性:
CPU缓存:为了弥补CPU和主存速度上的差异,处理器内部会引入多级Cache,称为CPU缓存。
-
缓存行(Cache Line):CPU Cache中的最小单元,是用于Cache存储和传输的单位。x86处理器典型的缓存行大小为64字节。编译器在优化的时候需要考虑这个参数。
-
缓存一致性:主存中的数据可能被多个CPU缓存下来。当一个CPU修改了内存,所有CPU Cache需要知道他的Cache已经失效,应该被丢弃,这样后续读到最新的数据,这个过程叫缓存一致性。在Java中volatile就是用来解决该问题的。
运行队列:
一个等待CPU服务的可运行线程队列。对于多CPU系统,每个CPU都会有一个运行队列,并尽量使得线程每次都被放入同一队列之中。这意味着线程更有可能在同一CPU上运行,因为CPU缓存里保持了他们的数据。
用户态与系统态执行时间:
-
用户时间:CPU花在执行用户态应用程序代码的时间;
-
内核时间:执行内核态代码的时间,包含系统调用、内核线程、中断、上下文切换等;
一个典型的Web应用程序通过系统调用来执行I/O操作,用户时间和内核时间比例大概在7:3左右。
上下文切换(context switchs):
-
自愿式上下文切换(voluntary context switch):不满足执行条件(比如发生系统调用),主动让出时间片;
-
抢占式上下文切换(involuntary context switch):时间片用尽,其他线程/进程抢占CPU资源。如果抢占式上下文切换次数过多说明可能有计算密集型应用,CPU资源出现瓶颈了。
CPU使用率与负载:
-
CPU使用率:一段时间内CPU用于执行工作的时间,它反映了一段时间CPU的繁忙程度;
-
CPU负载:等待CPU的进程数和正常运行CPU的进程数之和。
CPU架构
一个典型的双核处理器结构如下: image一个CPU里包含控制逻辑、寄存器、算术逻辑单元、一级Cache(又分为指令Cache和数据Cache)、二级Cache等。多个CPU单元共享浮点单元、MMU、TLB等。在某些处理器中可能还会有多个CPU共享的三级Cache。
其中MMU负责把虚拟地址转换到物理内存地址,TBL缓存最近查找过的VA对应的页表项。
典型的CPU读取数据逻辑如下:
imageCPU发出取数据请求,此时的地址为虚拟地址。到达一级Cache之后,查看Cache是否命中,命中返回;未命中请求到达MMU需要把虚拟地址转换为物理地址(转换的时候查看TBL中是否有对应的页表信息,若存在直接返回;不存在需要到主存获取页表信息并缓存到TLB中)。经过MMU之后就是获取到了物理地址,然后到二/三级Cache去读取、未命中到主存中读取数据。
但是在处理器的具体实现上可能和上面的步骤有所差别,主要是体现在MMU/TLB的位置上。有些处理器实现可能会把MMU/TLB放到一级Cache和CPU中间,有些处理可能把MMU/TLB放在二/三级Cache和主存之间,前者被称为物理Cache,后者被称为逻辑Cache。物理Cache中所有的Cache存储地址都是物理地址,所有请求都需要先经过转换然后再去Cache中读取数据。逻辑Cache中所有的Cache存储地址都是虚拟地址,所有请求现在Cache中查找未命中,然后再经过MMU/TLB去主存中查找。
逻辑Cache有个优势就是可以在完成虚拟地址到物理地址的翻译之前就可以开始比对Cache。但是有一个问题就是Cache一致性还有Cache eviction必须通过物理地址来做,因为多个虚拟地址可能对应同一个物理地址,不能保证不同的虚拟地址所以应的cache就一定不是同一份数据。
CPU性能分析常见工具
uptime:
imageload average后面的3个数字分别表示系统在过去的1分钟、5分钟和15分钟内的平均负载。
top:
具体含义可以查看:top命令
vmstat:
imager:运行队列中进程数量;
b:等待IO进程的数量;
in:每秒中断数
cs:每秒上下文切换数
us:用户态时间比例
sy:内核态时间比例
id:CPU空闲时间比例
wa:IO等待时间百分比
st:steal time的时间比例
dstat:
image其中usr同上的us,sys同上的sy,idl同上的id,wai同上的wa,int同上的in,csw同上的cs。
hiq:用于硬中断处理时间
siq:用于软中断处理时间
mpstat:
image能看到每个CPU的具体时间消耗比例。有时候CPU整体利用率不高,但是个别CPU过高也会影响应用性能。
pidstat:
image具体字段含义同上。
jstack/pstack/gstack:
pstack/gstack/jstack都是查看线程的调用栈情况,但是pstack/gstack主要针对于c调用栈或者系统函数,jstack主要是针对于Java应该。
在处理CPU过高的请求下,我们一般用top查看哪个CPU使用率过高,然后再使用'top -H -p pid'查看哪个线程使用cpu量最高,然后pstack/gstack/jstack去查看对应线程的调用栈即可定位问题。
perf:
号称Linux下诊断的瑞士军刀,具体的用法大家可查看官方文档或者man
来聊聊CPU上下文切换
哪些情况下可能带来CPU上下文切换,笔者简单列举一下几种情况:
-
CPU时间片到了。为了保证所有进程可以得到公平的调度,CPU时间被划分为一段段的时间片,这些时间片轮流分配给各个进程。
-
进程在系统资源不足的情况下(比如锁),需要主动挂起等到满足后才可以运行。
-
进程调用sleep函数主动挂起自己。
-
有更高优先级的进程需要运行时,为了保证高优先级的进程的运行,当前经常会被挂起。
-
发生硬件中断时,当前进程需要挂起处理中断服务程序。
因为CPU上下文切换都是在内核态中进行的,需要保持内核状态和CPU寄存器等信息,所以上下文切换不是免费的,是需要消耗CPU资源的。
那么如何统计/测量CPU上下文切换资源消耗呢?
目前现有的工具都集中在上下文切换次数上,要是有工具能统计到每次上下文切换耗时也可以。笔者推荐使用LMbench这个做上下文切换耗时的基准测试。我在test环境测试的结果如下:
image其中2p/16k表示2个并行处理16K大小的数据,上下文切换不仅跟线程/进程数有关还和处理的数据量有关(涉及到寄存器和cache的命中率问题)。但是基准测试基本上表明了每次上下文切换需要耗时1 -10微秒左右。也就说测试机器每秒能承受的上下文切换的最大值在10w-100w次(跟具体的应用有关)。当然读者也可以使用sysbench做基准测试。
一般来说,每秒几千次上万次的上下文切换对机器不会带来太大影响。还有CPU上下文切换都是在内核态下进行的消耗的系统态的时间,也可以通过查看系统态时间是否正常,要是正常上下文切换肯定也正常,不正常要留意是不是上下文切换导致的。
网友评论