在Linux系统中,由于成本的限制,往往会存在资源上的不足,例如 CPU、内存、网络、IO 性能。本文,就对 Linux 进程和 CPU 的原理进行分析,总结出 CPU 性能优化的方法。
一、分析手段
在理解平均负载之前,先要理清楚 Linux 下的进程状态。
1. 进程状态
(1) R (TASK_RUNNING),可执行状态
只有在该状态的进程才可能在 CPU 上运行。而同一时刻可能有多个进程处于可执行状态,这些进程的 task_struct 结构(进程控制块)被放入对应 CPU 的可执行队列中(一个进程最多只能出现在一个 CPU 的可执行队列中)。进程调度器的任务就是从各个 CPU 的可执行队列中分别选择一个进程在该 CPU 上运行。
很多操作系统教科书将正在 CPU 上执行的进程定义为 RUNNING 状态、而将可执行但是尚未被调度执行的进程定义为READY状态,这两种状态在linux下统一为 TASK_RUNNING 状态。
(2) S (TASK_INTERRUPTIBLE),可中断的睡眠状态
处于这个状态的进程因为等待某某事件的发生(比如等待 socket 连接、等待信号量),而被挂起。这些进程的 task_struct 结构被放入对应事件的等待队列中。当这些事件发生时
(由外部中断触发、或由其他进程触发),对应的等待队列中的一个或多个进程将被唤醒。通过 ps 命令我们会看到,一般情况下,进程列表中的绝大多数进程都处于 TASK_INTERRUPTIBLE 状态(除非机器的负载很高)。毕竟 CPU 就这么一两个,进程动辄几十上百个,如果不是绝大多数进程都在睡眠,CPU 又怎么响应得过来。
(3) D (TASK_UNINTERRUPTIBLE),不可中断的睡眠状态
与 TASK_INTERRUPTIBLE 状态类似,进程处于睡眠状态,但是此刻进程是不可中断的。
不可中断,指的并不是 CPU 不响应外部硬件的中断,而是指进程不响应异步信号。
绝大多数情况下,进程处在睡眠状态时,总是应该能够响应异步信号的。否则你将惊奇的发现,kill -9 竟然杀不死一个正在睡眠的进程了!于是我们也很好理解,为什么 ps 命令看到的进程几乎不会出现 TASK_UNINTERRUPTIBLE 状态,而总是 TASK_INTERRUPTIBLE 状态。
而 TASK_UNINTERRUPTIBLE 状态存在的意义就在于,内核的某些处理流程是不能被打断的。如果响应异步信号,程序的执行流程中就会被插入一段用于处理异步信号的流程(这个插入的流程可能只存在于内核态,也可能延伸到用户态),于是原有的流程就被中断了。
(参见《linux 内核异步中断浅析》) 在进程对某些硬件进行操作时(比如进程调用 read 系统调用对某个设备文件进行读操作,而 read 系统调用最终执行到对应设备驱动的代码,并与对应的物理设备进行交互),可能需要使用 TASK_UNINTERRUPTIBLE 状态对进程进行保护,以避免进程与设备交互的过程被打断,造成设备陷入不可控的状态。这种情况下的 TASK_UNINTERRUPTIBLE 状态总是非常短暂的,通过 ps 命令基本上不可能捕捉到。
(4) T (TASK_STOPPED or TASK_TRACED),暂停状态或跟踪状态
向进程发送一个 SIGSTOP 信号,它就会因响应该信号而进入 TASK_STOPPED 状态(除非该进程本身处于 TASK_UNINTERRUPTIBLE 状态而不响应信号)。(SIGSTOP 与 SIGKILL 信号一样,是非常强制的。不允许用户进程通过 signal 系列的系统调用重新设置对应的信号处理函数。)
向进程发送一个 SIGCONT 信号,可以让其从 TASK_STOPPED 状态恢复到 TASK_RUNNING 状态。
当进程正在被跟踪时,它处于 TASK_TRACED 这个特殊的状态。"正在被跟踪"指的是进程暂停下来,等待跟踪它的进程对它进行操作。比如在 gdb 中对被跟踪的进程下一个断点,进程在断点处停下来的时候就处于 TASK_TRACED 状态。而在其他时候,被跟踪的进程还是处于前面提到的那些状态。
对于进程本身来说,TASK_STOPPED 和 TASK_TRACED 状态很类似,都是表示进程暂停下来。而 TASK_TRACED 状态相当于在 TASK_STOPPED 之上多了一层保护,处于 TASK_TRACED 状态的进程不能响应 SIGCONT 信号而被唤醒。只能等到调试进程通过 ptrace 系统调用执行 PTRACE_CONT、PTRACE_DETACH 等操作(通过 ptrace 系统调用的参数指定操作),或调试进程退出,被调试的进程才能恢复 TASK_RUNNING 状态。
(5) Z (TASK_DEAD - EXIT_ZOMBIE),退出状态,进程成为僵尸进程
进程在退出的过程中,处于 TASK_DEAD 状态。在这个退出过程中,进程占有的所有资源将被回收,除了 task_struct 结构(以及少数资源)以外。于是进程就只剩下 task_struct 这么个空壳,故称为僵尸。之所以保留 task_struct,是因为 task_struct 里面保存了进程的退出码、以及一些统计信息。而其父进程很可能会关心这些信息。比如在 shell 中,$?变量就保存了最后一个退出的前台进程的退出码,而这个退出码往往被作为 if 语句的判断条件。
当然,内核也可以将这些信息保存在别的地方,而将 task_struct 结构释放掉,以节省一些空间。但是使用 task_struct 结构更为方便,因为在内核中已经建立了从 pid 到 task_struct 查找关系,还有进程间的父子关系。释放掉 task_struct,则需要建立一些新的数据结构,以便让父进程找到它的子进程的退出信息。
父进程可以通过 wait 系列的系统调用(如 wait4、waitid)来等待某个或某些子进程的退出,并获取它的退出信息。然后 wait 系列的系统调用会顺便将子进程的尸体(task_struct)也释放掉。
子进程在退出的过程中,内核会给其父进程发送一个信号,通知父进程来"收尸"。这个信号默认是 SIGCHLD,但是在通过 clone 系统调用创建子进程时,可以设置这个信号。
2. 平均负载
单位时间内,系统处于可运行状态和不可中断状态的平均进程数,也就是平均活跃进程数,它和 CPU 使用率并没有直接关系。
既然是平均的活跃进程数,那么最理想的,就是每个cpu 上都刚好运行着一个进程,这样每个 cpu 都得到了充分利用,比如当平均负载 2时,意味着什么呢?
- 在只有2 个 CPU的系统上,意味着所有的 CPU都刚好被完全占用
- 在 4 个CPU的系统上,意味着 CPU 有 50%的空闲
- 而在只有 1 个CPU 的系统上,则意味着有一半的进程竞争不到 CPU
(1) 平均负载多少合理?
平均负载最理想的情况是等于 CPU 个数。查看系统 CPU 的命令如下:
- cat /proc/cpuinfo
Figure 1 四核 CPU 查看平均负载的命令:
给了我们三个不同时间间隔的平均值,给我们提供了分析系统负载趋势的数据来源,让我们更全面、更立体地理解目前的负载情况。
- 1 分钟、5 分钟、15 分钟 的三个值基本相同,或者相差不大,说明系统负载很平
- 如果1 分钟的值远小于15 分钟 的值,说明系统最近 1 分钟的负载在减少,而过去15 分钟内却有很大的负载
- 如果1 分钟 的值远大于 15 分钟的值,就说明最近 1 分钟的负载在增加。一旦 1 分钟的平均负载接近或超过了 CPU 的个数,就意味着系统正在发生过载的问题。
uptime 命令在有些嵌入式设备中,会被裁减掉,但是可以通过 proc 文件系统来获取。命令:
- cat /proc/loadavg
很显然,当前命令展示的平均负载在 CPU 为 4 个时候已经过载
(2) 平均负载与 CPU 使用率
平均负载不仅包括了正在使用 CPU 的进程,还包括了等待 CPU 和等待 I/O 的进程。
CPU 使用率是指单位时间内 CPU 繁忙情况的统计,跟平均负载并不一定完全对应。比如:
- CPU 密集型进程,使用大量 CPU 会导致平均负载升高,此时这两者是一致的;
- I/O 密集型进程,等待 I/O 也会导致平均负载升高,但 CPU 使用率不一定很高
- 大量等待 CPU 的进程调度也会导致平均负载升高,此时的 CPU 使用率也会比较高。
3. CPU 上下文切换
在每个任务运行前, CPU 都需要知道任务从哪里加载、又从哪里开始运行、也就是说,需要系统事先给他设置好 CPU 寄存器和程序计数器(Program Counter, PC)
- CPU 寄存器:是 CPU 内置的容量小、但速度极快的内存。
- 程序计数器:是用来存储 CPU 正在执行的指令位置、或者即将执行的下一条指令位置。
它们都是 CPU 在运行任何任务前,比如的依赖环境,因此也被叫做 CPU 上下文。
- 上下文切换:就是先把前一个任务的 CPU 上下文(也就是 CPU 寄存器和程序计数器)保存起来,然后加载新任务的上下文到这些寄存器和程序计数器,最后再跳转到程序计数器所指的新位置,运行新任务。
- CPU 的上下文切换可以分为进程上下文切换、线程上下文切换以及中断上下文切换。
(1) 进程上下文切换
Linux 按照特权等级,把进程的运行空间分为内核空间和用户空间
- 内核空间(Ring 0)具有最高权限,可以直接访问所有资源。
- 用户空间(Ring 3)只能访问受限资源,不能直接访问内存等硬件设备,必须通过系统调用陷入到内核中,才能访问这些特权资源。
(2) 进程上下文切换和系统调用的区别
进程是由内核来管理和调度的,进程的切换只能发生在内核态。所以,进程的上下文不仅包括了虚拟内存、栈、全局变量等用户空间的资源,还包括了内核堆栈、寄存器等内核空间的状态。
系统调用过程中,并不涉及到虚拟内存等进程用户态的资源,也不会切换进程。
- 进程上下文切换,是指从一个进程切换到另一个进程进行。
- 系统调用过程中一直是同一个进程在运行。
因此,进程的上下文切换比系统调用时多了一步:在保存当前进程的内核状态和 CPU 寄存器之前,需要先把该进程的虚拟内存、栈等保存下来;而加载了下一个进程的内核态后,还需要刷新进程的虚拟内存和用户栈。
(3) 什么时候会切换进程上文
- 进程执行终止,它之前使用的 CPU 会释放出来,这时再从就绪队列里,拿一个新的进程过来运行。
- 当某个进程的时间片耗尽了,就会被系统挂起,切换到其他正在等待 CPU 的进程进行
- 进程在系统资源不足(比如内存不足)时,等到资源满足后才可以运行,这个时候进程也会被挂起,并由系统调度其他进程运行。
- 当进程通过睡眠函数 sleep 这样的方法将自己主动挂起时,自然也会重新调度。
- 当有优先级更高的进程运行时,为了保证高优先级进程的运行,当前进程会被挂起,由高优先级进程来运行。
- 发生硬件中断时,CPU 上的进程会被中断挂起,转而执行内核中的中断程序服务。
(4) 线程上下文切换
线程和进程的区别:
- 线程是调度的基本单位,而进程则是资源拥有的基本单位。
- 当进程只有一个线程时,可以认为进程就等于线程。
- 当进程拥有多个线程时,这些线程会共享相同的虚拟内存和全局变量等资源。这些资源在上下文切换时是不需要修改的。
- 线程也有自己的私有数据,比如栈和寄存器等,这些在上下文切换时也是需要保存的。
线程的上下文切换两种情况:
- 前后两个线程属于不同进程。此时,因为资源不共享,所以切换过程就跟进程上下文切换是一样的。
- 前后两个线程属于同一个进程。此时,因为虚拟内存是共享的,所以在切换时,虚拟内存这些资源就保持不动,只需要切换线程的私有数据、寄存器等不共享的数据。
(5) 中断上下文切换
中断处理会打断进程的正常调度和执行。在打断其他进程时,需要将进程当前的状态保存下来,中断结束后,进程仍然可以从原来的状态恢复运行。
进程上下文切换和中断上下文切换的区别:
- 中断上下文切换并不涉及到进程的用户态。所以,即便中断过程打断了一个正处在用户态的进程,也不需要保存和恢复这个进程的虚拟内存、全局变量等用户态资源。中断上下文,其实只包括内核态中断服务程序执行所必须的状态,包括 CPU 寄存器、内核堆栈、硬件中断参数等。
- 对同一个 CPU 来说,中断处理比进程拥有更高的优先级。
进程上下文切换和中断上文切换的相同之处:
- 都需要消耗 CPU,切换次数过多会耗费大量 CPU,甚至严重降低系统的整体性能。
(6) CPU 上下文切换小结
- CPU 上下文切换,是保证 Linux 系统正常工作的核心功能之一,一般情况下不需要我们特别关注。
- 但过多的上下文切换,会把 CPU 时间消耗在寄存器、内核栈以及虚拟内存等数据的保存和恢复上,从而缩短进程真正运行的时间,导致系统的整体性能大幅下降
(7) 如何查看系统的上下文切换
常用的系统性能分析工具,主要用来分析系统的内存使用情况,也常用来分析CPU 上下文切换和中断次数。
Figure 2 每隔 2 秒输出一组数据
需要特别关注的四列内容:
- cs (context switch):每秒上下文切换的次数。
- in (interrupt):每秒中断的次数。
- r (Running or Runnable) :就绪队列的长度,也就是正在运行和等待 CPU 的进程数。
- b (Blocked):处在不可中断睡眠状态的进程数。
在嵌入式 Linux 设备中,一般 vmstat 工具是不存在的。所以如果想要 vmstat 工具,可以自己实现代码,他的原理是获取/proc/diskstats 和/proc/slabinfo 的信息组合而成。实现代码见 procps 工具
vmstat 只给出了系统总体的上下文切换情况,并不能查看每个进程的上下文切换情况。
:查看某个进程中线程的上下文切换情况,下图查看的是 hicore 进程中所有的线程上下文切换情况。
关注两列内容:
- 自愿上下文切换:进程无法获取所需资源,导致的上下文切换。比如, I/O、内存等系统资源不足时。
- 非自愿上下文切换:进程由于时间片已到等原因,被系统强制调度,进而发生的上下文切换。比如,大量进程都在争抢 CPU 时。
(9) Procps 工具
procps 是一组命令行和全屏工具,是由内核动态生成的一个 "伪" 文件系统,可以提供进程表中条目状态的信息。该文件系统为内核数据结构提供了一个简易接口,procps 程序通常就集中在这个描述了系统进程运行状态的数据结构上。
procps 包括以下程序:
- free - 报告系统中可用内存和已用内存的数量
- kill - 基于 PID,向进程发送信号
- pgrep - 根据名称或其他属性列出进程
- pkill - 根据名称或其他属性向进程发送信号
- pmap - 报告进程的内存映射
- ps - 报告进程信息
- pwdx - 报告进程的当前目录
- skill - pgrep/pkill 的过时版本
- slabtop - 实时显示内核 slab 缓存信息
- snice - Renice 一个进程
- sysctl -运行时内核参数的读或写
- tload - 系统负载均值的可视化
- top - 正运行进程的实时动态视图
- uptime - 显示系统的已运行时间和负载情况
- vmstat - 报告虚拟内存统计信息
- w - 报告登录用户,以及他们正在做什么
- watch - 定期执行程序,显示全屏输出官网地址:http://procps.sourceforge.net/
(10) sysstat 工具
在嵌入式 Linux 设备中同样也不存在该工具,busybox 中也没有相关命令。需要安装 Linux 性能监控工具 sysstat,他是一个工具集,包括 sar、sadf、mpstat、iostat、pidstat 等,这些工具可以监控系统性能和使用情况。各工具的作用如下:
- iostat - 提供 CPU 统计,存储 I/O 统计(磁盘设备,分区及网络文件系统)
- mpstat - 提供单个或组合 CPU 相关统计
- pidstat - 提供 Linux 进程级别统计:I/O、CPU、内存等
- sar - 收集、报告、保存系统活动信息:CPU、内存、磁盘、中断、网络接口、TTY、内核表等
- sadc - 系统活动数据收集器,作为 sar 后端使用
- sa1 - 收集系统活动日常数据,并二进制格式存储,它作为 sadc 的工具的前端,可以通过 cron 来调用
- sa2 - 生成系统每日活动报告,同样可作为 sadc 的工具的前端,可以通过 cron 来调用
- sadf - 可以以 CSV、XML 格式等显示 sar 收集的性能数据,这样非常方便的将系统数据导入到数据库中,或导入到 Excel 中来生成图表
- nfsiostat-sysstat: 提供 NFS I/O 统计
- cifsiostat: 提供 CIFS 统计
sysstat 功能强大,功能也在不断的增强,每个版本提供了不同的功能,可以到 sysstat 官网了 解 工 具 最 先 发 展 情 况 和 获 得 相 应 的 帮 助 手 册 。 官 网 地 址 :
(11) 中断
中断是一种异步的事件处理机制,可以提高系统的并发处理能力。中断处理程序会打断其他进程的运行,为了减少对正常进程运行调度的影响,中断处理程序就需要尽可能快地运行。
Linux 将中断处理过程分成了两个阶段,也就是上半部和下半部。
- 上半部用来快速处理中断,它在中断禁止模式下运行,主要处理跟硬件紧密相关的或时间敏感的工作。
- 下半部用来延时处理上半部未完成的工作,通常以内核线程的方式运行。
/proc/interrupts:查看硬中断发生的类型
硬件中断发生频繁,是件很消耗 CPU 资源的事情,Linux 默认情况下是将所有的硬件中断都绑定在 CPU0 上,在多核 CPU 条件下如果有办法把大量硬件中断分配给不同的
CPU (core) 处理显然能很好的平衡性能。
(12) 根据上下文切换的类型做具体分析
- 自愿上下文切换变多,说明进程都在等待资源,有可能发生 I/O 等其他问题
- 非自愿上下文切换变多,说明进程都在被强制调度,也就是都在争抢 CPU,说明 CPU 的确成了瓶颈
- 中断次数变多,说明 CPU 被中断处理程序占用,还需要通过查看 /proc/interrupts 文件来分析具体的中断类型。
4. CPU 使用率
/proc/stat,提供的是系统的 CPU 和任务统计信息。这个信息非常的原始
CPU 使用率相关的重要指标:
- 第一列:user(us),代表用户态 CPU 时间。
- 第二列:nice(ni),代表低优先级用户态 CPU 时间,也就是进程的 nice 值被调整为 1-19 之间时的 CPU 时间。nice 可取值范围是 -20 到 19, 数值越大,优先级反而越低
- 第三列:system (sys),代表内核态 CPU 时间。
- 第四列:idle(us),代表空闲时间。注意,这里它不包括等待 I/O 的时间(iowait)。
- 第五列:iowait(wa),代表等待 I/O 的 CPU 时间。
- 第六列:irq(hi),代表处理硬中断的 CPU 时间
- 第七列:softirq(si),代表处理软中断的 CPU 时间。
真正查看 CPU 使用率的命令是通过 top 命令:
5. 软中断
提供了软中断的运行情况。
- 注意软中断的类型,也就是第一列内容。
- 注意同一种软中断在不同 CPU 上的分布情况,也就是同一行内容。
- 软中断实际上是以内核线程的方式运行的,每个 CPU 都对应一个软中断内核线程,这个软中断内核线程就叫做 ksoftirqd/CPU 编号
二、优化方法
1. CPU 使用率
CPU 使用率描述了非空闲时间占总 CPU 时间的百分比,根据 CPU 上运行任务的不同,又被分为用户 CPU、系统 CPU、等待 I/O CPU、软中断和硬中断等。
- 用户 CPU 使用率,包括用户态 CPU 使用率(user) 和低优先级用户态 CPU 使用率 (nice),表示 CPU 在用户态运行的时间百分比。用户 CPU 使用率高,通常说明有应用程序比较繁忙。
- 系统 CPU 使用率,表示 CPU 在内核态运行的时间百分比(不包括中断)。系统 CPU 使用率高,说明内核比较繁忙。
- 等待 I/O 的 CPU 使用率,通常也称为 iowait,表示等待 I/O 的时间百分比。iowait 高,通常说明系统与硬件设备的 I/O 交互时间比较长。
- 软中断和硬中断的 CPU 使用率,分别表示内核调用软中断处理程序、硬中断处理程序的时间百分比。它们的使用率高,通常说明系统发生了大量的中断。
2. 平均负载(Load Average)
平均负载,也就是系统的平均活跃进程数,它反映了系统的整体负载情况,主要包括三个数值,分别指过去 1 分钟、过去 5 分钟和过去 15 分钟的平均复制子。
理想情况下,平均负载等于逻辑 CPU 个数,这表示每个 CPU 都恰好被充分利用。如果平均负载大于逻辑 CPU 个数,就表示负载比较重了。
3. 进程上下文切换
- 无法获取资源而导致的自愿上下文切换。
- 被系统强制调度导致的非自愿上下文切换。
4. CPU 缓存的命中率
由于 CPU 发展的速度远快于内存的发展, CPU 的处理速度就比内存的访问速度快得多。这样,CPU 在访问内存的时候,免不了要等待内存的响应。为了协调这两者巨大的性能差距,CPU 缓存(通常是多级缓存)就出现了。
根据不断增长的热点数据,这些缓存按照大小不同分为 L1、L2、L3 等三级缓存,其中L1 和 L2 常用在单核中,L3 则用在多核中。
从 L1 到 L3,三级缓存的大小依次增大,相应的,性能依次降低(当然比内存还是好得多)。而它们的命中率,衡量的是 CPU 缓存的复用情况,命中率越高,则表示性能越好。
5. tcmalloc 替换 ptmalloc
(1) ptmalloc
Ptmalloc 采用主-从分配区的模式,当一个线程需要分配资源的时候,从链表中找到一个没加锁的分配区,在进行内存分配。
- 小内存分配
在 ptmalloc 内部,内存块采用 chunk 管理,并且将大小相似的 chunk 用链表管理,一个链表被称为一个 bin。前 64 个 bin 里,相邻的 bin 内的 chunk 大小相差 8 字节,称为 small bin,后面的是 large bin,large bin 里的 chunk 按先大小,再最近使用的顺序排列,每次分配都找一个最小的能够使用的 chunk。
Chunk 的结构如上所示,A 位表示是不是在主分配区,M 表示是不是 mmap 出来的,P 表示上一个内存紧邻的 chunk 是否在使用,如果没在使用,则 size of previouschunk 是上一个 chunk 的大小,否则无意义(而且被用作被分配出去的内存了),正式根据P 标记位和 size of previous chunk 在 free 内存块的时候来进行 chunk 合并的。当然,如果 chunk 空闲,mem 里还记录了一些指针用于索引临近大小的 chunk 的,实现原理就不复述了,知道大致作用就行。
在 free 的时候,ptmalloc 会检查附近的 chunk,并尝试把连续空闲的 chunk 合并成一个大的 chunk,放到 unstored bin 里。但是当很小的 chunk 释放的时候,ptmalloc 会把它并入 fast bin 中。同样,某些时候,fast bin 里的连续内存块会被合并并加入到一个 unsorted bin 里,然后再才进入普通 bin 里。所以 malloc 小内存的时候,是先查找 fastbin,再查找 unsorted bin,最后查找普通的 bin,如果 unsorted bin 里的 chunk 不合适,则会把它扔到 bin 里。
- 大内存分配
Ptmalloc 的分配的内存顶部还有一个 top chunk,如果前面的 bin 里的空闲 chunk 都不足以满足需要,就是尝试从 top chunk 里分配内存。如果 top chunk 里也不够,就要从操作系统里拿了。
还有就是特别大的内存,会直接从系统 mmap 出来,不受 chunk 管理,这样的内存在回收的时候也会 munmap 还给操作系统。
简而言之,就是:
- 小内存: [获取分配区(arena)并加锁] -> fast bin -> unsorted bin -> small bin -> large bin
- -> top chunk -> 扩展堆
- 大内存: 直接 mmap
总结:
释放的时候,几乎是和分配反过来,再加上可一些 chunk 合并和从一个 bin 转移到另一个 bin 的操作。并且如果顶部有足够大的空闲 chunk,则收缩堆顶并还给操作系统。
介于此,对于 ptmalloc 的内存分配使用有几个注意事项:
- Ptmalloc 默认后分配内存先释放,因为内存回收是从 top chunk 开始的。
- 避免多线程频繁分配和释放内存,会造成频繁加解锁。
- 不要分配长生命周期的内存块,容易造成内碎片,影响内存回收。
(2) Tcmalloc
具体实现原理不加以赘述,可自行百度学习之,总结以下特点。
- Tcmalloc 占用更少的额外空间。例如,分配 N 个 8 字节对象可能要使用大约 8N * 1.01字节的空间。即,多用百分之一的空间。Ptmalloc2 使用最少 8 字节描述一个 chunk。
- 更快。小对象几乎无锁, >32KB 的对象从 CentralCache 中分配使用自旋锁。 并且>32KB 对象都是页面对齐分配,多线程的时候应尽量避免频繁分配,否则也会造成自旋锁的竞争和页面对齐造成的浪费。
6. 思维导图
三、分析工具
从 CPU 的性能指标出发。当你要查看某个性能指标时,要清楚知道哪些工具可以做到。
四、思路
性能优化并不是没有副作用的,通常情况下 Linux 系统是不需要特意调整某些指标。往往性能优化会带来整体系统的复杂度的上升,降低了可移植性,也可能在调整某个指标的时候导致其他指标异常。
并不是所有的性能问题都需要去优化,需要对瓶颈点进行优化。比如当前系统有瓶颈,用户 CPU 使用率升高了 10%,而系统系统 CPU 使用率却升高了 50%,这个时候就应该首先优化系统 CPU 使用率。
1. 应用程序优化
从应用程序的角度来说,降低 CPU 使用率最好的方法是,排除所有不必要的工作,只保留最核心的逻辑。比如减少循环层次、减少递归、减少动态内存分配等等。
常见的几种应用程序的性能优化方法:
- 编译器优化:很多编译器都会提供优化选项,适当开启它们,在编译阶段你就可以获得编译器的帮助,来提升性能。目前设备采用的优化选项为 Os,相当于 O2.5
- 算法优化:使用异步处理,可以避免程序因为等待某个资源而一直阻塞,从而提升程序的并发处理能力。
- 多线程代替多进程:线程的上下文切换并不切换进程地址空间,因此可以降低上下文切换的成本。目前设备采用的是多线程模式。
- 使用 buffer:经常访问的数据,可以放在内存中缓存起来,这样在下次用时可以直接从内存中获取,加快程序的处理速度。
- 小内存使用:小内存的申请,在保证栈空间不溢出的情况下,尽量采用栈上申请,少使用动态内存申请,提高程序运行效率。
2. 系统优化
从系统的角度来说,优化 CPU 的运行,一方面要充分利用 CPU 缓存的本地性,加速缓存访问;另一方面,就是要控制进程的 CPU 使用情况,减少进程间的相互影响。
常见的方法:
- CPU 绑定:把进程绑定到一个或者多个 CPU 上,可以提高 CPU 缓存的命中率,减少跨 CPU 调度带来的上下文切换问题。
- 优先级调整:使用 nice 调整进程的优先级,正值调低优先级,负值调高优先级。
- 中断负载均衡:无论是软中断还是硬中断,它们的中断处理程序都可能消耗大量的 CPU。
配置 smp_affinity,就可以把中断处理过程自动负载均衡到其他 CPU 上
- 替换 ptmalloc:当前使用的 gblic 库,其动态内存管理采用就是 ptmalloc。当前应用程序大量使用小尺寸动态内存,可以采用 tcmalloc,在小内存上更快,几乎无锁。