解读 SOSP‘23 硬核论文 Mira:程序行为感知的远端内存访问优化

112 篇文章 5 订阅
70 篇文章 4 订阅

source:  存储前沿技术

Zhiyuan Guo, Zijian He, Yiying Zhang

University of California San Diego

https://dl.acm.org/doi/10.1145/3600006.3613157

引言:我们早就计划解读Mira这篇文章,并已阅读了部分内容。然而,我们一直感觉理解还不够深入,无法达到撰写解读文章的水平。Mira 确实是一篇很硬核、难读的文章,其中的思想容易理解,但工程难度非常高,设计实现细节繁多。基于我们在阅读此文过程中遇到的挑战,我们认为仅凭文章所呈现的文字,很难完全理解这项工作,只有通过查看源代码才能深入了解。然而,目前Mira的代码文档尚不完善,接近2万行的代码要梳理清楚也需要很长时间。因此,本文作为一个引子,将主编们对这篇文章的初步理解呈现给同行们,希望感兴趣的读者能自行深入研究,必定会有不小的收获。

1. 背景和问题

1.1 为什么远端内存重要

在一个宽泛的意义上,远端内存包括远端服务器上的内存[1],分离式节点上的内存[2],以及比本地内存慢一些的闪存[3]。远端内存系统可以提供更大的内存空间,使得数据中心能够处理更大规模的数据。这对于需要大量内存的应用(如大数据分析、机器学习等)来说非常重要。此外,远端内存系统还可以提高数据中心的资源利用率,因为它们可以根据需要动态地分配和回收内存资源[4]。总的来说,远端内存旨在解决本地内存的“容量墙”问题。

1.2 现有远端内存方案及问题

主要方案问题
透明的内存页面交换,比如FastSwap [5]4KB粒度的页面交换导致严重的数据放大问题
新编程模型,比如AIFM [6]远端内存的分配和访问需要程序员手动编程解决,非应用透明

此外,两类方案都对程序语义缺乏深层次形式化的感知(编程模型方案对程序语义只有静态感知,缺乏运行时动态感知),无法利用多样化的应用特征和内存访问模式来深入优化应用性能。

2 Mira概述

动机:解决远端内存访问的数据放大问题,兼顾现有两类方案的优点,以应用透明的方式实现远程内存的分配和访问,无需程序员介入,达到甚至超越程序员显式编程所能获得的性能。

新方法:程序分析可以揭示有关程序如何访问数据及利用内存的模式,从而优化内存访问。

2.1 两类程序分析

基于插桩的运行时动态统计 (Dynamic Profiling): 在运行时系统中进行的传统分析可能会增加相当高的性能开销,这对Mira的分析目标并不必要。Mira在编译期间对分析代码进行插桩,仅对函数级别或分配站点的粗粒度缓存部分性能进行分析。Mira的大部分分析都与cache section的行为有关(例如,错失率,错失延迟,命中开销)。只有在发生非本地缓存事件时,才收集这些指标,从而保持本地内存访问的完整性,并实现轻量级的分析。Profiling的主要目标是收缩范围,确定哪些关键函数需要进行进一步细致的程序静态代码分析。

静态代码分析 (Static Analysis): Mira进行静态程序分析,以推断目标函数的内存访问模式,包括它们的生命周期、访问顺序、访问粒度、读取或写入的访问,以及哪些数据经常一起访问。然后,Mira将这些Analysis结果与Profiling结果综合使用,以确定用于应用程序执行的各种cache section (local cache的进一步划分,每个section适配不同的访问模式) 配置:cache line大小以及cache section结构。

静态代码分析的简单示例,下面的for循环涉及间接内存访问:
for (i=0; i< size; i++) B[A[i]]++;
进行程序分析之后,编译器可以插入预取操作:
%1=(fetch A[i+distance]) 并且提前distance个元素 fetch B[%1].

注:静态代码分析是软件工程方面的成熟技术。对其基本原理和工作机制感兴趣的读者可以进一步了解cppcheck开源工具https://cppcheck.sourceforge.io

图片

图1:Mira基本工作流程。

2.2 Mira工作流程

Mira是一个包括程序分析工具、编译器、运行时系统、性能剖析系统在内的综合性系统设计,它能为不同的程序适配不同的最优系统配置以优化远程内存访问。Mira的基本工作流程如图1所示。

1) 初始执行:最初,缺乏运行时信息或程序分析,Mira将本地缓存配置为通用交换区,并在其中放置所有堆对象和静态数据。初始执行几乎与传统的基于页面交换的系统一样,只是编译器插入了性能profiling代码。

