摘要

最近内存技术的进步意味着商品机器可能很快就会有TB级的内存;然而,这种机器在今天仍然很昂贵且少见。因此,很少有程序员和研究人员能够针对可伸缩性问题进行调试和原型修复,或者探索由TB级内存引起的新系统行为。

为了对这种机器能够快速、早期的原型化和探索其系统软件,我们建立了0sim模拟器,并将其开源。0sim使用虚拟化来模拟在普通机器上执行的大规模工作负载。我们的关键观察是,无论输入是什么,许多工作负载都遵循相同的控制流。我们称这种工作负载为数据无关的。0sim利用数据无关性,通过内存压缩使大型模拟变得可行和快速。

0sim对于许多任务来说足够精确,并且可以模拟一个比主机大20-30倍的客户系统,对于我们观察到的工作负载,它的速度会有8-100倍的减缓,而可压缩的工作负载运行得更快。例如,我们在31GB机器上模拟1TB机器,在160GB机器上模拟4TB机器。我们通过实例来说明0sim的实用性。例如,我们发现对于混合工作负载,尽管有几十GB的空闲内存,Linux内核仍然会创建不可修复的碎片,并且我们使用0sim来调试运行在大内存上的memcached的意外失败。

1. 引言

自从计算机出现以来,计算机内存的大小一直在增长。随着内存容量呈指数级增长(例如,IBM的System/360的架构限制为28MB),过去严格限制最大内存量的设计面临着巨大的困难。随着技术的进步,存储器的密度大大增加,成本大大降低,这种增长趋势将继续下去。最近,英特尔的3D Xpoint内存支持高达6TB的双插槽机器。因此,多TB的系统可能会变得普遍,为未来拥有几十到几百TB内存的系统铺平道路。

迫切需要研究大内存系统(多个TB或更多)上的系统软件和应用程序的可伸缩性,为增加内存容量做准备。虽然在今天可以容忍,但是当内存增加10-100倍时,许多常见操作系统算法的线性计算和空间开销可能是不可接受的。其他系统软件,如公共语言运行库和垃圾收集器也需要重新设计,以便在多TB内存系统上有效运行。在Linux中,许多可伸缩性问题对于小内存是可以容忍的,但在更大的规模下则是痛苦的。例如:

  • 对于4TB的内存,Linux在启动时需要超过30秒来初始化页面元数据,这降低了需要重新启动时的可用性。
  • 在大型系统上,内核元数据增长到多个GB。在异构系统上,元数据可能会压倒较小的快速的内存。
  • 线性复杂度的内存管理算法(如内存回收和碎片整理)在内存扩展10倍时可能会导致显著的性能开销,我们稍后将说明这一点。
  • 大页面对于大型系统上的TLB性能至关重要,但是以前发现形成大页面的内存碎片整理会导致某些应用程序出现较大的延迟峰值。
  • 为小内存构建的内存管理策略(例如允许固定百分比的缓存文件内容是脏的)在大内存中表现很差,当这个小百分比由几百GB组成时。

我们预计,当内存规模增长到TB级甚至更高时,系统设计师将遇到新的可伸缩性问题。但是,探索具有巨大内存的系统行为、再现可伸缩性问题和原型解决方案需要开发人员以巨大的成本拥有或租用一个系统。4TB实例的云服务每小时成本超过25美元。较大的实例需要三年的合同,费用超过$780000。

为此,我们构建并开源了0sim(“zero·sim”),这是一个基于虚拟化的平台,用于在多TB的机器上模拟系统软件行为。0sim运行在一个商用主机(平台)上,并为用户提供一个虚拟机(模拟目标),该虚拟机具有巨大的物理内存。0sim足够快,可以进行大规模的、长时间运行的模拟,甚至可以进行直接的交互,既可以进行性能测量,也可以进行可伸缩性问题的交互调试。

我们采用了几种新技术将TB级的目标内存内容放入GB级的平台内存中。首先,我们注意到许多程序是数据无关的:它们执行相同的计算而不依赖于输入值。因此,我们可以使用预先确定的输入来执行目标;那么,0sim就可以将预先确定内容的4KB页面压缩为1位。其次,当前的处理器可能有较小的物理地址空间来简化CPU设计。我们使用软件虚拟化技术来确保模拟的物理地址不会被平台CPU看到,而是由0sim进行转换。因此,我们的系统可以模拟给定目标体系结构的最大允许地址空间。最后,通过公开报告目标时间流逝的硬件时间戳计数器,我们可以在模拟中实现高效的性能度量。

0sim模拟了目标的功能和性能方面,就像在一个真正的多TB机器上测量一样。0sim的目标不是成为一个体系结构模拟器或做得完全准确,因为目标可能在许多方面取决于平台,包括处理器微体系结构。相反,0sim能够很好地模拟系统软件,以满足再现可伸缩性问题、原型化解决方案和探索系统行为等重要用例的需要。0sim为了提高仿真速度而牺牲了一些精度,从而实现了大型、长时间运行的仿真和交互式调试。

本文介绍了0sim的体系结构和实现。我们根据这些目标验证了0sim的准确性和模拟速度。0sim可以模拟比平台大20-30倍的目标系统,与我们测试的工作负载的本地执行相比,只有8-100倍的速度放缓,且可压缩的工作负载运行得更快。例如,我们在160GB平台上模拟4TB的memcached目标,在30GB平台上模拟1TB的memcached目标,只有8倍的减缓。相比之下,体系结构模拟器会导致10000倍甚至更糟的减速。

