Linux透明大页机制在云上大规模集群实践介绍

3,879 阅读17分钟

图片

作者 | 第二天太阳、凯文神父

导读:本文探索和研究Linux内存透明大页相关技术,优化内存分配,提升服务性能,节省机器成本。通过设计完整的透明大页机制,实现在云上大规模机器集群快速推广应用,取得CPU性能和时延优化10%+显著双收益。本文总结透明大页技术架构机制实践经验,介绍如何在保证服务稳定性前提下,取得多场景性能收益,希望感兴趣的读者能有所启发。

全文8456字,预计阅读时间22分钟。

01 背景

随着业务和计算机硬件技术不断发展,出现越来越大的内存机器(>=700G),满足复杂计算处理需求,同时Linux内核的同步持续迭代升级,操作系统可以支持现代硬件架构的大页面容量功能,以适应越来越大的系统内存。

在云上大规模机器集群中,传统内存页面管理机制存在一定的性能问题,以典型推荐系统服务为例,一方面单次请求产生的中间数据规模较大,另一方面程序广泛应用了本地词典&缓存等技术,这使响应单次请求所需要涉及的内存访存范围显著增大。通过perf统计线上部分典型程序的dtlb_load_misses.walk_active和dtlb_store_misses.walk_active的占比,也观测到页表缓存命中率不足的表现,平均观测大多程序有5%-10%的时钟周期存在未命中导致陷入Page Walk的现象。

目前在机器硬件大内存和系统内核技术都具备情况下,透明大页(Transparent Huge Page)内存管理应用成为可能。通过增大页面大小,预期可以显著解决页表缓存命中不足的现象,进而普遍提升程序性能,但是由于存在若干缺陷,主要集中在内存用量显著上涨,频繁回收重整带来额外开销增加以及不可控的随机卡顿。基于这些因素,目前业界对透明大页机制的一般认知均偏负向,排查问题追到透明大页,尚无确切结论说明对服务影响情况,这也是应用透明大页技术一道难题。

02 大页内存技术演进

内存作为计算机上非常重要的一种资源,程序加载运行、CPU指令和数据获取等都依赖内存使用,因此内存访问性能是影响计算机性能的一个很重要的因素。现代Linux系统上内存的分配主要过程如下,见图1:

1、应用程序通过调用内存分配函数(malloc, free, realloc, calloc),系统调用brk或者mmap进行内存分配,申请虚拟内存地址空间。

2、虚拟内存至物理内存映射处理过程,通过请求MMU分配单元,根据虚拟地址计算出该地址所属的页面,再根据页面映射表的起始地址计算出该页面映射表(PageTable)项所在的物理地址,根据物理地址在高速缓存的TLB中寻找该表项的内容,如果该表项不在TLB中,就从内存将其内容装载到TLB中。

其中:

虚拟内存(Virtual Memory:现代操作系统普遍使用的一种技术,每个进程有用独立的逻辑地址空间,内存被分为大小相等的多个块,称为页(Page).每个页都是一段连续的地址,对应物理内存上的一块称为页框,通常页和页框大小相等。

MMU(Memory-Management Unit:内存管理单元,负责管理虚拟地址到物理地址的内存映射,实现各个用户进程都拥有自己的独立的地址空间,提供硬件机制的内存访问权限检查,保护每个进程所用的内存不会被其他的进程所破坏。

PageTable: 虚拟内存至物理内存页面映射关系存储单元。

TLB(Translation Lookaside Buffer:高速虚拟地址映射缓存, 主要为了提升MMU地址映射处理效率,加了缓存机制,如果存在即可直接取出映射地址供使用。

图片

图 1 Linux内存分配机制(虚拟+物理映射)

2.1 标准大页(HugePage)

标准大页Huge pages 通过提升单位页面的大小,替代传统的 4KB 内存页面,以适应越来越大的系统内存,让操作系统可以支持现代硬件架构的大页面容量功能,降低MMU管理开销,提升内存处理性能。主要有两种格式大小:2MB 和1GB ,2MB块大小适合GB 大小的内存, 1GB 页块大小适合用于 TB 级别的内存;2MB 是默认的页大小。

优点:

  • 无需交换:不存在页面由于内存空间不足而换入换出的问题。

  • 减轻 TLB Cache 的压力:相同的内存大小,管理的虚拟地址数量变少,降低了 CPU Cache 可缓存的地址映射压力。

  • 降低 Page Table 负载:内存页的数量会减少,从而需要更少的Page table,节约了页表所占用的内存数量。

  • 消除Page table查找负载::因为页面不需要替换,所以不需要页表查找。

  • 提高内存的整体性能:页面变大后,处理的页面较少,因此可以明显避免访问页表时可能出现的瓶颈。

缺点:

  • 需要合理设置,避免内存浪费:在操作系统启动期间被动态分配并被保留,共享内存不会被置换,在使用HugePage的内存不能被其他的进程使用,所以要合理设置该值,避免造成内存浪费。

  • 静态设置无法自适应:如果增加HugePage或添加物理内存或者是当前服务器增加了新的实例发生变化,需要重新设置所需的HugePage大小。

使用方式:

1)标准大页查看机器开启情况

可以通过以下命令查看大页开启情况

grep Huge /proc/meminfo
HugePages_Total:       0
HugePages_Free:        0
HugePages_Rsvd:        0
HugePages_Surp:        0
Hugepagesize:       2048 kB

2)大页设置

通过设置 vm.nr_hugepages 参数的值修改大页数量,大页分配需要连续的内存,机器长期运行存在内存碎片,可能不满足分配需要,一般开机启动时就需要分配设置,如以下命令设置大页配置:

sysctl -w vm.nr_hugepages=20

3)大页使用