2) 程序Profiling:Profiling的重点是识别受当前缓存配置影响最大的函数,并寻找更大的对象以放置在它们自己的cache section中。

3) 函数代码Analysis:基于初始执行的profiling结果,Mira 的程序分析器和编译器接收函数代码作为输入,使用生命周期分析、profiling的网络性能和内存访问顺序来确定cache structure和cache section size。

4) 代码生成、优化和编译:code range被编译以访问cache section,或者,在缓存未命中的情况下,访问远程内存。

5) 程序执行:分析完程序后,Mira 运行时基础架构开始执行它并管理系统中各个内存层之间的内存资源分配。  

6) 近数据处理:Mira基于计算负载和网络流量来确定将哪些函数卸载到远程内存以获得最佳性能。具体通过远程过程调用(RPC)实现。

另外,在某些情况下,Mira可能会采用地址空间迁移将应用程序的部分地址空间迁移到远程内存,以将系统的主内存腾初来缓存访问更加频繁的对象,以减少远程内存访问延迟;以及根据程序的运行时行为优化不同内存层之间的数据移动,包括预取数据以减少延迟,迁移内存页面以最大限度地减少远程内存访问延迟。Mira还会对采样输入进行性能剖析,如果性能下降,它会触发代码优化的下一轮迭代。

Mira的目标应用包括静态分析和动态剖析可以推断的内存访问模式的应用,主要包括数据分析、机器学习和图处理。

3 Mira设计实现

3.1 设计

Mira设计部分提炼了8大设计点:5个核心 + 3个支撑。

设计点1:通过动态profiling确定cache section划分。这个设计主要为了达成两个目标:确定哪些函数需要进行进一步的静态代码分析,以及local cache分为哪几个cache sections。一次profiling完成后,Mira收集所有函数的cache开销和执行时间,对所有函数的cache性能开销进行比较,选出开销最高的前10%函数,以及函数中涉及的前10%的大内存对象(heap objects)进行进一步的静态代码分析。

在对选定的函数和对象进行分析,了解它们的访问模式之后,Mira将相似模式的对象分组到一个区域,并将不同模式的对象留在不同的区域。这意味着如果多个对象的访问模式相似,那么它们可以在同一区域中,这使得以section为配置单位进行cache管理可以获得更高的cache命中率。

设计点2:通过静态analysis确定cache section结构和cache line大小。对选定的代码区域执行静态程序分析,以推断访问模式并确定缓存配置。缓存行大小(cache line size)是根据访问粒度和网络传输效率确定的,并考虑到昂贵的指针解除引用过程。Mira支持直接映射、组关联、全关联等多种缓存段结构(cache section structure);根据程序范围分析和局部性集(locality set)识别进行选择。为不同cache section选择正确的缓存结构会显着影响性能。

设计点3:确定cache section的size。这个设计主要为了确定cache资源的分配。缓存区大小对远程内存系统的性能有显著影响。不同的对象和访问模式可能会受到本地缓存量的不同影响。顺序和跨步的缓存区仅需要较小的大小来隐藏网络延迟。section size是由大小和性能之间的样本测试取得的关系来决定的。Mira使用整数线性规划(ILP)来最小化总的缓存开销。

设计点4:远端代码转换。这个设计将所有指向non-swap区域对象的指针转换为remote 指针,并将这些指针相关的allocate、load、store操作转换为对应的remote APIs。Mira的编译器为非交换缓存区(non-swap cache section)中的对象生成显式远程操作,使得更多的本地内存能作为频繁访问的远程对象的cache,以提高应用程序的性能。如果在发生远程指针解引用时缓存行位于缓存中,那么远程指针解引用就可以直接转换为本地内存加载指令。Mira编译器通过维护已访问缓存行的本地内存地址来优化缓存查找过程。与AIFM的基于库的远程操作实现相比,Mira的实现具有更少的运行时开销和需要更少的元数据。

设计点5:程序优化。这部分包含多个设计点,都是为了提高程序的总体性能。编译器通过包括自适应预取、驱逐提示(eviction hints)、选择性传输、数据访问批处理和读/写优化等方法来优化代码。通过使用程序分析来预测未来的数据访问,预取技术得以增强,从而减少了远程内存数据的开销。通过程序引导的行刷新和标记,驱逐提示能提高本地缓存的利用率。有选择地传输通过使用程序分析,降低了从远程内存获取不必要数据的频率。通过识别多个可访问的地址,数据访问批处理将它们打包成单一的消息。读/写优化通过丢弃局部缓存的只读模式对象和避免获取只写模式的对象,提高了性能。

