操作系统作为所有程序的载体,对应用的性能影响是非常重要的。然而计算机各个组件之间的速度,是非常不均衡的。拿CPU和硬盘的速度来说,比兔子和乌龟的速度差别还要大。下面将简单的介绍CPU、内存、I/O的一些基本知识,以及一些如何评估它们性能的命令。
首先介绍计算机中最重要的计算组件:中央处理器。一般我们可以通过top命令来观测它的性能。
CPU的使用有多个维度的指标,以下分别说明一下:
一般的,我们比较关注空闲CPU的百分比,它可以从整体上体现CPU的利用情况。
我们还要评估CPU任务执行的排队情况,这些值就是负载(load)。top命令显示的CPU负载,分别是最近1分钟、5分钟、15分钟的数值。
如图,以单核操作系统为例,将CPU资源抽象成一条单向行驶的马路。则会发生三种情况:
那load为1代表的是啥?针对这个问题,误解还是比较多的。
很多老兄认为,load达到1,系统就到了瓶颈,这不完全正确。load的值和cpu核数息息相关。举例如下:
所以,对于一个load到了10,却是16核的机器,你的系统还远没有达到负载极限。通过uptime命令,同样能够看到负载情况。
要看CPU的繁忙程度,还可以通过vmstat命令。下面是vmstat命令的一些输出息。
我们比较关注的有下面几列:
要想了解内存对性能的一些影响,就需要从操作系统层面来看一下内存的分布。我们在平常写完代码后,比如写了一个C++程序,如果去查看它的汇编,可以看到其中的内存地址,并不是实际的物理内存地址。那么应用程序所使用的就是逻辑内存,这个学过计算机组成结构的同学都有了解。逻辑地址可以映射到物理内存和虚拟内存上。比如你的物理内存是8GB,分配了16GB的SWAP分区,那么应用可用的总内存就是24GB。从top命令可以看到几列数据,注意方块括起来的三个区域,解释如下:
由于CPU和内存之间的速度差异是非常大的,解决方式就是加入高速缓存。其实,这些高速缓存,往往会有多层,如下图。
Java有大部分知识点是围绕多线程的,那是因为,如果一个线程的时间片跨越了多个CPU,那么就会存在同步问题。在Java中,最典型的和CPU缓存相关的知识点,就是并发编程中,针对Cache line的伪共享(false sharing)问题。伪共享是指:在这些高速缓存中,是以缓存行为单位进行存储的。哪怕你修改了缓存行中一个很小很小的数据,它都会整个的刷新。所以,当多线程修改一些变量的值时,如果这些变量在同一个缓存行里,就会造成频繁刷新,无意中影响彼此的性能。通过以下命令即可看到当前操作系统的缓存行大小。
通过以下命令可以看到不同层次的缓存大小。
在JDK8以上的版本,通过开启参数-XX:-RestrictContended,就可以使用注解@sun.misc.Contended进行补齐,来避免伪共享的问题。在并发优化中,我们再详细讲解。
回头看我们最长的那副图,上面有一个叫做TLB的组件,它的速度虽然高,但容量也是有限的。这就意味着,如果物理内存很大,那么映射表的条目将会非常多,会影响CPU的检索效率。默认内存是以4K的page来管理的。如图,为了减少映射表的条目,可采取的办法只有增加页的尺寸。像这种将Page Size加大的技术,就是Huge Page。
HugePage有一些副作用,比如竞争加剧,Redis还有专门的研究(https://redis.io/topics/latency) ,但在一些大内存的机器上,开启后会一定程度上增加性能。
另外,一些程序的默认行为,也会对性能有所影响。比如JVM的-XX:+AlwaysPreTouch参数。默认情况下,JVM虽然配置了Xmx、Xms等参数,但它的内存在真正用到时,才会分配。但如果加上这个参数,JVM就会在启动的时候,把所有的内存预先分配。这样,启动时虽然慢了些,但运行时的性能会增加。
I/O设备可能是计算机里速度最差的组件了。它指的不仅仅是硬盘,还包括外围的所有设备。硬盘有多慢呢?我们不去探究不同设备的实现细节,直接看它的写入速度(数据未经过严格测试,仅作参考)。
可以看到普通磁盘的随机写和顺序写相差是非常大的。而随机写完全和cpu内存不在一个数量级。缓冲区依然是解决速度差异的唯一工具,在极端情况比如断电等,就产生了太多的不确定性。这些缓冲区,都容易丢。最能体现I/O繁忙程度的,就是top命令和vmstat命令中的wa%。如果你的应用,写了大量的日志,I/O wait就可能非常的高。
对于硬盘来说,可以使用iostat命令来查看具体的硬件使用情况。只要%util超过了80%,你的系统基本上就跑不动了。
详细介绍如下:
kafka比较快的一个原因就是使用了zero copy。所谓的Zero copy,就是在操作数据时, 不需要将数据buffer从一个内存区域拷贝到另一个内存区域。因为少了一次内存的拷贝,CPU的效率就得到提升。我们来看一下它们之间的区别:
要想将一个文件的内容通过socket发送出去,传统的方式需要经过以下步骤:
除了iotop、iostat这些命令外,sar命令可以方便的看到网络运行状况,下面是一个简单的示例,用于描述入网流量和出网流量。
当然,我们可以选择性的只看TCP的一些状态。
不要寄希望于这些指标,能够立刻帮助我们定位性能问题。这些工具,只能够帮我们大体猜测发生问题的地方,它对性能问题的定位,只是起到辅助作用。想要分析这些bottleneck,需要收集更多的信息。 想要获取更多的性能数据,就不得不借助更加专业的工具,比如基于eBPF的BCC工具,这些牛x的工具我们将在其他文章里展开。