应用需要通过mmap系统调用或者shmat和shmget系统调用适配才可以使用大页,一般的应用进程都是不可以访问此大页空间。

2.2 透明大页(Transparent Huge Pages)

标准大页可带来性能提升,但存在配置和管理上的困难,很难手动管理,而且通常需要对代码进行重大的更改才能有效使用,不能通用适配应用的接入使用,从RHEL 6 开始引入透明大页技术(kernel >=kernel-2.6.32),在标准大页的基础上,通过一个抽象层,能够自动创建、管理和使用传统大页,用来提高内存管理的性能,解决大页手动管理的问题,透明大页为系统管理员和开发人员减少了很多使用传统大页的复杂性。

优点:

  • 相对标准大页,操作系统动态管理大页分配,支持大页分配 ,大页->普通页换出拆分,根据具体应用使用情况,自适应调整分配。

  • 使用上对应用透明,且开源社区持续测试、优化和适配大部分应用,同时支持多种大页申请方式配置,满足多场景的分配需求

缺点:

  • THP在运行时动态分配内存,可能会带来运行时内存分配的CPU开销和上涨问题。

  • THP同步申请模式下,分配失败时触发申请时,导致应用的一些随机卡顿现象。

使用方式:

透明大页生效方式:对匿名内存的透明Hugepage支持可以完全禁用,也可以仅在MADV_HUGEPAGE区域内启用(避免消耗更多内存资源的风险),也可以在系统范围内启用,可以通过以下方法之一实现:

echo always >/sys/kernel/mm/transparent_hugepage/enabled
echo madvise >/sys/kernel/mm/transparent_hugepage/enabled
echo never >/sys/kernel/mm/transparent_hugepage/enabled

大页内存页面碎片整理控制:

/sys/kernel/mm/transparent_hugepage/defrag

大页内存整理模式:

图片

具体使用如下:

echo always >/sys/kernel/mm/transparent_hugepage/defrag
echo defer >/sys/kernel/mm/transparent_hugepage/defrag
echo defer+madvise >/sys/kernel/mm/transparent_hugepage/defrag
echo madvise >/sys/kernel/mm/transparent_hugepage/defrag
echo never >/sys/kernel/mm/transparent_hugepage/defrag

整体内存页面技术汇总:普通页,标准大页和透明大页主要技术特征和优缺点情况如下表:

图片

03 整体解决方案

大规模在线服务集群开启透明大页,实践中面临一些问题和挑战:

1、大规模机器集群,多应用混布下,环境复杂,多数程序未很好适配大页分配,如何控制大页生效方式,对混布应用影响可控。

2、如何提升应用接入大页机制效率,降低业务接入成本,实现通用推广。

根据云上大规模集群的机器硬件情况,内核版本和服务混布的特点,整体大页机制技术方案设计,从机器内核(Machine & Kernel),混布容器(container)和应用服务(Applicatoin)三个层级考虑,保障机器大页环境,实现容器内随应用定制开启和关闭大页功能,同时兼容默认的标准页面,在接入上基于内存分配库考虑通用接入,对业务方透明。下面主要从三个层级介绍大页机制设计思路:

图片

图 2 大页机制技术架构

3.1 机器内核层(Kernel)

在机器内核层面,大页使用主要有透明大页(Transparent Huge Page)和普通大页(Hupgetlb)两种方式,透明大页具体工作模式分为显式开启(Explicit Enable)和隐式开启(Implicit Enable);申请方式上有异步申请(Async Defrag)和同步申请(Sync Defrag)。实践中,根据云上集群混布&大部分服务常驻进程特点,选择透明大页 + 显式开启 + 异步申请模式方式,具体技术选型说明如下:

  • 透明大页(THP)