我们执行了几个案例研究来证明0sim的有效性:我们重现并扩展了一个提出的内核补丁的开发性能结果;我们测试内存压缩对memcached尾部延迟的最坏影响,为22倍的减速;我们显示了,对于混合的工作负载,Linux可能导致不可修复的内存碎片,即使有几十GB的空闲内存;同步页面回收可以以非常小的额外延迟为代价提高效率。此外,我们使用0sim交互式地调试memcached中的可伸缩性bug,该bug只在内存超过2TB时发生。0sim代码可以从该链接获取:https://github.com/multifacet/0sim-workspace。

2. 问题:根据内存容量进行缩放

0sim解决了研究软件如何利用内存容量进行扩展的关键需求,包括有效利用内存、解决算法瓶颈以及设计具有巨大内存容量的策略。此外,它为开发人员消除了限制软件在大内存系统中进行有效测试和部署的障碍。

计算效率低下:任何执行时间随内存量线性增加的操作都可能成为瓶颈。例如,页帧回收算法、大页压缩、页重复数据删除、内存分配和脏/引用位抽样都是对每页元数据进行操作的。如果内核试图透明地将一系列页面升级为一个巨大的页面,那么运行巨大的页面压缩可能会导致应用程序中出现不可预知的延迟峰值和长尾延迟。这导致许多数据库和存储系统建议关闭这些内核特性,尽管这些特性可能带来性能提高。

同样,分配、初始化和销毁页表的代价可能非常昂贵。这会影响创建和销毁进程或服务页面错误的时间。Linus Torvalds认为,为预填充页表分配页的开销使得mmap的MAP_POPULATE标志用处更小,因为它的延迟高得不可接受。

另一个例子是Linux内核的页结构。大内存系统可能有数十亿个这样的结构,为每个4KB的物理内存页存储元数据。在一个4TB的系统中,在我们的测试机器上,初始化它们并在启动时将它们释放到内核的内存分配器分别需要18秒和15秒。这对服务可用性有影响,其中内核的引导时间可能位于服务重新启动的关键路径上。

内存使用情况:在某些情况下,任何与主存大小成比例的内存使用都可能占用太多空间。例如,一台具有少量DRAM和TB级非易失性内存的机器可能会发现它的所有DRAM都被页表和非易失性内存的内存管理元数据所消耗。

如前所述,对于每个4KB的页面,Linux内核都保留一个200字节的带有元数据的结构页。类似地,在大型系统上,用于地址转换的页表可能会消耗几十GB的内存。虽然这个空间可能只占总内存的一小部分,但它会消耗宝贵的系统资源,并且如上所述,会增加管理的时间成本。

Huge-Memory-Aware策略:针对小内存的有效内存管理策略在大规模时可能表现不佳。例如,在Linux内核中,一旦脏页超过了一定的内存百分比,就会将它们刷新到存储中,但是在大型机器上,这会导致长时间的暂停,因为GB级的数据被刷新。另外,一些使用巨大内存来缓冲流数据的应用程序发现内核页面缓存是一个瓶颈。在一个巨大和/或非易失性内存的时代,页面缓存应该扮演什么角色并不明确。随着高吞吐量、低延迟的网络、存储和大内存变得越来越普遍,重新评估缓冲和刷新脏数据的内核策略非常重要。

同样,需要检查用于大型连续分配和碎片控制的策略。许多现代高性能I/O设备,如网卡和固态驱动器,使用大型物理连续固定内存缓冲区。研究人员提出了大型连续分配以减少TLB miss的开销。为了满足这样的内存分配,内核必须控制物理内存碎片,这在Linux中一直是一个问题。另一个补充问题是,在多TB的系统上,急切分页和透明的大页面所引起的内部碎片的影响。对于较小的系统,这些问题已经得到了很好的研究,但是据我们所知,在大内存系统和工作负载上还没有重新讨论过这些问题。

容量可伸缩性问题并非操作系统所独有。其他系统软件,如语言运行库,也需要适应巨大的内存系统。例如,Oracle的Java虚拟机采用了针对大内存系统优化的新的垃圾收集器。

发展的障碍:由于费用问题,大内存系统并不常见。因此,系统软件不能很好地测试将很快普及的大内存系统。在我们的工作中,我们经常发现软件有bug和任意的硬编码限制,导致它在大内存系统上失败。通常,由于不相关的内核错误或无限期挂起等用户不友好的故障模式,这些限制并没有很好地记录下来,而且故障很难调试。0sim让开发人员更容易大规模测试软件。

3. 相关工作

仿真:通常用于硬件不可用的情况(例如,处理器模拟器)。与其他硬件进步(如提高处理器核心或设备速度)不同,在现有硬件上模拟内存容量具有挑战性,因为必须维护大量的状态。Alameldeen等人通过费力地缩减和调整他们测试的基准和系统来解决这个问题。虽然准确,但这种方法容易出错、繁琐且难以验证。在Quartz和Simics中,模拟尺寸受主机尺寸的限制。0sim通过利用数据无关性来存储比主机的内存或存储容量更多的内存状态,能够在一个适中的主机上运行大型模拟。研究人员通过减慢模拟机器的时钟来模拟快速网络。0sim同样会调整目标对时间推移的看法。David和Exalt通过只存储元数据和在读取时生成内容来模拟大型存储系统。这种技术对于内存来说很困难,因为在大多数程序中,重要的元数据并没有与可丢弃的数据分开。0sim通过预先确定的输入来实现类似的结果。多个系统将一个机器集群虚拟化,从而产生一个单独的大型虚拟机;Firesim使用基于云的加速器来缩放模拟。相比之下,0sim使用虚拟化,但在单一的商品主机上,这对研究人员和开发人员更容易访问。

