注:转载于https://www.cnblogs.com/jimbo17/p/8987443.html,部分内容有修改
1、NUMA(Non-Uniform Memory Access) 非一致内存访问及NODE
该模型假定给定CPU对不同内存的单元访问时间不一样。NUMA架构下,CPU平均划分为多个Node,每个Node有自己的内存控制器及内存插槽。CPU访问自己Node上的内存时速度快,而访问其他CPU所关联Node的内存的速度比较慢。
通过以下方式查看NUMA系统信息,
[root@localhost ~]# numactl -H
available: 2 nodes (0-1)
node 0 cpus: 0 1 2 3 4 5 6 7 8 9 10 11 24 25 26 27 28 29 30 31 32 33 34 35
node 0 size: 130539 MB
node 0 free: 11053 MB
node 1 cpus: 12 13 14 15 16 17 18 19 20 21 22 23 36 37 38 39 40 41 42 43 44 45 46 47
node 1 size: 131072 MB
node 1 free: 14420 MB
node distances:
node 0 1
0: 10 21
1: 21 10
由上可见,系统有两个node,其中node distances即表示node间内存访问的速度级别。跨node的访问速度比本node慢两倍多。
NUMA内存分配策略:
- default:仅在本地节点分配,即程序运行节点
- bind:仅在指定节点上分配
- interleavel:在所有节点交叉分配
- preferred:先在指定节点上分配,失败则可在其他节点上分配
NUMA存在的问题:
来源:https://www.cnblogs.com/klb561/p/9053692.html
MySQL是单进程多线程架构数据库,当numa采用默认内存分配策略时,MySQL进程会被并且仅仅会被分配到numa的一个节点上去。假设这个节点的本地内存为10GB,而MySQL配置20GB内存,超出节点本地内存部分(20GB-10GB)Linux会使用swap而不是使用其他节点的物理内存。在这种情况下,能观察到虽然系统总的可用内存还未用完,但是MySQL进程已经开始在使用swap了。这就导致数据库性能变差。
如果单机只运行一个MySQL实例,可以选择关闭numa,关闭nuam有两种方法:
1.硬件层,在BIOS中设置关闭;
2.OS内核,启动时设置numa=off。
不过最新的MySQL已经从代码层面解决了该问题,
https://www.kancloud.cn/taobaomysql/monthly/67064
2、ZONE
ZONE的出现是由于硬件约束:
- ISA总线的DMA只能范围内存的前16M
- X86-32系统由于线性地址空间太少,无法访问所有物理内存(2^32=4GB),需要借助HIGHMEM机制
因此有以下ZONE,
- ZONE_DMA,地址最低的16M内存,归DMA设备使用
- ZONE_DMA32,该Zone用于支持32-bits地址总线的DMA设备,只在64-bits系统里才有效
- ZONE_NORMAL,该Zone的内存被内核直接映射为线性地址并可以直接使用。在X86-32架构下,该Zone对应的地址范围为16MB~896MB。在X86-64架构下,DMA和DMA32之外的内存全部在NORMAL的Zone里管理
- ZONE_HIGHMEM,该Zone只在32位系统才有,通过建立临时页表的方式映射超过896MB的内存空间
查看系统当前各个node的ZONE管理区
[root@localhost ~]# cat /proc/zoneinfo |grep -E "zone| free|managed"
Node 0, zone DMA
pages free 3973
managed 3975
Node 0, zone DMA32
pages free 127782
managed 314664
Node 0, zone Normal
pages free 1710485
managed 32506279
Node 1, zone Normal
pages free 2688883
managed 33028971
可见,Node0上有DMA、DMA32和Normal三个Zone,Node1上只有一个Normal Zone。
3、Page(页)
Page是Linux底层内存管理的基本单位,大小为4KB。一个Page映射为一段连续的物理内存,内存的分配和释放都要以Page为单位进行。进程虚拟地址到物理地址的映射也是通过Page Table页表进行,页表的每一项记录一个Page的虚拟地址对应的物理地址。
4、TLB(转换检测缓冲区)——硬件级缓存
内存访问时需要查找地址对应的Page结构,这个数据记录在页表里。所有对内存地址的访问都要先查询页表,因此页表的访问次数是频率最高的。为了提高对页表的访问速度,引入了TLB(Translation Lookaside Buffer)机制,将访问较多页表缓存在CPU的cache里。因此CPU的性能统计里很重要的一项就是L1/L2 cache的TLB miss统计项。在内存较大的系统里,如256GB内存全量的页表项有256GB/4KB=67108864条,每个条目占用16字节的话,需要1GB,显然是CPU cache无法全量缓存的。这时候如果访问的内存范围较广很容易出现TLB miss导致访问延时的增加。
5、Hugepages(大页)
为了降低TLB miss的概率,Linux引入了Hugepages机制,可以设定Page大小为2MB或者1GB。2MB的Hugepages机制下,同样256GB内存需要的页表项降低为256GB/2MB=131072,仅需要2MB。因此Hugepages的页表可以全量缓存在CPU cache中。 通过sysctl -w vm.nr_hugepages=1024可以设置hugepages的个数为1024,总大小为4GB。需要注意是,设置huagepages会从系统申请连续2MB的内存块并进行保留(不能用于正常内存申请),如果系统运行一段时间导致内存碎片较多时,再申请hugepages会失败。
如下所示为hugepages的设置和mount方法,mount之后应用程序需要在mount路径下通过mmap进行文件映射来使用这些hugepages。
sysctl -w vm.nr_hugepages=1024
mkdir -p /mnt/hugepages
mount -t hugetlbfs hugetlbfs /mnt/hugepages
6、Buddy System(伙伴系统)
Linux Buddy System是为了解决以Page为单位的内存分配导致外内存碎片问题:即系统缺少连续的Page页导致需要连续Page页的内存申请无法得到满足。原理很简单,将不同个数的连续Pages组合成Block进行分配,Block按2的幂次方个Pages划分为11个Block链表,分别对应1,2,4,8,16,32,64,128,256,512和1024个连续的Pages。调用Buddy System进行内存分配时,根据申请的大小找最合适的Block。
如下所示为各个Zone上的Buddy System基本信息,后面11列为11个Block链表里可用的Block个数。
[root@localhost ~]# cat /proc/buddyinfo
Node 0, zone DMA 1 0 1 0 2 1 1 0 1 1 3
Node 0, zone DMA32 769 3912 1663 368 949 339 249 139 195 0 0
Node 0, zone Normal 46347 63606 19435 267024 28762 522 5 0 0 0 0
Node 1, zone Normal 47237 62462 144525 345185 21462 375 23 3 0 0 0
7、Slab
Buddy System的内存都是大块申请,但是大多数应用需要的内存都很小,比如常见的几百个Bytes的数据结构,如果也申请一个Page,将会非常浪费。为了满足小而不规则的内存分配需求,Linux设计了Slab分配器。原理简单说就是为特定的数据结构建立memcache,从Buddy System里申请Pages,将每个Page按数据结构的大小划分为多个Objects,使用者从memcache里申请数据结构时分配一个Object。
可以通过/proc/slabinfo查看系统slab信息,
[root@localhost ~]# cat /proc/slabinfo |head
slabinfo - version: 2.1
# name <active_objs> <num_objs> <objsize> <objperslab> <pagesperslab> : tunables <limit> <batchcount> <sharedfactor> : slabdata <active_slabs> <num_slabs> <sharedavail>
wstcplog_time 0 0 3392 1 1 : tunables 0 0 0 : slabdata 0 0 0
wstcplog_req 0 0 3264 1 1 : tunables 0 0 0 : slabdata 0 0 0
wstcplog_fmt 0 0 1280 3 1 : tunables 0 0 0 : slabdata 0 0 0
ws_get_snd_item 391514 396646 1472 2 1 : tunables 0 0 0 : slabdata 198323 198323 0
wip_recver 0 0 2176 1 1 : tunables 0 0 0 : slabdata 0 0 0
wip_sender 0 0 320 12 1 : tunables 0 0 0 : slabdata 0 0 0
wip_sock 0 0 832 4 1 : tunables 0 0 0 : slabdata 0 0 0
ext4_groupinfo_4k 60937 61080 136 30 1 : tunables 0 0 0 : slabdata 2036 2036 0
也可通过slabtop查看排序后的slab信息,
[root@localhost ~]# slabtop -s c -o | head -n15
Active / Total Objects (% used) : 93008521 / 111244867 (83.6%)
Active / Total Slabs (% used) : 8164760 / 8164760 (100.0%)
Active / Total Caches (% used) : 72 / 104 (69.2%)
Active / Total Size (% used) : 24807655.46K / 27493165.03K (90.2%)
Minimum / Average / Maximum Object : 0.01K / 0.25K / 8.00K
OBJS ACTIVE USE OBJ SIZE SLABS OBJ/SLAB CACHE SIZE NAME
14600118 14596164 99% 1.01K 4866706 3 19466824K ext4_inode_cache
27971412 16389388 58% 0.19K 1331972 21 5327888K dentry
34800831 31395601 90% 0.10K 892329 39 3569316K buffer_head
3002482 2996544 99% 0.57K 428926 7 1715704K radix_tree_node
21322182 18353309 86% 0.04K 209041 102 836164K ext4_extent_status
387656 384687 99% 1.44K 193828 2 775312K ws_get_snd_item
6908715 6908715 100% 0.05K 81279 85 325116K shared_policy_node
290706 280743 96% 0.64K 48451 6 193804K proc_inode_cache
8、kmalloc
和glibc的malloc()一样,内核也提供kmalloc()用于分配任意大小的内存空间。同样,如果放任应用程序随意从Page里申请任意大小的内存也会导致Page内存碎片化。为了解决内部碎片问题,Linux使用Slab机制来实现kmalloc内存分配。原理和Buddy System类似,即创建2的幂次方的Slab池用于kmalloc根据大小适配最佳的Slab进行分配。
如下所示为用于kmalloc分配的Slabs:
[root@localhost ~]# cat /proc/slabinfo | grep kmalloc | grep -v dma
kmalloc-8192 236 245 8192 1 2 : tunables 0 0 0 : slabdata 245 245 0
kmalloc-4096 3619 3627 4096 1 1 : tunables 0 0 0 : slabdata 3627 3627 0
kmalloc-2048 7030 7060 2048 2 1 : tunables 0 0 0 : slabdata 3530 3530 0
kmalloc-1024 12560 12768 1024 4 1 : tunables 0 0 0 : slabdata 3192 3192 0
kmalloc-512 13922 15088 512 8 1 : tunables 0 0 0 : slabdata 1886 1886 0
kmalloc-256 111151 135504 256 16 1 : tunables 0 0 0 : slabdata 8469 8469 0
kmalloc-192 126473 134337 192 21 1 : tunables 0 0 0 : slabdata 6397 6397 0
kmalloc-128 47745 56160 128 32 1 : tunables 0 0 0 : slabdata 1755 1755 0
kmalloc-96 56495 56742 96 42 1 : tunables 0 0 0 : slabdata 1351 1351 0
kmalloc-64 296432 301440 64 64 1 : tunables 0 0 0 : slabdata 4710 4710 0
kmalloc-32 93781 94976 32 128 1 : tunables 0 0 0 : slabdata 742 742 0
kmalloc-16 68352 68352 16 256 1 : tunables 0 0 0 : slabdata 267 267 0
kmalloc-8 37376 37376 8 512 1 : tunables 0 0 0 : slabdata 73 73 0