普通大页hugetlb虽然没有动态重整开销,但是目前必须开机预留,在逐步推广期透明大页自适应性更强。

  • 显式开启(Explicit Enable)

大多数应用依赖的malloc并未良好适应透明大页,会因为碎片带来更大的内地冗余和更大的重整开销,显式开启可以随程序malloc机制升级逐步开启,也实现混布环境对其它应用影响可控。

  • 异步申请(Async Defrag)

同步重整会在重启期带来明显卡顿,运行期也可能会有零星随机卡顿,异步模式可以尽可能避免卡顿发生,由于集群几乎都是常驻进程,异步模式一般也足以提供充足的大页填充率。

机器内核参数设置

开启:

echo 'never' >/sys/kernel/mm/transparent_hugepage/defrag
echo 'madvise' >/sys/kernel/mm/transparent_hugepage/enabled

关闭:

echo never > /sys/kernel/mm/transparent_hugepage/enabled
echo never > /sys/kernel/mm/transparent_hugepage/defrag

使用情况:

#整机查看
grep AnonHugePages /proc/meminfo 
AnonHugePages:    612832 kB
#进程查看
cat /proc/pid/smaps | awk '/AnonHugePages/{h+=$2}/Anonymous/{a+=$2}END{print a, h, h/a}'
#大页使用和申请情况
egrep 'trans|thp' /proc/vmstat
nr_anon_transparent_hugepages 2018
thp_fault_alloc 6021
thp_fault_fallback 0
thp_collapse_alloc 201
thp_collapse_alloc_failed 0
thp_split 22

3.2 Pass容器层(Container)

云上集群的应用部署,在物理机器之上,主要通过Pass化容器虚拟化技术,实现相互间资源和使用隔离,保障应用独立干净的运行环境,目前透明大页对机器操作系统内核版本有要求(Kernels >= kernel-2.6.32),这样需要对开启大页的服务,支持按内核版本调度部署,保障大页的运行环境,其次对于PageCache场景添加安全监控和相应的处理机制,具体说明如下:

  • 亲和部署(Kernel Version Affinity)

透明大页异步重整,依赖高版本内核,在集群多版本内核并存情况下,容器可通过亲和内核部署保障大页环境;这样既可以降低集群全升级内核资源依赖,实现部分服务快速应用大页,还可以通过同步逐渐升内核支持更多大页环境,实现逐步全覆盖。

  • 代码锁定(mlock)

代码段锁定mlock机制,部分机器PageCache短缺,可能异步重整进一步回收会引起代码段淘汰带来卡顿,目前落地观察不明显。

3.3 应用服务层(Application)

应用服务层,接入大页需要对应用的内存分配做兼容处理,目前内存使用上,为了性能上的考虑,主要间接通过内存库使用内存,我们对内存库做了针对性优化,对业务使用透明,实现应用通用的快速接入,其次对应数据和词典类服务可通过直接大页使用方式接入,减少内存库管理的开销,进一步提升处理性能。具体说明如下:

  • 通用业务逻辑(Common Business)

优化内存库适应大页分配,接口上统一通过环境变量的方式,控制应用的大页是否开启,提升常规内存使用性能,降低接入成本,实现应用透明快速接入。

  • 大块内存消费(DB&Dict类)精细化管理

对于大内存消费场景,比如数据库类或者词典类服务,消费的内存块大小比较固定,这样可以通过自主mmap&madvise使用大页方式,减少通过内存库带来的额外开销,追求极致性能优化。

3.4 辅助设施(Assistant)

为了更好的把控和了解机器和应用透明大页使用情况,设计了完善的透明大页监控机制,实现有效的性能指标监控,也方便支持对大页内核参数的相关调优。

  • 大页使用率

通用大页监控库Lib支持任意服务的通用接入,填补非云上环境的监控需求;

云上容器内嵌透明大页监控,透明实现对应用内存大页覆盖率指标查看。

  • THP内核调优

建立机器粒度的内核大页Khugepaged进程消耗指标监控;

通过优化调整大页内核参数,综合Khugepaged重整大页时间和资源消耗,保障应用较佳的大页使用率。

04 关键技术

工程实践中,通过大页内核参数调优+优化内存库,让应用充分使用大页内存,最大化发挥大页内存处理性能;同时优化内存使用,控制内存涨幅在合理范围。主要有如下两个方面优化目标:

  • 提升大页覆盖率

由于机器混布环境和异步分配大页的机制,不一定保障机器内存大页足够分配使用,如何调优大页内核参数和优化内存库,提升应用大页使用率。如测试中发现系统THP默认参数非最佳,JeMalloc内存库存在大页使用上的不足地方,可优化实现使用大页性能最大化。

  • 降低内存上涨幅度

对于页面从标准的4KB提升至2MB大小, 未做很好适配的应用,可能存在一定的内存涨幅,通过调整内存库参数,降低开启大页内存带来的内存上涨开销,控制在合理的10%左右范围。