可伸缩性:之前的大量工作已经研究了系统软件中不同类型的可伸缩性问题。例如,RadixVM试图克服由于内核数据结构上的内存管理操作的序列化而导致的高并发工作负载中的性能问题。其他研究表明,struct page、struct vm_area_struct和页表构成内存管理开销的很大一部分。Java 11提供了一个新的垃圾收集器,它允许扩展到TB级的堆,同时保持低延迟。然而,针对提高系统在内存容量方面的可伸缩性的工作很少。0sim使解决这个问题的原型和测试软件变得很容易。

技术:0sim的设计和实现利用了许多已知的软件技术来有效地过度使用内存。我们的贡献之一是展示0sim如何使用这些技术来构建一种新的模拟方法。页面压缩和重复数据删除可以在使用过量的情况下增加内存利用率;它们在广泛使用的软件中实现,如Linux、MacOS和VMware ESX Server。在Linux中,通过使用更有效的分配器和优化相同填充的页面,已经完成了提高可实现的内存压缩比的工作。Remote-memory提出通过网络将页面交换到远程机器,允许更大的工作负载在本地运行。基于硬件的内存压缩和零感知优化的工作也已经完成,但这些建议需要专门的硬件,不像0sim。

4. 0sim设计

0sim允许在具有巨大物理内存的机器上评估系统软件。我们强调0sim不是一个架构模拟器,相反,它有以下目标:

  • 运行在廉价的硬件上。
  • 对模拟软件要求最小的更改。
  • 保持性能趋势,而不是精确的性能。
  • 运行足够快以模拟长时间运行的工作负载。

图1(左)显示了0sim架构的概述。0sim引导一个虚拟机(VM)作为target,使用的物理内存比主机或平台上可用的物理内存大几个数量级,同时保持合理的模拟速度。0sim是作为在平台上运行的经过修改的内核和管理程序实现的,但不需要进行任何target的更改。任何未修改的目标操作系统和各种各样的工作负载都可以通过在目标中执行它们来模拟(例如,通过SSH)。x86 rdtsc指令可以在目标中用来读取硬件时间戳计数器(TSC),以进行模拟时间测量。

0sim Design

0sim力求保持趋势,而不是精确预测性能,从而在模拟速度和系统的易用性与准确性之间进行权衡。0sim在模拟环境中保留了时间指标(例如延迟)和非时间指标(例如内存使用)的趋势。0sim可用于比较两个target的性能,以衡量优化的影响。

0sim面临的主要挑战是(1)模拟巨大的内存、(2)保存时间度量。我们使用数据无关的工作负载和内存压缩来解决(1)。我们通过虚拟化TSC来解决(2)。

4.1 数据无关性

模拟大内存系统与模拟CPU或网络设备等更快的硬件有本质的不同。如前所述,模拟巨大的内存需要维护比平台能够容纳的更多的状态。0sim依赖于平台内核的交换子系统,以透明地将target状态溢出到交换设备。然而,平台可能没有足够的交换空间用于我们希望模拟的状态;即便如此,从存储中写入和读取所有状态也会非常缓慢,而且会使大型的、长时间运行的模拟变得不切实际。

我们的关键观察是,无论输入是什么,许多工作负载都遵循相同的控制流。我们称这种工作负载为数据无关的。例如,内存中的memcached键值存储并没有根据键值对中的值而表现出不同的行为——反而只有键。另一个例子是固定计算,如矩阵乘法;我们可以用稀疏或已知的矩阵提供矩阵工作负载。一个工作负载,NAS共轭梯度基准,自然地使用稀疏矩阵。

图1(右)描述了0sim中target状态的管理。为数据无关的工作负载提供预先确定的数据集,使其能够在不改变其行为的情况下进行内存压缩。0sim能够识别带有预先确定内容的页面(例如,一个零页面),并将它们压缩到1位,存储在一个名为zbit的位图中。与预先确定的内容不匹配的页面可以被压缩并存储在名为ztier的高效内存池中。这允许0sim运行大型工作负载,同时在一个更普通的平台机器上保持模拟状态。此外,由于大部分模拟状态都保存在内存中,所以与必须将所有状态写入交换设备相比,zbit能够更快地模拟。例如,在我们的工作站上,将4KB写入SSD大约需要24个µs,而LZO压缩只需要4个µs。

0sim的模拟性能依赖于数据无关性。有些有趣的工作负载很难做到与数据无关,比如图表和带有反馈循环的工作负载。尽管如此,为了学习系统软件(如内核),数据无关的工作负载可以以不同的方式(包括不同的内存分配和访问模式)有效地运行系统。因此,我们认为数据无关的工作负载足以暴露许多问题,我们的许多发现也适用于其他工作负载。例如,大部分内核内存管理子系统可以被训练,因为它们与页面内容无关。我们将在第8节中使用几个案例研究来演示这一点。此外,为数据无关的工作负载准备系统也有利于非数据无关的工作负载。

4.2 硬件限制

现有的商品系统可能不支持我们希望研究的内存大小。例如,我们的一个实验平台有39个物理地址位,只能处理512GB的内存,而我们想模拟多TB的系统。这种硬件限制阻止了运行大内存工作负载进行研究。0sim使用阴影页表克服了地址大小的限制:是管理程序(而不是硬件)将target物理地址转换为适当宽度的平台物理地址。虽然尚未实现,但也可以用这种技术模拟运行虚拟机或5级分页的target,这是Intel宣布的,但尚未得到广泛支持。类似地,0sim通过在管理程序中透明地使用内存压缩来利用工作负载数据无关性,从而支持大于可用交换空间的内存大小。

