在软件开发过程中,一般要先实现功能方面的需求,功能方面的需求开发完毕之后,往往会考虑性能方面的优化。在业务发展的初期,性能往往能满足使用的需求,这时性能优化不是必不可少的。随着业务的发展,软件复杂度的提高,性能有时会成为瓶颈,这时性能优化是必须要做的工作。
1 性能优化的一些概念
(1)性能优化的两个方面:软件和硬件
从广义上来说,性能可以从硬件和软件两个方面来优化。比如网络方面的性能优化,网卡带宽从 10M、100M 到现在的 1G、10G 甚至 100G 的网卡,同样软件的情况下,网卡硬件性能的提高可以带来整体性能的提高。内存,磁盘,cpu 等硬件,在过去的发展中,性能都得到了很大的提升。另一方面是通过优化软件来做性能优化,在实际开发中,性能优化的工作大多都是在硬件不改变前提下,通过优化软件的方式来达到提高性能的目的。
(2)性能优化的指标
性能优化的指标是评价性能高低的,可量化的指标。性能优化的指标有一些是通用的指标,有一些是和业务场景强相关的指标。
① 基础通用指标
通用的指标,包括资源的使用率,比如软件占用的 cpu、内存、磁盘的多少,这些指标是最基础的,往往也是最重要的。
② QPS
在互联网服务中经常关注这个指标, QPS(Queries Per Second),即服务每秒钟可以响应的查询次数。
③ 延时和吞吐率
在网络应用中,经常关注这两个指标。延时是指通信的双方一发一收的时间为一次通信延时,多次统计的平均值是平均延时。吞吐率指的是单位时间内处理的网络报文的个数。延时越低,性能越好;吞吐率越高,性能越好。
在硬件条件不变的情况下,延时和吞吐率往往是此消彼长的两个方面,延时降低之后,吞吐率往往会降低;吞吐率提升之后,延时往往会增长。之所以说硬件条件不变的前提,是因为从硬件方面来优化的话,延时和吞吐率可以同时优化,比如网卡从 10M 升级到 100G,那么延时和吞吐率都会提升。类似于马路上的车流量,如果正常情况下,马路上没有专用车辆(警车,救护车等),都是普通的家用车,这时候车辆可以随便开,每辆车都可以自由变道,这种情况下马路的吞吐量是最大化的。而如果这个时候马路上出现了一辆救护车,普通的家用车要给救护车让出一条专用道出来,那么普通家用车可用的车道数量就变少了,救护车的延时降低了,普通车的延时增大了,总体的吞吐率也降低了。
延时也不仅仅局限于网络性能指标,在我们开发的很多应用中,延时都是很重要很关键的一个指标。特别是现在服务器内存和磁盘相对充裕的情况下,多用一点内存或者磁盘,不是那么的严重,这时延时是最能体现业务性能的指标。
(3)平均值还是单次的最值
以延时为例,有些情况下考虑的是延时的平均值,比如路由器产品中,往往关注平均延时。互联网服务中,很多时候也关注平均延时。而有些情况下,关注的是每一次的延时,这种使用场景,吞吐量往往不是很大。比如智能驾驶系统中,业务处理时,从传感器获取到环境信息到感知模块处理,到规划、控制模块处理,到最后下发车辆的实际控制信号(减速,绕行等),整个流程的时间是有最大要求的,比如每一次的时间都不能大于 10ms,智能驾驶系统对安全性的要求非常高,要求每次都要满足这样的要求。如果有两个系统,一个系统的平均延时能到 5ms,但是偶尔的延时会达到 15ms;另一个系统的平均延时是 9ms,最大延时不会超过 10ms。那么第二种系统是满足要求的。
(4)性能测试工具
在性能优化过程中,性能测试工具是必不可少的。只有用性能测试工具对比测试出优化前后的性能指标,才能知道我们的优化是不是有效。性能测试工具,比如 linux 中的 ftrace,perf,是 linux 中自带的测试工具;还有现在比较热的 ebpf,也可以用来做性能测试;用于网络性能测试的 iperf 等。很多时候,性能测试也需要我们在代码中打桩,自己开发工具进行测试。
(5)软件架构和实现细节
在做软件性能优化时,不仅要优化软件的实现细节,有时候也需要优化架构方案。当然改变架构比改变细节的工作量要大一些。架构设计和方案的选择要尽量在架构和方案评审时做充分的验证与讨论,争取选择一个最优的架构,以防到后边再做大的修改。
2 epoll
epoll 作为一种多路复用技术,相比于 select 和 poll 来说,在很多场景下都是有性能提升的。
[linux][epoll] 带着 6 个问题深入理解 epoll
epoll 相对于 select 和 poll 的优化,相当于在架构上做了优化。有时候,对架构进行优化,不是在旧的架构上进行优化,而是新定义了一种机制。比如这里的 epoll 和 select 与 poll;比如开发语言中的 c 和 c++,c++ 在 c 的基础上增加了面向对象的编程,但是支持之后的语言已经不是 c 语言了,而是成为了 c++。
3 内存
很多优化工作都涉及到内存。
(1)内存池
如果使用的内存需要频繁的申请和释放,那么使用内存池比较合适,防止每次申请和释放内存都要和系统打交道。linux 内核中的 slab 就是内存池。我们在做应用开发时,有时也会自己写内存池。
内存池往往有两个变量,一个是内存池中每个 buffer 的长度,一个是内存池中 buffer 的数量。在实际使用中,实际数据的大小是不一样,我们需要根据数据大小的分布使用不同的内存池,比如长度小于 128 字节的就使用第一个内存池,小于 1024 字节就使用第二个内存池,小于 4096 字节就使用第三个内存池。
slab 是内核中使用的内存池,其中 kmalloc-8 到 kmalloc-8k 是内核默认创建的内存池,每个内存池中内存块的长度从 8B 到 8KB 不等。另外,内核中的其它子系统也可以使用 slab 创建自己的内存池,比如 task_struct 内存池,是用来管理 task_struct 的,一个 task_struct 代表一个线程;dentry 和 inode 内存池是文件系统用来保存 inode 和 dentry 的;tcp 相关的内存池用于 tcp 创建连接和断开连接的过程。
root@wyl-virtual-machine:/home/wyl# cat /proc/slabinfo
slabinfo - version: 2.1
# name <active_objs> <num_objs> <objsize> <objperslab> <pagesperslab> : tunables <limit> <batchcount> <sharedfactor> : slabdata <active_slabs> <num_slabs> <sharedavail>
isofs_inode_cache 147 147 656 49 8 : tunables 0 0 0 : slabdata 3 3 0
ext4_groupinfo_4k 840 840 144 56 2 : tunables 0 0 0 : slabdata 15 15 0
fsverity_info 0 0 248 66 4 : tunables 0 0 0 : slabdata 0 0 0
ip6-frags 0 0 184 44 2 : tunables 0 0 0 : slabdata 0 0 0
PINGv6 130 130 1216 26 8 : tunables 0 0 0 : slabdata 5 5 0
RAWv6 494 494 1216 26 8 : tunables 0 0 0 : slabdata 19 19 0
UDPv6 120 120 1344 24 8 : tunables 0 0 0 : slabdata 5 5 0
tw_sock_TCPv6 0 0 248 66 4 : tunables 0 0 0 : slabdata 0 0 0
request_sock_TCPv6 0 0 304 53 4 : tunables 0 0 0 : slabdata 0 0 0
TCPv6 104 104 2432 13 8 : tunables 0 0 0 : slabdata 8 8 0
kcopyd_job 0 0 3312 9 8 : tunables 0 0 0 : slabdata 0 0 0
dm_uevent 0 0 2632 12 8 : tunables 0 0 0 : slabdata 0 0 0
scsi_sense_cache 1536 1536 128 64 2 : tunables 0 0 0 : slabdata 24 24 0
mqueue_inode_cache 34 34 960 34 8 : tunables 0 0 0 : slabdata 1 1 0
fuse_request 168 168 144 56 2 : tunables 0 0 0 : slabdata 3 3 0
fuse_inode 117 117 832 39 8 : tunables 0 0 0 : slabdata 3 3 0
ecryptfs_key_record_cache 0 0 576 56 8 : tunables 0 0 0 : slabdata 0 0 0
ecryptfs_inode_cache 0 0 1024 32 8 : tunables 0 0 0 : slabdata 0 0 0
ecryptfs_file_cache 0 0 16 256 1 : tunables 0 0 0 : slabdata 0 0 0
ecryptfs_auth_tok_list_item 0 0 832 39 8 : tunables 0 0 0 : slabdata 0 0 0
fat_inode_cache 44 44 744 44 8 : tunables 0 0 0 : slabdata 1 1 0
fat_cache 0 0 40 102 1 : tunables 0 0 0 : slabdata 0 0 0
squashfs_inode_cache 414 414 704 46 8 : tunables 0 0 0 : slabdata 9 9 0
jbd2_journal_handle 340 340 48 85 1 : tunables 0 0 0 : slabdata 4 4 0
jbd2_journal_head 1020 1020 120 68 2 : tunables 0 0 0 : slabdata 15 15 0
jbd2_revoke_table_s 256 256 16 256 1 : tunables 0 0 0 : slabdata 1 1 0
ext4_inode_cache 10267 11513 1096 29 8 : tunables 0 0 0 : slabdata 397 397 0
ext4_allocation_context 256 256 128 64 2 : tunables 0 0 0 : slabdata 4 4 0
ext4_system_zone 102 102 40 102 1 : tunables 0 0 0 : slabdata 1 1 0
ext4_io_end 256 256 64 64 1 : tunables 0 0 0 : slabdata 4 4 0
ext4_pending_reservation 512 512 32 128 1 : tunables 0 0 0 : slabdata 4 4 0
ext4_extent_status 7752 7752 40 102 1 : tunables 0 0 0 : slabdata 76 76 0
mbcache 292 292 56 73 1 : tunables 0 0 0 : slabdata 4 4 0
userfaultfd_ctx_cache 0 0 192 42 2 : tunables 0 0 0 : slabdata 0 0 0
dnotify_struct 0 0 32 128 1 : tunables 0 0 0 : slabdata 0 0 0
pid_namespace 0 0 208 39 2 : tunables 0 0 0 : slabdata 0 0 0
UNIX 3810 3810 1088 30 8 : tunables 0 0 0 : slabdata 127 127 0
ip4-frags 0 0 200 40 2 : tunables 0 0 0 : slabdata 0 0 0
xfrm_state 0 0 704 46 8 : tunables 0 0 0 : slabdata 0 0 0
PING 0 0 1024 32 8 : tunables 0 0 0 : slabdata 0 0 0
RAW 832 832 1024 32 8 : tunables 0 0 0 : slabdata 26 26 0
tw_sock_TCP 66 66 248 66 4 : tunables 0 0 0 : slabdata 1 1 0
request_sock_TCP 53 53 304 53 4 : tunables 0 0 0 : slabdata 1 1 0
TCP 126 126 2240 14 8 : tunables 0 0 0 : slabdata 9 9 0
hugetlbfs_inode_cache 51 51 632 51 8 : tunables 0 0 0 : slabdata 1 1 0
dquot 256 256 256 64 4 : tunables 0 0 0 : slabdata 4 4 0
eventpoll_pwq 3080 3080 72 56 1 : tunables 0 0 0 : slabdata 55 55 0
dax_cache 42 42 768 42 8 : tunables 0 0 0 : slabdata 1 1 0
request_queue 60 60 2104 15 8 : tunables 0 0 0 : slabdata 4 4 0
biovec-max 112 112 4096 8 8 : tunables 0 0 0 : slabdata 14 14 0
biovec-128 64 64 2048 16 8 : tunables 0 0 0 : slabdata 4 4 0
biovec-64 128 128 1024 32 8 : tunables 0 0 0 : slabdata 4 4 0
khugepaged_mm_slot 0 0 112 36 1 : tunables 0 0 0 : slabdata 0 0 0
user_namespace 61 61 536 61 8 : tunables 0 0 0 : slabdata 1 1 0
uid_cache 256 256 128 64 2 : tunables 0 0 0 : slabdata 4 4 0
dmaengine-unmap-256 15 15 2112 15 8 : tunables 0 0 0 : slabdata 1 1 0
dmaengine-unmap-128 30 30 1088 30 8 : tunables 0 0 0 : slabdata 1 1 0
sock_inode_cache 5850 5850 832 39 8 : tunables 0 0 0 : slabdata 150 150 0
skbuff_ext_cache 168 168 192 42 2 : tunables 0 0 0 : slabdata 4 4 0
skbuff_fclone_cache 256 256 512 64 8 : tunables 0 0 0 : slabdata 4 4 0
skbuff_head_cache 10176 10432 256 64 4 : tunables 0 0 0 : slabdata 163 163 0
configfs_dir_cache 46 46 88 46 1 : tunables 0 0 0 : slabdata 1 1 0
file_lock_cache 148 148 216 37 2 : tunables 0 0 0 : slabdata 4 4 0
fsnotify_mark_connector 640 640 32 128 1 : tunables 0 0 0 : slabdata 5 5 0
net_namespace 18 18 4800 6 8 : tunables 0 0 0 : slabdata 3 3 0
task_delay_info 4743 4743 80 51 1 : tunables 0 0 0 : slabdata 93 93 0
taskstats 188 188 344 47 4 : tunables 0 0 0 : slabdata 4 4 0
proc_dir_entry 1050 1050 192 42 2 : tunables 0 0 0 : slabdata 25 25 0
pde_opener 4998 4998 40 102 1 : tunables 0 0 0 : slabdata 49 49 0
proc_inode_cache 12165 12816 680 48 8 : tunables 0 0 0 : slabdata 267 267 0
bdev_cache 156 156 832 39 8 : tunables 0 0 0 : slabdata 4 4 0
shmem_inode_cache 3061 3105 720 45 8 : tunables 0 0 0 : slabdata 69 69 0
kernfs_node_cache 117286 117480 136 60 2 : tunables 0 0 0 : slabdata 1958 1958 0
mnt_cache 1887 1887 320 51 4 : tunables 0 0 0 : slabdata 37 37 0
filp 38146 40064 256 64 4 : tunables 0 0 0 : slabdata 626 626 0
inode_cache 53611 54166 608 53 8 : tunables 0 0 0 : slabdata 1022 1022 0
dentry 81112 84840 192 42 2 : tunables 0 0 0 : slabdata 2020 2020 0
names_cache 40 40 4096 8 8 : tunables 0 0 0 : slabdata 5 5 0
iint_cache 0 0 128 64 2 : tunables 0 0 0 : slabdata 0 0 0
lsm_file_cache 7820 7820 24 170 1 : tunables 0 0 0 : slabdata 46 46 0
buffer_head 30797 31824 104 39 1 : tunables 0 0 0 : slabdata 816 816 0
uts_namespace 111 111 440 37 4 : tunables 0 0 0 : slabdata 3 3 0
nsproxy 292 292 56 73 1 : tunables 0 0 0 : slabdata 4 4 0
vm_area_struct 49493 50466 208 39 2 : tunables 0 0 0 : slabdata 1294 1294 0
files_cache 2438 2438 704 46 8 : tunables 0 0 0 : slabdata 53 53 0
signal_cache 2268 2268 1152 28 8 : tunables 0 0 0 : slabdata 81 81 0
sighand_cache 1200 1200 2112 15 8 : tunables 0 0 0 : slabdata 80 80 0
task_struct 1053 1135 6016 5 8 : tunables 0 0 0 : slabdata 227 227 0
cred_jar 6678 6678 192 42 2 : tunables 0 0 0 : slabdata 159 159 0
anon_vma_chain 37205 37632 64 64 1 : tunables 0 0 0 : slabdata 588 588 0
anon_vma 18853 19773 104 39 1 : tunables 0 0 0 : slabdata 507 507 0
pid 9728 9728 128 64 2 : tunables 0 0 0 : slabdata 152 152 0
Acpi-Operand 10696 10696 72 56 1 : tunables 0 0 0 : slabdata 191 191 0
Acpi-ParseExt 273 273 104 39 1 : tunables 0 0 0 : slabdata 7 7 0
Acpi-State 1020 1020 80 51 1 : tunables 0 0 0 : slabdata 20 20 0
numa_policy 186 186 264 62 4 : tunables 0 0 0 : slabdata 3 3 0
trace_event_file 1554 1554 96 42 1 : tunables 0 0 0 : slabdata 37 37 0
ftrace_event_field 11985 11985 48 85 1 : tunables 0 0 0 : slabdata 141 141 0
pool_workqueue 768 768 256 64 4 : tunables 0 0 0 : slabdata 12 12 0
radix_tree_node 12833 14784 584 56 8 : tunables 0 0 0 : slabdata 264 264 0
task_group 204 204 640 51 8 : tunables 0 0 0 : slabdata 4 4 0
mm_struct 1770 1770 1088 30 8 : tunables 0 0 0 : slabdata 59 59 0
vmap_area 2816 2816 64 64 1 : tunables 0 0 0 : slabdata 44 44 0
dma-kmalloc-8k 0 0 8192 4 8 : tunables 0 0 0 : slabdata 0 0 0
dma-kmalloc-4k 0 0 4096 8 8 : tunables 0 0 0 : slabdata 0 0 0
dma-kmalloc-2k 0 0 2048 16 8 : tunables 0 0 0 : slabdata 0 0 0
dma-kmalloc-1k 0 0 1024 32 8 : tunables 0 0 0 : slabdata 0 0 0
dma-kmalloc-512 0 0 512 64 8 : tunables 0 0 0 : slabdata 0 0 0
dma-kmalloc-256 0 0 256 64 4 : tunables 0 0 0 : slabdata 0 0 0
dma-kmalloc-128 0 0 128 64 2 : tunables 0 0 0 : slabdata 0 0 0
dma-kmalloc-64 0 0 64 64 1 : tunables 0 0 0 : slabdata 0 0 0
dma-kmalloc-32 0 0 32 128 1 : tunables 0 0 0 : slabdata 0 0 0
dma-kmalloc-16 0 0 16 256 1 : tunables 0 0 0 : slabdata 0 0 0
dma-kmalloc-8 0 0 8 512 1 : tunables 0 0 0 : slabdata 0 0 0
dma-kmalloc-192 0 0 192 42 2 : tunables 0 0 0 : slabdata 0 0 0
dma-kmalloc-96 0 0 96 42 1 : tunables 0 0 0 : slabdata 0 0 0
kmalloc-rcl-8k 0 0 8192 4 8 : tunables 0 0 0 : slabdata 0 0 0
kmalloc-rcl-4k 0 0 4096 8 8 : tunables 0 0 0 : slabdata 0 0 0
kmalloc-rcl-2k 0 0 2048 16 8 : tunables 0 0 0 : slabdata 0 0 0
kmalloc-rcl-1k 0 0 1024 32 8 : tunables 0 0 0 : slabdata 0 0 0
kmalloc-rcl-512 0 0 512 64 8 : tunables 0 0 0 : slabdata 0 0 0
kmalloc-rcl-256 0 0 256 64 4 : tunables 0 0 0 : slabdata 0 0 0
kmalloc-rcl-192 0 0 192 42 2 : tunables 0 0 0 : slabdata 0 0 0
kmalloc-rcl-128 960 960 128 64 2 : tunables 0 0 0 : slabdata 15 15 0
kmalloc-rcl-96 1303 1344 96 42 1 : tunables 0 0 0 : slabdata 32 32 0
kmalloc-rcl-64 4892 5376 64 64 1 : tunables 0 0 0 : slabdata 84 84 0
kmalloc-rcl-32 0 0 32 128 1 : tunables 0 0 0 : slabdata 0 0 0
kmalloc-rcl-16 0 0 16 256 1 : tunables 0 0 0 : slabdata 0 0 0
kmalloc-rcl-8 0 0 8 512 1 : tunables 0 0 0 : slabdata 0 0 0
kmalloc-8k 392 392 8192 4 8 : tunables 0 0 0 : slabdata 98 98 0
kmalloc-4k 2510 2552 4096 8 8 : tunables 0 0 0 : slabdata 319 319 0
kmalloc-2k 2224 2224 2048 16 8 : tunables 0 0 0 : slabdata 139 139 0
kmalloc-1k 5792 5792 1024 32 8 : tunables 0 0 0 : slabdata 181 181 0
kmalloc-512 31872 31872 512 64 8 : tunables 0 0 0 : slabdata 498 498 0
kmalloc-256 4352 4352 256 64 4 : tunables 0 0 0 : slabdata 68 68 0
kmalloc-192 4284 4284 192 42 2 : tunables 0 0 0 : slabdata 102 102 0
kmalloc-128 2240 2240 128 64 2 : tunables 0 0 0 : slabdata 35 35 0
kmalloc-96 3864 3864 96 42 1 : tunables 0 0 0 : slabdata 92 92 0
kmalloc-64 23352 23552 64 64 1 : tunables 0 0 0 : slabdata 368 368 0
kmalloc-32 35968 35968 32 128 1 : tunables 0 0 0 : slabdata 281 281 0
kmalloc-16 14848 14848 16 256 1 : tunables 0 0 0 : slabdata 58 58 0
kmalloc-8 16896 16896 8 512 1 : tunables 0 0 0 : slabdata 33 33 0
kmem_cache_node 2112 2112 64 64 1 : tunables 0 0 0 : slabdata 33 33 0
kmem_cache 1980 1980 448 36 4 : tunables 0 0 0 : slabdata 55 55 0
root@wyl-virtual-machine:/home/wyl#
(2)内存池的内存如何从系统申请 ?
假如我们要创建一个内存池,这个内存池中的每个 buffer 大小是 1024 个字节的长度,共申请 128 个 buffer。那么我们是每个 buffer 单独申请,还是申请总大小为 1024 * 128 内存,然后再自己做切分。当然是选择后者,因为后者申请的内存是一整块内存,这些内存都挨着,一方面可以减少内存的碎片化,一方面可以增加内存的缓存命中率(内存挨着,从缓存 LRU 算法的角度来看的话,可以提升缓存的命中率)。
(3)内存池的本地化
在网络应用中,往往给每个 cpu 核都申请内存池,这样每个核都有自己的内存池,在申请或释放内存的时候不用加锁,对性能友好。
(4)cpu cache 和文件系统 page cache
因为 cpu 的速度远远高于内存的访问速度,访问内存的速度又远远高于访问磁盘的速度,所以在 cpu 和内存之间以及 cpu 和磁盘之间都加了缓存。这两个缓存没有任何关系,只不过都起到了提高性能的作用,思路是类似的,所以放在这里一起说。
cpu cache 是在 cpu 和内存之间,用来增大内存访问速度的,cpu cache 硬件本身就在 cpu 中。page cache 是对访问文件系统的优化,本身使用的是内存,防止每次读写文件都要直接读写磁盘,提高文件系统的读写性能。
(5)大页
操作系统管理内存的基本单位是页,常用的页的大小是 4KB。
我们程序使用的地址都是虚拟地址,cpu 在访问内存的时候首先要将虚拟地址转换为物理地址,承担这个工作的就是 MMU,MMU 是一种硬件,集成在 cpu 中。在地址转换时,首先要查页表,页表一般缓存在 TLB 里,如果不在 TLB 里,那么就去内存中查找页表,从页表中可以找到虚拟地址对应的物理内存。如果同样大小的内存,比如 16MB,如果页的大小都是 4KB,那么 TLB 中要保存 4K 个页表项,如果页的大小是 16MB,那么 TLB 中只要保存一个页表项就可以了。TLB 一般很小,不会保存太多的页表项,这就是大页的好处,可以提升 TLB 的命中率。
(6)减少内存拷贝
减少内存拷贝很常见的,也是很容易想到的一个性能优化点。
4 cpu
提高 cpu 调度性能的方法主要有两个:
(1)调度策略和优先级
改变线程的调度策略,比如从SCHED_NORMAL 改成 SCHED_FIFO,或者提高线程的调度优先级,比如优先级从 1 提高到 99,可以提升调度性能。
linux 调度策略的几点理解_属于linux实时调度策略-CSDN博客
(2)线程绑核甚至独占
将线程绑定到指定核上,这样这个线程就只能运行在这个 cpu 核上,这样可以提升线程使用内存的 cache 命中率。试想,如果线程一会运行在一个核上,一会又换到另一个核上运行,那么当线程换到另外一个核上运行的时候,访问的内存需要重新加载到 cache(一级 cache ),一级 cache 一般是每个 cpu 核独有的。
绑核可以提高缓存的命中率,但是并不一定能提升线程的调度性能,如果有很多个线程都绑定在了同一个核上,那么这么多个线程都要在这一个核上排队调度,性能也不会高。要想让自己的目标线程的调度性能高,还要让线程独占,独占的话,就是把自己的目标线程绑定到一个核上,把其它的线程绑定到其它核上,这样就保证了独占。