另外,Mira还描述了3个支撑性的设计点:多线程,自适应的单双边通信方法,以及近数据处理的函数自动卸载。

3.2 实现

Mira在MLIR的基础上,使用C++编写了7.7K行代码,实现了程序分析和编译器。Mira运行时库的实现基于C++编写了12.1K行代码,包括在本地节点和远程内存节点上运行两部分代码。目前,Mira只能在一个计算节点和一个内存节点上运行。通过将Mira与分布式内存管理层(例如LegoOS[58]中使用的那种)整合,就可以支持多个内存节点或内存池。在这种情况下,Mira会决定卸载(offload)哪些对象和函数,而分布式内存管理器则决定将其卸载到哪个内存节点。

4 实验评估

图片

图片

图片

Mira的评估包括整体应用性能提升和模块级别的分析。这里仅展示整体性能。Mira 使用三种不同的应用程序进行了测试:DataFrame、MCF 和 GPT-2,它们代表常见的数据中心应用程序类型。Mira 在管理远内存使用方面的性能与其他三个系统进行了比较:AIFM、FastSwap 和 Leap。

  • Mira在分离和定制缓存区,并有效利用预取功能上胜过FastSwap和Leap。

  • AIFM由于指针解引用而产生了显著的运行时开销,比其它系统运行速度慢。

  • 即使本地内存大小显著缩小,Mira的性能也能保持稳定,这主要归功于精确的预取和驱逐提示。

  • 没有程序行为知识的FastSwap和Leap在本地内存大小缩小时会出现严重的性能下降。

  • 即使对于像MCF这样复杂的应用程序,Mira也可以配置最优缓存,并作出正确的预取/驱逐决策。

  • 与AIFM相比,Mira维护的元数据较少,避免了高开销的指针解引用。

  • Mira的迭代优化方法能在更少的迭代中实现最佳性能。

  • 在分析和编译的范围中减少,使得即使针对大程序,Mira的运行速度也更快,并且它的分析开销显著降低。

5 论文评述

这篇文章的引言 Introduction 章节清晰明了,如同教科书一般。在第一段内,文章强调了远端内存的重要性;第二段阐述了现有的所有方案都有缺点;在第三段中,作者通过易于理解的例子和引人注意的叙述方式,表明他们提出的方案的强大之处;第四段则与现有的技术进行了比较,讨论了本文设计与现有技术的差异、机会以及挑战;文章在第五段揭示了Mira核心思想是基于何种观察,如何借助机会来克服挑战;第六段介绍了实施的具体步骤;第七段探讨了实施过程中的挑战以及如何克服这些挑战;最后作者讲述了工程化的实施情况。尽管全文并未总结出文章的贡献,但这些贡献已经跃然纸上。这篇文章的Introduction是一个极好的模板,你的文章就大致按这个套路写,准没错。

文章的设计并不都是关键的,但在总共八个设计点中,有一半是核心要点,其余的则为其整体框架提供了有力的支撑。Mira的设计部分有一个非常规动作,那就是通过一个简单而易于理解的例子对几个关键设计点(如分离缓存部分、不同的缓存行大小等)所带来的优点进行量化测试。这个测试方式和后面对每个设计点对端到端性能影响的评估有显著的区别,因此并不显得冗余。作者的意图可能是让读者在设计阶段就能对设计点带来的优点有直观而深刻的理解。

主体评估部分简洁明了。对于在顶级会议上的论文来说,这是一个很难处理的部分。由于我们常常担忧数据不足、测试不充分,会堆砌大量的数据,填充了许多内容。然而,Mira的评估部分只用了不到三页,就全面展示了其性能,包括端到端性能和模块拆解评估两部分。这证明了立意突出、原理明了的情况下,评估部分无需填充过多的内容。评估的关键是展现整体性能提升,以及明确解释每个设计点对总体性能提升的贡献。

项目的实施难度非常高,给人极强的碾压感。这篇文章一般人写是写不出来的,作为读者,是否有足够的耐心理解实现细节都是一个问题。论文讲述的问题十分重要,解决方案极其彻底,效果显著,实施则需要高超的技术。对于这篇文章,除了欣赏学习,无话可说。

此文价值在源码。尽管Mira的代码库已经开源,但是仍在建设之中,文档还没有完善,期待其全版本的代码能够发布。

未来影响。在CXL时代,远端内存的应用将无所不在,Mira的思想和工程化实践经验在许多场合都可能成为我们的指路明灯。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值