4.3 时间虚拟化

硬件模拟器,如gem5,经常使用模拟器生成的离散事件来模拟时间的流逝。然而,这是非常缓慢的,与本机执行相比,会导致许多数量级的减缓。这样的慢速度使得在中长时间尺度上研究大内存系统的行为变得不切实际。相反,0sim在平台上使用硬件时间戳计数器(TSCs)来测量时间的流逝。

每个物理核心都有一个独立的硬件TSC,可以连续运行。但是,系统管理程序中有许多开销来源,例如页面错误,这些不应该反映在目标性能度量中。我们为目标创建一个虚拟硬件TSC,只有当target在运行时,系统管理程序才能增加这个TSC。我们通过现有的硬件虚拟化支持来实现这一点,以调整target的虚拟化TSC。因此,在仿真中使用硬件报告目标时间。

5. 实现

本节描述了0sim实现中具有挑战性和创新性的部分。注意0sim只在平台内核中运行;0sim可以运行任何未修改的target内核。我们将0sim实现为对Linux kernel 4.4和KVM管理程序的修改版。内核更改包括大约4100行新代码和770行更改,对于KVM则是400行新代码和12行更改。相比之下,gem5模拟器的代码几乎有50万行。

5.1 内存压缩

我们修改了Linux的Zswap内存压缩内核模块,以利用数据无关性。

首先,我们对Zswap进行修改,以实现通常情况下数据的2的15次方压缩比:我们用zbit位图中的单个位表示零页,表示它是否为零页。在实践中,页表和可压缩性较低的页(例如,文本段或应用程序元数据)限制了可压缩性,但理想情况下1TB可以压缩到32MB。在zbit中,每个页面对应一点。当平台选择交换时,一个全零的目标页将被Zswap识别,并在zbit中压缩到1位。当交换子系统查询Zswap以检索页面时,它会检查zbit。如果设置了页面位,则返回零页;否则,Zswap照常进行。在内部,zbit使用基数树来有效地存储稀疏位图。

其次,我们注意到,即使是非零页面也可以被显著压缩并密集存储。Zswap使用称为“zpools”的特殊内存分配器来存储压缩页面。默认的zpool——zbud以内存开销为代价避免了计算开销和实现复杂性。它将有效压缩比限制为2:1,并且每个压缩页面存储24字节的元数据。

我们实现了自己的zpool——ztier,这比zbud有了显著的改进。ztier使用的所有内存都用于存储压缩页面。它通过利用struct page的未使用字段和在池中未分配的页面空间中维护自由列表来减少元数据,但计算开销可以忽略不计。此外,它支持多种分配大小,从而提高了空间效率。因此,ztier比Linux的zbud实现了更高的有效压缩比,但牺牲了实现的复杂性。例如,在未修改的Zswap上为500GB的memcached工作负载使用zbud需要294GB内存。使用zbit, zbud需要15GB的RAM来存储压缩页面。相比之下,ztier的消耗不到6GB。

总的来说,通过我们的修改,具有预先确定内容的目标页面需要66位平台内存:64位用于页表条目,1位图位,1位用于上层页表。

这些优化允许平台将大多数target状态保存在内存中,但是仍然需要交换设备,因为不是所有模拟状态都是可压缩的,而且状态可能仍然需要溢出到交换设备。所需交换空间的数量在很大程度上取决于工作负载。Linux在尝试插入Zswap之前分配交换空间,即使在使用Zswap时它实际上并不写入交换空间。因此,我们使用设备映射器来精简分配交换空间,使其看起来比实际大得多。我们发现1TB的交换空间对于我们的大部分工作负载已经足够了。具有高流动率或低压缩性的工作负载需要2-3TB的交换空间。

5.2 影子页表

如第4.2节所述,0sim使用阴影页表将目标地址空间的大小与平台处理器中的物理地址位的数量解耦。系统管理程序读取来宾内核的页表并构造影子页表,硬件使用这些影子页表将来宾虚拟地址转换为主机物理地址。硬件永远看不到客户物理地址。因此,目标物理地址空间和虚拟地址空间与平台虚拟地址空间一样大(在我们的机器上是48位,而不是39位)。

当平台有足够的物理地址位时,0sim可以选择使用基于硬件的嵌套分页扩展。嵌套分页没有影子页表的空间开销,而且速度更快,因为管理程序不在地址转换的关键路径上。然而,增加的模拟速度是以牺牲一些准确性为代价的,因为0sim不能考虑嵌套分页的开销,这在硬件中是透明的。因此,在仿真速度和精度之间存在一个权衡;为了更准确,可以禁用嵌套的分页扩展。

5.3 时间虚拟化

0sim虚拟化rdtsc x86指令,该指令返回一个周期级硬件时间戳计数器(TSC)。每个物理核心都有一个独立的TSC, Linux内核在引导时同步它们。大多数Intel处理器都能够虚拟化TSC,这样客户TSC与它在上运行的处理器的平台TSC是一个偏移量。0sim调整每个vCPU的偏移量,以隐藏在管理程序中花费的时间,而不是执行目标。

虚拟化本身有相关的开销。例如,系统管理程序模拟来自target内核的admin指令和I/O操作。我们修改了KVM,以便在模拟中隐藏大部分虚拟化开销。当vCPU停止时(即target时间暂停时),我们记录平台TSC值。在vCPU恢复之前,我们再次读取平台TSC,并以所经过的时间偏移目标TSC。

