性能:如何提高iTLB(指令地址映射)的命中率

引入

开发项目时,当程序开发完成后,生成的二进制程序需要部署到服务器上并运行。运行这个程序时,我们会不断衡量各种性能指标。而生产实践中,我们经常发现一个问题:*指令地址映射的命中率太低(High iTLB miss rate),导致程序运行不够快。那怎么解决这个问题呢?

在这里插入图片描述

为什么要关注iTLB的命中率

iTLB命中率低会导致CPU性能不高。这里的iTLB就是Instruction Translation Lookaside Buffer,也就是指令转换后备缓冲区。iTLB命中率不高,就会导致CPU无法高效运行。

那么TLB(转换后备缓冲区)又起到了什么作用呢?

  • 我们知道,在虚拟内存管理中,内核需要维护一个地址映射表,将虚拟内存地址映射到实际的物理地址,对于每个内存里的页表操作,内核都需要加载相关的地址映射。在x86计算机体系中,使用内存的页表(page table),来存储虚拟内存和物理内存之间的内存映射的。但是,内存页表的访问,相对于CPU的运算速度,是远远不够快的
  • 所以,为了能进行快速的虚拟到物理地址转换,TLB(转换后备缓冲区)这种专门的硬件就被发明了出来,它可以作为内存页表的缓存。TLB有两种:数据TLB(Data)和指令TLB(Instruction),也就是iTBL和dTLB。因为处理器的大小限制,这两者的大小,也就是条目数,都不是很大。只能存储为数不多的地址条目。

为什么说TLB的命中率很重要呢?

  • 因为,内存页表的访问时延比TLB高很多;因此命中TLB的地址转换,比未命中TLB也就快得多。因为CPU每时每刻都在执行指令,所以iTLB的性能尤为关键。
  • iTLB未命中率,是衡量因iTLB未命中率而导致的性能损失的度量标准
  • 当iTLB命中率低时,CPU将花费大量周期来处理未命中,这导致指令执行速度变慢

具体来讲,iTLB 命中和不命中之间的访问延迟,差异可能是 10 到 100 倍。命中的话,仅需要 1 个时钟周期,而不命中,就需要 10-100 个时钟周期,因此 iTLB 不命中的代价是极高的。

我们可以用具体的数据来感受一下。假设这两种情况分别需要 1 和 60 个时钟周期,未命中率为 1%,将导致平均访问延迟为 1.59 个周期,相比全部命中的情况(即 1 个周期)的访问延迟,足足高出 59%。

如何提高指令地址映射的命中率

优化软件的二进制文件

优化软件的二进制文件可以提高TLB命中率。

一般而言,根据编译源代码的不同阶段(即编译、链接等阶段),分别存在三种优化方法。这样的例子包括优化编译器选项,来对函数进行重新排序,以便将经常调用的所谓“热函数”放置在一起,或者使用 FDO(Feedback-Directed Optimization,就是基于反馈的优化)来减少代码区域的大小

FDO是什么呢?简单来说,就是把一个程序放在生产环境中运行,剖析真实的生产数据,并且用这些信息来对这个程序进行精准的优化。比如,可以确切知道在生产环境中,每个函数的调用频率。

那么要如何进行二进制的优化呢?

我们可以通过编译优化来将频繁访问的指令汇总在一起,放在二进制文件中的同一个地方,以提高空间局部性,这样就可以提高iTLB命中。这块防止频繁访问指令的区域,就叫做热区域(hot text)。

在热区域的指令,它们的提取和领取会更快的完成。

具体的二进制优化过程,包括以下三大步骤:

  • 首先,通过分析正在运行的二进制文件来识别热指令。我们可以用Linux的perf工具来达成此目的。有两种方法识别:堆栈跟踪,LBR(Last Branch Record,最后分支记录)。LBR比较适宜,是因为它的好处是能提高数据质量,并减少数据占用量
  • 其次,根据函数的访问频率,对配置文件函数进行排序。我们可以使用名为HFSort的工具,来为热函数创建优化表单
  • 最后,链接器脚本将根据访问顺序,优化二进制文件中的函数布局。

采用大页面

