代码不能完全按照您的想法运行,它只能完全按照您的写法运行。

在算法或者数据结构课程上我们会学习大表示法用来表示程序的复杂度,但是却很少教我们如何找到程序中的热点,鉴于过早优化是万恶之源,我们应该了解一下性能分析器 (profilers) 和监控工具。它们会帮助我们找到程序中最耗时、最耗资源的部分,从而让我们能够集中精力优化这些特定的部分。

计时

和调试一致,我们可以打印代码从一处到另外一处所消耗的时间就可以发现问题

import time, random
n = random.randint(1, 10) * 100
 
# 获取当前时间 
start = time.time()
 
# 做些工作
print("Sleeping for {} ms".format(n))
time.sleep(n/1000)
 
# 比较当前时间和起始时间
print(time.time() - start)

不过,这种程序上计时的 墙上时间 也可能会带来误导性,因为计算机可能同时在运行其他进程,或者在等待某些事件发生

工具通常会区分实际时间、用户时间和系统时间。通常用户时间加系统时间代表了您的进程在 CPU 上实际消耗了多少时间

性能分析工具

CPU

CPU的性能分析器分为两种:

  • 追踪分析器
  • 采样分析器

追踪分析器会记录程序的每一次函数调用,而采样分析器则只会周期性的监测(通常为每毫秒)我们的程序并记录程序堆栈

大多数编程语言都有一些基于命令行的分析器,我们可以用这些分析器来分析代码,通常都可以集成在IDE中

对于一个程序的分析我们可以使用这些程序的分析器,比如Python中的cProfile模块或者GO语言中的pprof

对于这些精准程序的分析,我们可以看GO语言下pprf的分析这篇文章,我们现在主要还是关注Linux下的进程性能分析

top

我们使用可以使用 top 命令对程序进行分析,当我们输入top后出现的应该是类似于下面的这种图片

看起来十分头晕目眩,仿佛要晕文字了一般,但是稍加注意一些我们就会发现一些有趣的事情

我们注意PID = 271710 的这个 神比程序 ,其占据了1157%的CPU,相当于11个CPU核心跑满,即在默认情况下,我们输入top命令后会默认按CPU负载为我们的程序排序

这里面还有一些参数需要我们解释一些:

  • PR/NI:优先级,PR表示内核层面的默认优先级,NI表示用户态设置的 Nice 值
  • VIRT:虚拟内存占位,Go 运行时在启动时会预先向内核申请一大块虚拟地址空间,用于垃圾回收(GC)管理和堆栈分配。这不代表它真的用了这么多物理内存,只是“挂个号”
  • RES:常驻内存,实打实占据的RAM值
  • SHR:共享内存
  • S:表示状态(status),其中 R 表示 Running 正在运行,还有 S 表示 Sleep
  • TIME+:表示累加时间,

如果我们需要排除这种神人程序是谁写出来的,我们可以在top界面下按下 C 这将把完整命令运行语句表示出来,我们可以看见这个神人程序的运行地址是 /tmp/go-build.../exe/3

通过查阅资料我们可以知道 go-build程序是使用 go run xxx.go启动的,此时go会编译源码并存放在/tmp 文件下

一番排查后,我们可以发现罪魁祸首是下面这么一段程序(来自imicola忘记关掉的vscode中):

package main
 
import (
	"math"
)
 
func main() {
	for i := 0; i < 10; i++ {
		go worker()
	}
	select {}
}
 
func worker() {
	i := 0
	for {
		i++
		if i == math.MaxInt {
			i = 0
		}
	}
}
 

这个神人程序创建了10个协程然后疯狂的运行自增的计算,因为10个程序都会一直的进行,所以Go语言会分配10个线程给10个程序,结果就是这10个程序被分配到了10个核心中

如果我们在top界面按下H,就会显示具体的子进程:

这个线程“分裂”成了10个子线程,每个线程都吃着近乎100%的CPU,简直是残忍至极(

但是imicola并不懂得如何关掉一个进程,也不知道在vscode终端下按下 Ctrl + C 会向进程发送SIGINT(9)信号通知进程关闭

但是top命令可以帮助他关闭这个失控的程序,在top界面按下 K 输入 PID后 top 就会向程序发送 SIGTERM(15) 信号

在更多现代的命令行工具或者TUI工具中,top命令中的内容可以有更加直观的展现,比如 htopbtop。imicola强推 btop,颜值至上是对的

btop界面

load average

不管我们在看top命令还是btop命令的时候,我们都不免会注意到有一行文本叫 load average,翻译过来即平均负载,表示在特定时间间隔内,系统中处于运行队列中的平均活跃进程数

其中一般而言这个特定时间间隔表示 1分钟 5分钟 和 15分钟