在多核模拟中保持计时:这带来了额外的挑战,因为管理程序在不同的平台内核上并发地执行模拟的内核。因为vCPU可以由hypervisor独立运行或暂停,所以它们的目标TSCs可能会变得不同步。如果时间度量可能跨核或受其他核上的事件(例如同步事件、来自服务器线程的响应)的影响,这可能会有问题。对于某些用例,这可能导致度量不准确。

在本节中,我们提出了一个解决这个问题的方案,我们称之为动态TSC同步(DTS)。虽然我们已经在0sim中实现了DTS,但我们在本文的所有实验中都没有使用它,因为它增加了模拟时间,而且我们当前的实现有时会导致不稳定;在使用之前需要进行更多的评估。虽然我们在实验中观察到了偏移,但我们发现0sim对于许多用例来说仍然足够精确,如第6和8节所示。

DTS的工作原理如下:为了防止过度漂移,0sim会延迟运行得太超前的vCPU,从而给其他内核迎头赶上的机会。具体来说,让tv作为vCPU v的目标TSC。0sim延迟一个vCPU c,如果它至少比最滞后的目标TSC早D个时间单位,则通过取消调度它运行δ时间单位:

DTS Algorithm

定时中断等周期性事件可以防止内核无限期地提前运行。图2演示了一个示例。一般来说,模拟器的速度会随着D的降低和模拟核的增加而降低。用户可根据需要调整参数D和δ。

DTS Example

DTS有许多理想的特性。最重要的是,它将目标TSCs之间的漂移量限制在阈值d。这意味着,由于误差不会累积,通过更长时间的测量可以获得更精确的测量结果。此外,它不会导致目标时间跳跃或倒退。

LAPIC定时器中断:操作系统通常使用定时器中断的到来作为时钟滴答的一种形式。例如,Linux内核调度器和某些同步事件(例如,rcu_sched)使用jiffies感知时间流逝,jiffies是通过中断的传递来度量的。我们通过延迟计时器中断到目标内核的交付,直到接收中断的vCPU达到适当的客户时间,从而虚拟化了计时器中断的交付。我们通过经验验证了目标感知到的中断率与平台相当。

局限性:由于许多事件(例如I/O)是由hypervisor模拟的,因此没有明确的方法来正确地知道它们在本地硬件上要花费多少时间。我们选择让这些事件不占用target时间,而不是猜测或使用任意常数;实际上,当系统管理程序处理它们时,target时间会暂停。因此,0sim不适合测量I/O事件的延迟,尽管它可以测量I/O绑定进程中事件的CPU延迟。这与其他模拟器类似。

我们的方案没有考虑虚拟化带来的微架构和架构行为的变化。切换到平台的上下文可能会产生影响target性能的微体系结构影响,例如污染平台处理器的缓存或TLB。例如,在系统管理程序从Zswap交换一个页面之后,target可能会出现TLB miss,这是没有考虑到的。

此外,0sim不能完美地保存硬件事件。特别是,Linux使用多个时间源,包括处理器时间戳(例如,rdtsc)、其他硬件时钟(例如,跟踪处理器休眠时的时间)和中断传递的速率。0sim只虚拟化处理器时间戳和LAPIC定时器中断。

在实践中,我们发现0sim很好地保存了行为,如第6节所示。但是,目标的I/O中断率高于正常水平(例如,网络或存储),这可能导致I/O密集型工作负载的性能较差和崩溃。

6. 模拟器验证

在第8节展示案例研究之前,我们将演示0sim足够精确,可以用于再现可伸缩性问题、原型化解决方案和探索系统行为。因为0sim不修改目标的数据结构或算法,所以它自动保留非时间指标,例如消耗的内存量。

6.1 方法

0sim可以在各种各样的商用硬件上运行,从旧的工作站到服务器。我们的测试平台的规格可以在表1中找到。wk-old是一台6年历史的工作站机器,有31GB的DRAM。wk-new是一台有4年历史的工作站机器,64GB的DRAM。这两台机器最初购买时的价格都在1000-2000美元左右。server是一个具有160GB DRAM的服务器类机器。这些机器的成本都比一个巨大的内存机器或长期租用云实例的成本低几个数量级。

Test Platforms

我们将CPU伸缩调控器设置为“性能”。hyperthread和Intel Turbo Boost在任何地方都是启用的,以保持一致性,因为我们使用的服务器测试平台没有办法禁用它们。

在所有的模拟中,目标操作系统是CentOS 7.6.1810和Linux kernel v5.1.4,因为CentOS内核已经有好几年的历史了。我们禁用Meltdown和Spectre缓解措施,因为当主机过度使用时,它们会导致严重的性能下降。

为了从模拟中收集指标,我们将一个NFS服务器从平台导出到目标。这具有合理的性能,并且没有引入新的性能构件。我们为memcached和redis等工作负载提供全零数据集。我们修改了微基准测试,比如memhog,以使用全零值。在所有服务器应用程序(例如memcached)的实验中,我们在与服务器相同的VM中运行客户端程序来驱动工作负载。否则,I/O虚拟化很快就会成为瓶颈,因此度量实际上是度量系统管理程序的各个方面,而不是目标。

所有多核模拟实验在桌面类计算机上有8个模拟核,在服务器类计算机上有6个模拟核。我们发现,当平台过度使用时,即使是未修改的KVM也无法启动服务器上超过6个核的多TB的虚拟机。我们认为这是因为KVM的模拟硬件设备使用的是真实(平台)时间,并且运行非常大的机器的开销会导致硬件协议超时。未来,我们会扩展0sim的时间虚拟化来解决这个问题。同时,我们期望6个模拟核足以实现软件的多核效果。

