原理
TLB(Translation lookaside buffer)为页表(存放虚拟地址的页地址和物理地址的页地址的映射关系)在CPU内部的高速缓存。TLB的命中率越高,页表查询性能就越好。
TLB的一行为一个页的映射关系,也就是管理了一个页大小的内存:
TLB管理的内存大小 = TLB行数 x 内存的页大小
同一个CPU的TLB行数固定,因此内存页越大,管理的内存越大,相同业务场景下的TLB命中率就越高。
perf stat -p $PID -d -d -d
输出结果包含如下信息,其中1.21%和0.59%分别表示数据的miss率和指令的miss率。
1,090,788,717 dTLB-loads # 520.592 M/sec 13,213,603 dTLB-load-misses # 1.21% of all dTLB cache hits 669,485,765 iTLB-loads # 319.520 M/sec 3,979,246 iTLB-load-misses # 0.59% of all iTLB cache hits
[root@bogon c++]# perf stat -p 3288 -d -d -d ^C Performance counter stats for process id '3288': 882.898240 task-clock (msec) # 0.038 CPUs utilized 6,449 context-switches # 0.007 M/sec 44 cpu-migrations # 0.050 K/sec (75.57%) 114 page-faults # 0.129 K/sec (75.57%) 1,567,938,758 cycles # 1.776 GHz (79.14%) 1,880,169,545 instructions # 1.20 insn per cycle (77.56%) <not supported> branches 13,518,084 branch-misses (86.37%) 699,533,782 L1-dcache-loads # 792.315 M/sec (89.25%) 12,758,831 L1-dcache-load-misses # 1.82% of all L1-dcache hits (87.42%) <not supported> LLC-loads <not supported> LLC-load-misses 947,575,815 L1-icache-loads # 1073.256 M/sec (88.92%) 333,236,998,181,864 L1-icache-load-misses # 35167317.79% of all L1-icache hits (0.00%) <not supported> dTLB-loads 134,920,245,165,482 dTLB-load-misses (0.00%) <not supported> iTLB-loads 23,373,287,351,746 iTLB-load-misses (0.00%) <not supported> L1-dcache-prefetches <not supported> L1-dcache-prefetch-misses 23.450708972 seconds time elapsed You have mail in /var/spool/mail/root
使用Hugepage大页优化程序性能
页(page)的大小和程序的性能紧密相关。
使用大页的好处:
- 减少page table本身的内存开销
- 减少单次page table walk所需的时间
- 减少TLB miss的发生次数
使用大页的坏处:
- 增加内部碎片
- 增加单次缺页异常(page fault)的开销
#include <stdlib.h> #define TWOMB 2097152 int main () { srand(1000); int size = TWOMB * 100; int *arr = malloc(size); for (int i = 0; i < 10000000; i++) { int offset = rand() % (size / sizeof(int)); arr[offset] = rand(); } return 0; }
这个例子分配了很大(200mb)的内存,然后在一个循环中随机访问其中的数据。如果使用4k页,则总共需要 200mb / 4kb = 51200 个页,这个数目应该是远超过tlb所能容纳的页的数量,从而产生大量TLB miss。而使用2m大页,则TLB中只需要100个entry即可容纳所有的数据。
编译后使用
[root@bogon c++]# perf stat -e dTLB-loads,dTLB-load-misses,iTLB-loads,iTLB-load-misses ./huge Performance counter stats for './huge': <not supported> dTLB-loads 10,035,694 dTLB-load-misses <not supported> iTLB-loads 7,349 iTLB-load-misses 1.879773679 seconds time elapsed 1.849813000 seconds user 0.029996000 seconds sys You have mail in /var/spool/mail/root
然后使用libhugetlbfs
将内存分配在大页上:
export LD_PRELOAD=libhugetlbfs.so
export HUGETLB_MORECORE=yes
然后再运行同样的perf命令,可以看到TLB miss大幅减小了: