量化投资与机器学习微信公众号,是业内垂直于量化投资、对冲基金、Fintech、人工智能、大数据等领域的主流自媒体。公众号拥有来自公募、私募、券商、期货、银行、保险、高校等行业30W+关注者,荣获2021年度AMMA优秀品牌力、优秀洞察力大奖,连续2年被腾讯云+社区评选为“年度最佳作者”。
延迟通常是算法交易中的一个关键因素。在HRT,我们一直努力在最小化交易栈的延迟上。低延迟优化可能是晦涩难懂的,但幸运的是,有许多非常好的指南和文档可以开始使用。有一个不会经常深入讨论但非常重要的方面是大内存页(Hugepages)和转译后备缓冲器(Translation Lookaside Buffer,TLB)的作用。在本系列文章中,我们将解释它们是什么,为什么它们重要,以及如何使用它们。我们将关注运行在64位X86硬件上的 Linux 操作系统,但是大多数观点也适用于其他体系结构。这一系列的文章是相对技术性的,需要对操作系统概念(如内存管理)以及一些硬件细节(如 CPU 缓存)有一些高层次的理解。在第一篇文章中,我们将解释Hugepages的好处。在第二篇文章中,我们将解释如何在生产环境中使用它们。内存管理101
硬件和操作系统以块的形式处理内存。这些小块叫做页面(pages)。例如,当操作系统分配或交换内存时,内存是以页为单位进行的。在64位x86体系结构中,默认的页面大小为4 KB (KiB)。但是 x86 64位 CPU 还支持其他两种页面大小,Linux称之为Hugepages: 2MiB 和1GiB(1GiB页面也被称为巨型页面)。为了简单起见,本系列文章主要关注2MiB 页面。1GiB 页面也很有帮助,但是它们太大了,所以往往有更专门的用例。内存地址映射快速入门
当常规程序运行时,它们使用虚拟地址访问内存。这些地址通常只在当前进程中有效。硬件和操作系统协作将这些映射到物理内存(RAM)中的实际物理地址。这种翻译是每页完成的。由于程序只能看到虚拟地址,因此在每次访问内存时,硬件必须将程序可见的虚拟地址转换为物理 RAM 地址(如果虚拟地址确实由物理内存支持的话)。内存访问意味着从处理器加载或存储数据或指令,而不管它们是否被缓存。
操作系统将这些转换存储在一个称为页表的数据结构中,硬件也能理解这种数据结构。对于每个由真实内存支持的虚拟页,页表中的一个条目包含相应的物理地址。对于机器上运行的每个进程,页表通常是唯一的。为什么访问页表可能会显著增加延迟?
除非程序的分配器和/或操作系统设置为使用Hugepages,否则内存将由4KiB 页面支持。X86上的页表使用多个层次结构级别。因此,在页表中查找4 KiB 页的物理地址至少需要3个相关的内存负载。缓存将用于尝试实现这些功能(类似于任何常规的内存访问)。但是让我们假设所有这些加载都是未缓存的,并且需要来自内存。使用70ns 作为内存延迟,我们的内存访问已经有70 * 3 = 210纳秒的延迟ーー而且我们甚至还没有尝试获取数据!进入转译后备缓冲器
CPU 设计人员非常清楚这个问题,并提出了一系列优化来减少地址转换的延迟。我们在这篇文章中关注的具体优化是转译后备缓冲器(TLB)。TLB 是地址转换信息的硬件缓存。它包含页表中许多最近访问的条目的最新副本(最好是当前进程的页表中的所有条目)。正如访问 CPU 缓存比访问内存快一样,在 TLB 中查找条目比在页面表中搜索要快得多。当 CPU 找到它在 TLB 中寻找的转换时,它被称为 TLB Hit。如果没有,这是一个 TLB Miss。但是,就像常规的 CPU 缓存一样,TLB 的大小是有限的。对于许多需要大量内存的进程,整个页表的信息将放不进 TLB。TLB 有多大?
一个不错的经验法则是,最近的服务器 x86 CPU 的 TLB 为每个核心大约1500-2000个条目(例如,cpuid 可以用来为 CPU 显示这些信息)。因此,对于使用4KiB 页面的进程,TLB 可以缓存翻译,以覆盖2000(TLB 中的条目数)* 4KiB(页面大小)字节,即8MiB 值的内存。这比许多程序的工作集小得多。甚至 CPU 缓存通常也要大得多!现在,假设我们使用的是Hugepagesー我们的 TLB 可以包含2000 * 2MiB = ~ 4GiB 的转换信息。这样好多了。Hugepages的其他好处
一个Hugepage占用的内存是4KiB 页面的512倍。这意味着对于相同的工作集,页表中的条目数也比使用常规页时少512倍。这不仅大大减少了页表使用的内存量,而且使表本身更容易缓存。此外,2MiB 的页表格式比普通页简单,因此查找需要更少的内存访问。因此,即使一个由Hugepages支持的程序在地址转换时遇到了TLB miss,页表查找也会比普通页面快得多(甚至是显著地快)。测试与对比
如果没有一个完全人为的基准,任何关于优化的帖子都是不完整的。我们编写了一个简单的程序,分配一个32GiB 的双精度数字阵列。然后从这个数组中添加1.3亿个随机双精度数(完整的源代码在这里可以找到)。在第一次运行时,程序在数组中生成一个随机的索引列表,然后将它们存储在一个文件中。随后的运行将读取该文件,因此在每次运行期间内存访问将是相同的。我们在一台空闲的 Intel Alder Lake 机器上运行这个程序。当使用Hugepages时,程序初始化部分的基准时间要快40% 。数组是线性初始化的,这是硬件的最佳情况,因此加速效果不会很明显。但是,当进行随机访问以添加双精度数时,运行时会减少4.5倍。请注意,随着程序中的小更改或使用不同的编译器,运行的秒数可能会有很大的不同。然而,Hugepages的性能改进仍然十分明显。什么时候不应该使用Hugepages
Hugepages 一种优化。就像任何其他优化一样,它们可能适用于工作负载,也可能不适用于工作负载。基准管理对于确定是否值得投入时间来建立它们非常重要。在本系列的第二篇文章中,我们将详细介绍如何使用它们,并列出一些实质性的警告。结论
在每次访问代码或数据的内存时,硬件将虚拟地址转换为物理地址。使用Hugepages可以显著地加速这种映射。Hugepages还允许 TLB 覆盖大量内存。理想情况下,我们希望我们的整个工作集可由 TLB 映射,而不需要转到页表。这减少了内存访问的延迟,也释放了 CPU 缓存中的一些空间(不再需要包含那么多缓存页表条目)。