我们的比较基准是直接执行(而不是模拟)AWS 1e.32xlarge实例,内存3904GB,每小时价格26.818美元。这是我们能找到的最大的按需云实例。我们已经运行了高达8TB的模拟,但是这里我们使用了4TB的最大大小与基准进行比较。虽然0sim模拟了本机执行的性能(不包括在管理程序中花费的时间),但是AWS实例是一个虚拟机,因此存在虚拟化的开销,虽然它并没有过度使用。

6.2 单核准确性

我们首先评估了0sim对单核目标的精度。为了度量0sim对目标隐藏管理程序活动的程度,我们记录rdtsc后续执行之间的差异。图3a显示了时间戳差异的CDF。对于所有目标和基准,有三个主要模式:首先,几乎所有的度量都小于10ns,这是rdtsc所能度量的最细粒度的度量,大致与处理器的管道深度相对应。第二,有一组大约3µs的测量值。这些对应于存储实验结果时出现的页面错误。最后,一些度量不符合任何一种模式,而是对应于其他系统现象,如目标中断处理程序和目标内核调度器将处理器从工作负载中移除时的时间。0sim隐藏了管理程序活动,以便在所有三个仿真平台上接近基准行为。

Single Core Test

结论1:0sim能够在几十纳秒的测量粒度范围内隐藏99%的idle的hypervisor活动,比如服务中断。

为了验证0sim保持了目标操作系统的准确性,我们运行一个映射并依次接触所有目标内存的工作负载。这将导致大量的内核活动,因为内核必须处理页面错误并分配和零内存。同样,重要的管理程序活动必须对目标隐藏。图3b显示了每个页面的结果时间。注意,服务器平台有更大的缓存,这有助于提高性能。我们看到,0sim能够大致保留触摸内存的时间,尽管我们注意到0sim不是为这种细粒度的测量所设计。对于75%的操作,基准机器的延迟要高得多(大约2.5µs),因为它包含了管理程序活动,例如跨NUMA节点分配和置零页面。在模拟中,管理程序活动导致缓存新页面,从而对目标隐藏NUMA node间的延迟。

结论2:在有显著平台活动的情况下,0sim能够将事件的时间保留在2.5µs内,尽管它没有模拟NUMA效应。

为了验证0sim能够准确地模拟更真实的工作负载,我们模拟了一个填满大型memcached服务器的工作负载。键是唯一的整数,值是512KB的0。我们测量1TB工作负载下100个插入集的延迟。Memcached是作为hashmap实现的,所以对于整个工作负载来说,插入时间大致不变。图3c显示,与基准相比,0sim既保留了memcached的固定插入时间,也保留了请求的延迟时间。图中顶部的点的线性(注意对数尺度)趋势对应于memcached调整其散列表的大小,这将阻塞客户机,因为它们对单个核进行时间共享。这一趋势在基准中并不存在,因为它是一个多核机器,并且在不同的核心上并发地调整大小。

结论3:对于单核目标上的实际应用,0sim能够准确地保留重要趋势,除了保留高级定时行为。

结论4:当在不同平台上运行时,0sim会产生类似的结果。

为了评估仿真结果对平台上活动的敏感性,我们使用在该平台上运行的Linux内核构建重新运行前面的memcached实验。图4a显示了在wk-old上的结果与上面相应的测量值相比较。尽管平台上的活动非常活跃,但目标用户看到的结果和趋势与上面收集到的类似。

结论5:0sim能够很好地掩盖平台的活动,从而对目标隐藏显著的活动。

6.3 多核准确性

我们评估0sim在运行多核目标时的准确性。memcached客户端固定在一个核心上,测量对一个不固定在任何核心上的多线程服务器的请求的延迟。图4b显示了测量延迟的CDF。结果与预期的一样,比单核模拟的噪音更大。0sim能够保留memcached的固定时间行为,即使没有动态的TSC同步。我们认为,wk-old和wk-new的尾事件代表了锁等多核交互带来的抖动增加。我们认为服务器的长尾是由于使用了Intel嵌套分页扩展(EPT):嵌套页面错误对管理程序是隐藏的,所以我们不能针对它们进行调整,正如第5.2节所指出的那样。请注意,我们总共测量了100个请求,累积了它们所有的缓存丢失、TLB丢失、来宾和主机页面错误以及嵌套的页面遍历。禁用EPT可以获得更精确的测量结果。

Multi Core Test

结论6:虽然0sim对多核目标的结果不太准确,但保留了重要的趋势和定时。

6.4 最差情况的错误

为了测量微架构事件的最坏情况影响,我们运行一个时间和空间位置较差的工作负载,它涉及4GB内存范围内的随机地址。它会导致缓存和TLB失败,以及平台和目标页面错误,这是昂贵的,因为主机被超额订阅。图4c显示了这些工件导致了仿真可见的显著延迟。这些事件中大约90%的延迟不超过500ns,这与现代处理器中缓存和TLB miss的延迟相对应。此外,服务器机器显示的此类事件较少,这与它的较大缓存和TLB相对应。

结论7:主要测量微架构性能差异的实验,如TLB和缓存未命中,在0sim上可能是不准确的。

7. 数据无关性和速度

为了达到合理的仿真性能,0sim要求目标具有良好的压缩性。我们认为这包括大量有用的工作负载。表2报告了我们实验中几个示例工作负载的聚合压缩率,它是内核试图插入到Zswap的所有页面的平均压缩率(这与平台内存与目标内存的比率不同)。注意,聚合压缩比仅为20:1表示在整个工作负载过程中节省了95%的内存使用量。有一些结果值得评论。Metis是内存中的map-reduce框架;不幸的是,它会在巨大的工作负载中崩溃,这表明需要在大内存系统上测试系统软件。此外,NAS CG运行时没有修改其标准数据集(稀疏矩阵),这是压缩友好的,但不是数据无关的。总的来说,这些结果表明,0sim能够大大减少大量数据无关工作负载的内存占用,使大型模拟成为可能。