现代计算机系统,除了传统的4KB页面大小之外,通常还支持更大的页面大小,比如x86_64上分为为2MB和1GB。这两种页面都叫做大页面。使用较大的页面好处是,减少了覆盖二进制文件的工作集所需的TLB条目数,从而用较少的页面表就可以覆盖所有用到的地址,也就相应的降低了采用页面表地址转换的成本

在Linux上,有两种获取大页面的方法:

  • 手工:预先为应用程序预留大页面
  • 自动:使用透明大页面,也就是THP(Transparent Huge Pages)

· THP,是由操作系统来自动管理大页面,不需要用户去预留大页面。THP的显著优点是不需要对应用程序做任何修改;但是也有缺点,就是不能保证大页面的可用性。预留大页面的方式,则需要在启动内核时应用配置。假设我们想保留64个大页面,每个2MB,就用下面的配置:

hugepagesz = 2MB, hugepages = 64

我们在服务器上运行程序时,需要将相应的二进制文件加载到内存中。二进制文件由一组函数指令组成,它们共同位于二进制文件的文本段中,每个页面都尝试占用一个iTLB条目来进行虚拟到物理页面的转换。

如果内存页(比如4KB)很小,那么对于一定大小的程序,需要加载的内存页就较多,内核会加载更多的映射表条目,而这会降低性能。通常在执行过程中,我们使用4KB的普通页面。入股使用“大内存页”,页面变大了(比如 2MB 是 4KB 的 512 倍),自然所需要的页面就变少了,也就大大减少了由内核加载的映射表的数量。这样就提高了内核级别的性能,最终提升了应用程序的性能,最终提升了应用程序的性能。这也就是大页面为什么会被引入的原因。

由于服务器通常只有数量有限的iTLB条目,如果文本段太大,大于iTLB条目可以覆盖的范围,则会发生iTLB不命中。

例如,Intel HasWell架构中 4KB 页面有 128 个条目,每个条目覆盖 4KB,总共只能覆盖 512KB 大小的文本段。如果应用程序大于512KB,就会有iTLB不命中,从而需要去访问内存的地址映射表,这就比较慢了。iTLB未命中的处理是计入CPU使用时间的,所以等带访问内存地址映射的过程,就实际上浪费了CPU时间。

改进方法是,使用大页面来装载程序的热文件区域。通过在大页面上放置热文本,可以进一步提升iTLB命中率。使用大页面iTLB条目时,单个TLB条目覆盖的代码是标准4K页面的512倍。

更重要的是,当代的CPU体系结构,通常为大页面提供一些单独的TLB条目,如果我们不适用大页面,这些条目将处于空闲状态。所以,通过使用大页面,也可以充分利用那些TLB条目

如何获得最佳优化结果?

我们总共提出了两个方案,就是采用热文本和采用大页面放置。这两个其实是互补的优化方案,它们可以独立工作,也可以整合起来一起作用,这样可以获得最佳的优化结果。

采用热文本和大页面放置的传统方法需要多个步骤,比如在链接阶段,将源代码和配置文件数据混合在一起,并进行各种手动配置和刷新,这就导致整个过程非常复杂。这样的整合方案也就很难广泛应用到所有的系统中。

我们在生产中构建了一个流程,来自动化整个过程。这样,该解决方案就成为能被几乎所有服务简单采用的方案,而且几乎是免维护的解决方案。这个解决方案的流程图如下,整个系统包含三大模块:程序剖析(Profiling)、编译链接(Linking)和加载部署(loading)。

在这里插入图片描述

程序剖析

剖析模块在图的顶部。这个模块定期,比如每周执行一次数据收集作业,以剖析测量正在运行的服务。

Dataswarm 是一个的数据收集框架,这是 Facebook 自己开发和使用的数据存储和处理的解决方案。这个作业剖析了服务的运行信息(例如,热函数),并且对配置文件进行控制以使其开销很小。最后,它会把分析好的数据发送到名为Everstore的永久存储,其实这是一个基于磁盘的存储服务。

编译连接

在构建服务包时,链接程序脚本会从Everstore检索已配置的热函数,并根据配置文件,对二进制文件中的功能进行重新排序。这个模块的运行结果就是已经优化的二进制程序。

加载部署

加载服务二进制文件时,操作系统会尽最大努力,在大页面上放置热文本。如果没有可用的大页面,则放在常规内存页面上。

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值