5.1 大页相关参数调优

4.1 大页相关参数调优

结合内核大页参数和Malloc库参数,提升服务大页分配覆盖率和减少内存开销。

1.透明大页内核参数调优

通过优化相关内核参数,综合CPU开销和重整速度,提升大页分配效率,内核大页管理进程khugepaged进行大页的分配、聚合和拆分等操作,主要参数如下:

alloc_sleep_millisecs:整理一次碎片间隔,如果本次分配大页失败,下一次尝试分配时等待时长,单位毫秒。

max_ptes_none:指定将一组小页面合并为一个大页面时,可以分配多少额外的小页面数量。

pages_to_scan:每次扫描的页面数量。

scan_sleep_millisecs:扫描间隔,单位毫秒。

对应常驻的服务,可以通过如下参数组合配置,实现大页重整在较小开销下,保障较好的大页覆盖率。

echo '100' >/sys/kernel/mm/transparent_hugepage/khugepaged/alloc_sleep_millisecs
echo '511' > /sys/kernel/mm/transparent_hugepage/khugepaged/max_ptes_none
echo '2048' >/sys/kernel/mm/transparent_hugepage/khugepaged/pages_to_scan
echo '100' >/sys/kernel/mm/transparent_hugepage/khugepaged/scan_sleep_millisecs

2.Malloc库分配参数调优

以JeMalloc为例,默认初始化内存块(arena)大小, 是机器的CPU核数*4,在进程启动后无法动态修改,在实践中,通常情况应用无需这么多内存块,一般根据逻辑核数量配置即可,这样可以优化大页内存下的内存开销。

4.2 应用层内存池化技术

通过内存池化,进一步提升大页的使用效率,减少碎片内存长期占用导致的内存上涨,优化降低内存使用,目前主要从以下两个方面开展优化。

  • 内存池-长生命周期碎片内存

通过内存池技术,整块内存连续分配管理,避免反复申请和释放,解决长期cache场景下,小内存碎片长期滞留,导致内存上涨较多情况。

  • 自定义大页-大块内存申请使用场景

程序内有大块内存操作使用,大小较固定,如DB类,词典服务等,大块内存使用THP madvise自行开发管理,小内存使用Malloc库,进一步提升性能。

自定义大页,主要使用步骤:

1. HugePage 大小对齐申请

size_t mem_size = num*(2UL <<20); //2MB 倍数
auto* ptr = (char*)mmap(nullptr, mem_size, PROT_READ | PROT_WRITE, MAP_PRIVATE|MAP_ANON, -1, 0);

2. MADVISE区域标记使用

通过接口 int madvise(void *addr, size_t length, int advice) 使用标记MADV_HUGEPAGE大页内存,如:

int ret =  madvise(ptr, mem_size, MADV_HUGEPAGE);
if (ret != 0) {
  //fail 
  std::cout << "fail ret:" << ret;
  return -1;
}
//success todo

3. HugePage 大小对齐使用 & 释放

使用:可以自行管理这段num*HugePage大小内存使用,尽量凑齐到HugePage大小倍数使用

释放:保证HugePage大小倍数释放,这样减少大页被拆分,导致下次申请失败,触发异步重整操作,带来一定机器性能开销。

05 模块开启大页性能测试情况

以推荐系统中一个服务模块为例,通过实际业务场景流量压测,对比大页版本vs普通版本性能指标,从主要的底层cache性能和进程性能两方面进行分析评比:

  • 内存Cache miss指标

通过perf工具统计进程读写换页(dtlb_load_misses.walk_active和dtlb_store_misses.walk_active)下降66%+,具体指令情况如下表:

图片

  • 进程性能指标

统计服务进程的CPU和处理时延指标,大页版本实现CPU 平均优化11.3% ,平均时延优化11.2%,具体见下表:

图片

06 总结

通过研究透明大页技术优化内存分配,提升服务性能,并在云上大规模集群落地,实践证明是一个行之有效的性能优化手段。目前透明大页机制在百度核心推荐系统全面落地,覆盖推荐系统所有主要模块,性能收益显著,取得CPU优化10%+,时延降低10%+。

作为后续,本项目也在不断探索大规模C++服务优化的最佳实践,也欢迎志同道合者共同探讨。

参考资料:

www.kernel.org/doc/html/la…

jemalloc.net/jemalloc.3.…

linux.die.net/man/2/madvi…

-------END--------

推荐阅读:

百度直播iOS SDK平台化输出改造

百度APP 基于Pipeline as Code的持续集成实践

Go 语言使用 MySQL 的常见故障分析和应对方法

百度交易中台之钱包系统架构浅析

基于宽表的数据建模应用