Aggregate Compressibility

数据无关性对仿真性能至关重要,对仿真精度也有一定的影响。我们进行了一个实验,关闭Zswap,完全依赖交换。一个1TB的memcached工作负载需要3倍的时间来启动,10倍的时间来运行。更糟糕的是,开销如此之大,以至于尽管0sim采用了TSC补偿方案,开销仍然会泄漏到模拟中,并导致与工作负载大小成比例的目标性能下降。

模拟速度在很大程度上取决于工作负载、平台和模拟核的数量。图5显示了随着模拟核数量的增加,1TB memcached工作负载的模拟速度。一般来说,开销随着模拟的内核数量的增加而增加,但是运行时间可能会由于工作负载性能的提高而减少。

Memcached Speed

在我们的实验中,与本地多核执行相比,我们通常观察到8倍到100倍的速度放缓。作为参考,这相当于在20世纪90年代末或21世纪初的处理器上运行工作负载。架构模拟通常会导致10000倍或更糟的速度减慢(如Gem5)。我们发现,由于I/O虚拟化,高I/O工作负载会变慢。

随着平台内存的过度使用,模拟器性能会优雅地下降。用户可以通过改变目标物理内存的数量来平衡模拟速度和可伸缩性测试。在实践中,我们发现模拟大小受到硬编码限制和软件bug的限制,而不是内存容量。例如,KVM-QEMU不接受任何大于7999GB的参数字符串。通过工程的努力,这些限制可以被克服。总的来说,我们发现0sim使数据无关工作负载的大型多核模拟变得可行和高效。

8. 案例学习

0sim对于原型和测试以及研究和探索都很有用。我们给出了案例研究,展示了我们如何调试memcached中的可伸缩性bug,探讨了Linux内存碎片管理和页面回收中的设计空间问题,评估了一个建议的内核补丁集,并重现了已知的性能问题。

8.1 发展

与0sim工作负载交互的能力被证明是非常宝贵的。在运行实验时,memcached在从8TB的数据中只插入了2TB的数据后,返回了一个意外的内存不足错误。为了理解为什么memcached行为不正常,我们在正在运行的memcached实例上启动了一个交互式(尽管很慢)调试会话。我们发现memcached的分配模式在glibc的malloc实现中引发了病态的情况,导致大量对mmap的调用。这导致分配失败,因为系统参数限制了每个进程的内存区域。增加限制可以解决这个问题。

这个事件说明0sim对于查找、重现、调试和验证只发生在大内存系统上的错误的解决方案非常有用。memcached的一个主要开发人员不确定这个问题,因为他们没有尝试过在大于1.5TB的系统上使用memcached。我们希望0sim能让系统社区更好地为大内存系统的更广泛可用性做好准备。

8.2 探索

我们给出了Linux内核中的两个案例研究,演示0sim如何在设计空间探索中发挥作用。

8.2.1 内存碎片

先前的文献对应用级和系统级的内存碎片进行了广泛的研究。然而,我们意识到,在处理大内存工作负载中碎片的影响方面的工作很少。有些人认为,大内存工作负载不会受到碎片的严重影响。0sim非常适合研究这种工作负载及其系统级影响。

Linux内核分配器是伙伴分配器的一种变体,它对不同大小的连续空闲内存区域使用不同的空闲列表。具体来说,对于每个分配顺序都有一个空闲列表,其中order-n空闲列表包含连续的2的n次方个页。在将空闲区域添加到适当的空闲列表之前,内核会合并空闲区域以形成尽可能大的空闲内存区域。因此,如果很大比例的空闲页面位于低顺序空闲列表中(接近于0),则内存是高度碎片化的。

方法:在Linux v5.1.4物理内存分配器中,我们记录了页面随时间在空闲列表中的分布情况。我们在启用快照的情况下模拟一个1TB的Redis实例。Redis会定期快照它的内容:Redis进程分叉,子进程将它的内容写入磁盘,依赖于内核写时复制,然后终止,而父进程继续处理请求。

然后我们模拟一个混合的工作负载:redis客户端和服务器被用来代表一个典型的键值服务器,在许多分布式应用程序中都可以找到;Metis工作负载代表并行的CPU和内存密集型计算;一个修改为固定内存和数据无关的memhog工作负载模拟了高性能I/O驱动程序,它为缓冲区使用大型固定物理内存区域。这些应用程序每个接收1/3的系统内存。

结果:单独运行时,Redis不会受到碎片化的影响,但在存在其他工作负载的情况下,它会受到影响。图6显示了整个混合工作负载中每个伙伴列表中的可用内存量。更多的紫色(顶部)表示更大的连续空闲物理内存区域,而更多的红色(底部)表示物理内存高度碎片化。每一次空闲内存运行得较低,对于工作负载的后续部分,碎片就会降低:在2.5小时之前,几乎没有碎片,但在2.5小时之后,几乎有40GB的空闲内存处于8级或更低的水平,在4.2小时之后,几乎有100GB处于8级或更低的水平。请注意,分配一个大页面需要9或更高的顺序。经过进一步的检查,我们可以看到,虽然大多数区域是连续的,但许多独立的基页分散在物理内存中。这些页面代表某种“潜在的”碎片,尽管释放和合并了几百GB的内存,这些碎片仍然存在。这表明任何真正的防碎片解决方案都必须处理这种“潜在的”碎片。

Free Memory

上面的结果处理外部碎片——即导致未分配空间浪费的碎片。在运行我们的实验时,我们还发现,对于某些工作负载,内部碎片(分配中的浪费空间)是应用程序级的问题。具体来说,我们观察到在某些情况下,由于病态的内部碎片,一个4TB的memcached实例只能容纳2-3TB的数据。如果插入值的大小与memcached的内部内存分配单元不匹配,则会浪费内存。这种浪费与工作负载大小成比例地增加,因此对于多TB的memcached实例来说就会有问题。我们必须仔细地调优memcached的参数,以获得可接受的内存使用。这些观察结果还表明,在大内存系统中,内部碎片可能是一个更重要的问题。

8.2.2 页面再利用

数据中心工作负载可能会过度使用服务器以提高效率。当内存不足时,页面回收算法满足内核分配。在Linux中,直接回收(DR)满足阻塞用户空间的未完成分配(例如,从页面错误),而空闲回收(IR)则在空闲内存数量低于阈值时在后台发生。在大内存系统上的回收在计算上是昂贵的,因为它要扫描数十亿页来检查空闲状态,可能会抵消从额外可用内存中获得的任何好处。谷歌和Facebook都使用内部IR解决方案来实现更好的内存有效利用。使用0sim,我们测量每个算法花费在每个回收页面上的时间,并研究对DR算法的策略修改,试图使其更有效。

方法:我们运行一个占用所有内存并休眠的工作负载。然后,我们运行一个memcached工作负载,该工作负载只对键值存储区执行插入操作。这将导致闲置和直接回收的工作负载。我们用Linux测量空闲和直接回收的时间,以及扫描和回收的页面数量。

Time Reclaim

结果:表3的顶部(“空闲”和“直接”)显示,DR每个回收的页面需要7个µs,而IR每个回收的页面需要3.5个µs,但运行时间要长5倍左右。这是有道理的。DR阻塞用户空间的执行,因此它针对延迟而不是效率进行了优化。因此,只要它能满足所需的分配,它就会停止,而IR会继续,直到阈值被满足。

我们假设DR如果效率更高,运行的频率就会更低。我们修改DR以回收比请求多四倍的内存。表3(Idle 4x和Direct 4x)显示,Direct和IR现在每个回收的页面都花费大约4个µs。直接回收比之前多消耗约2秒,但运行频率减少约36%,整体回收时间减少1秒。这表明,在持续繁忙的系统上,能够容忍稍长的延迟的应用程序可以从我们的修改中受益。

8.3 复制和原型设计

0sim可以用来重现已知的可伸缩性问题和针对它们的原型修复。我们将通过Linux内核中的两个案例研究来演示这一点。

8.3.1 ktask可伸缩性

提出的ktask补丁集将CPU密集型的内核空间工作并行化,例如页结构的初始化。使用0sim,我们重现并扩展ktask补丁集的开发人员结果。

方法:我们将补丁集应用于Linux内核5.1,并通过使用rdtsc测量初始化过程中消耗的时间。在引导期间,结构体首先被初始化;然后,它们被释放到内核内存分配器,使它们对系统可用。我们还记录了ktask使用的32768页“块”的数量,这些“块”可以相互并行初始化。

结果:表4显示了带有8核的1TB计算机和带有20核的4TB计算机在初始化方面的3倍和5倍改进。这与补丁集作者为真正的512GB机器发布的结果成正比。然而,即使使用ktask,页面初始化仍然是昂贵的!在一台4TB的机器上,大约要消耗6秒的启动时间,然而,例如,5个“9”的可用性(99.999%)相当于每年大约5分钟的停机时间。内核开发人员之前的一些讨论研究了在某些用例中消除页结构的使用,我们的结果表明,内核中的页结构使用是不可伸缩的。需要的内存管理算法不能随着物理内存的数量线性伸缩。

Time ktask

8.3.2 内存压缩

使用0sim,我们再现并量化内存压缩开销。巨大的页面和许多高性能I/O设备(例如,网卡)需要大量连续物理内存分配。Linux内核使用昂贵的内存压缩算法创建连续的区域。突然压缩可能会导致应用程序中不可预测的性能下降,导致许多数据库和存储系统建议禁用透明大页等特性,包括Oracle Database、Redis、Couchbase和MongoDB。

方法:我们测量了存在和不存在压缩情况下memcached请求的延迟。压缩通常发生在短时间内,使其难以再现,因此我们修改内核以不断重试压缩。使用此修改的测量值近似于普通内核压缩期间的最坏情况。

结果:图7显示了1 TB工作负载压缩前后memcached请求的延迟时间。中值延迟降低了22倍,而99.999%尾延迟降低了10000倍,导致偶尔出现非常长的延迟峰值。有些尾部效应是由于0sim开销造成的,但是图3a显示这种开销不会造成如此大的影响。这表明,在生产系统中,压缩可能导致定义服务尾部延迟的事件。0sim可以用于进一步研究大型系统的内存分配和压缩策略。

Latency of Memcached

9. 总结

与内存容量有关的系统可伸缩性是关键的,但有待研究。当前系统的内存使用、计算效率低和策略选择通常不适合大型系统。0sim就是为解决这一问题而设计的仿真平台。它利用许多工作负载的数据无关性,使它们的内存内容高度可压缩。0sim运行在研究人员和开发人员易于使用的硬件上,使系统软件的原型和探索成为可能。它准确地保存了行为和趋势。0sim允许我们调试意外行为。通过开源0sim,我们希望能够让研究人员和开发人员准备适应TB级存储空间的系统软件。