论文笔记Persistent Memory Hash Indexes: An Experimental Evaluation
Posted Anyanyamy
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了论文笔记Persistent Memory Hash Indexes: An Experimental Evaluation相关的知识,希望对你有一定的参考价值。
目录
2.1 Optane DC Persistent Memory
2.2 Programming Persistent Memory
3.3 Dynamic and Scalable Hashing (Dash)
4.2 Single-threaded Performance
4.3 Multi-threaded Performance
4.10 Breakdown of Data Written to PM
4.11 Issues Related to PM Hardware
Abstract
持久化存储Persistent memory (PM)越来越多的被用于构建基于哈希的索引结构,并且有着低成本持久化、高性能和及时恢复等特性,特别是随着最近 Intel Optane DC Persistent Memory Modules(DCPMM)的发布,PM越来越火热。然而,大部分PM是在基于DRAM的仿真器emulators进行的评估,并且假设unreal,专注于特定的指标而越过了重要的特性。因此,理解最近提出的哈希索引在真实的PM上表现如何,以及在不同性能标准下比较不同索引技术之间的区别很重要。
为此,本文对持久化哈希表persistent hash tables提供详细的测评。在真实的PM硬件上测试6种最新的哈希表技术,包括Level hashing, CCEH, Dash, PCLHT, Clevel, SOFT。评估实验采用统一的测评框架和代表性的workload。除了常见的性能指标,也探索了不同硬件配置(PM带宽、CPU指令和NUMA)如何影响基于PM的哈希表性能。有了深入的研究,我们指出设计的trade-off,以及较好的范例,以及基于PM的哈希表未来可能的优化方向。
(在真实PM上对哈希表技术进行测评,给出建议)
1 INTRODUCTION
目前已经提出了多种基于PM的不同物理介质。PM的特征有byte-addressability, direct persistence, DRAM-scale latency。3D XPoint是最有名的PM技术之一,基于这项技术的DCPMM是首款商用PM产品。最近,很多人从宏观与微观角度都对DCPMM进行了测评,对本文有所启发,但其中一些结果不一致。Yang发现DCPMM的真正表现比slower persistent DRAM label指示的要更复杂和有细微差别。在PM上的应用性能与access size, workload, pattern, degree of concurrency密切相关。与之前工作不同,Lersch发现PM带宽是稀缺资源,对性能有很重要的影响。有研究显示,end-to-end write latency通常比read latency更低。在设计PM数据结构时没有充分考虑PM硬件的特征。例如,基于不对等的读写性能的假设,有些方法设计的read更多,write更少,这是不符合实际的。(设计与真实特征不符)
Scalable PM的出现有助于解决目前应用的问题,其中哈希索引结构/哈希表是许多软件系统的基础组件,能从PM的特征中受益,从而实现高性能和instant recovery。目前已经有许多面向PM设计的哈希表。他们要么基于DRAM emulation,要么是针对actual PM设计,要么从现有哈希表改进。这些方法在真实PM上表现如何,不同性能标准下有什么区别,在不同硬件配置下表现如何,目前仍不清楚。(缺少对哈希表的完善测评)
因此,本文在配有DCPMM的系统上对6种哈希表技术进行详细评估,使用PiBench的扩展benchmarking framework,能提供一系列接口和统一的测试环境。这6种技术覆盖了不同维度的很多技术。实验使用典型的workload,得到的启发可以指导未来基于PM的哈希表设计。贡献总结如下:
1. 使用扩展后的PiBench框架对哈希表技术评估,所有实验结果公平可比较。
2. 从宏观和微观角度评估基于PM的concurrent hash table,处理throughput, scalability, latency, load factor, LLC misses, resizing overhead, memory usage, recovery overhead,还考虑了PM硬件的影响,结合不同的CPU指令和NUMA框架。实验在真实PM上进行。
3. 对设计trade-off,范例,优化给出了建议和总结,能帮助未来基于PM的哈希表研究。
第2章介绍DCPMM和Persistent Memory Development Kit(PMDK)的背景知识。第3章介绍6种哈希表技术的设计与实现。第4章介绍实验结果。第5章总结观察结果。第6章介绍相关工作,第7章给出结论。(评估平台统一、评估标准扩充、结论具有指导意义)
2 BACKGROUND
2.1 Optane DC Persistent Memory
DCPMM是首款商用PM产品,它通过在易失DRAM和基于块的存储之间创建new non-volatile tier新的非易失层,改变了传统的计算机内存层次。
Hardware Architecture. DCPMM是随着Cascade Lake架构提出的,which supports multiple sockets (2/4/8), each consisting of one or two processor dies that comprise separate NUMA nodes。集成的内存控制器iMC可以与DDR4和DCPMM DIMMs相连。为了保持数据持久性,iMCs位于asynchronous DRAM refresh (ADR) domain,保证到达这的CPU存储能免受power failure。iMC对每个DCPMM有读写pending queues (RPQs, WPQs),但是只有WPQs在ADR域中。因此,当数据到达WPQs,一旦有系统crash,数据会被iMC flush到PM中。由于3D-XPoint物理介质的存取粒度是256 bytes,on-DIMM控制器会把更小的请求转成256 bytes,由于小数据的存储变成了read-modify-write操作,造成了写方法。On-DIMM控制器有small write-combing buffer (XPBuffer) 来合并adjacent writes。与WPQs相似,XPBuffer存在于ADR域中,因此all updates are already persistent when they arrive at the XPBuffer.(介绍DCPMM硬件架构)
Operating modes. DCPMM可以有Memory模式和App Direct模式。在每个模式下,内存都可以interleaved across channels and DIMMs。2个模式都允许通过load/store指令进行PM的direct access。在Memory mode,DRAM作为最常获取数据的cache,DCPMM提供没有persistence特性的大量存储空间memory capacity(DRAM补充)。在App Direct mode,应用程序和OS都知道有2种directly accessible memory。尽管PM是持久化的,CPU caches和registers都是volatile。当cacheline flush instruction,例如clflush, clflushopt, clwb指令执行,或者导致cacheline flush发生后,数据会在PM中持久化存储。为了保证系统崩溃后应用程序正确恢复,必须使用memory fences为避免指令重排。由于主要任务是评估性能,因此把DCPMM配置成App Direct mode。(DCPMM两种模式,设置成App Direct)
Performance. 尽管DCPMM为了direct and byte-addressable load/store access而设计,相比于DRAM,它有更低的带宽和更高的读写延迟。之前研究表明,DCPMM读延迟~300 ns,是DRAM的4倍。另外,DCPMM有非对称读写延迟,研究表明端到端写延迟as seen by the application比读延迟更低,因为当数据到达memory controller中的ADR区域后writes return。但是如果数据没有在RPQs中缓存,读很可能要访问DCPMM物理介质。最大的顺序读/写带宽是40GB/s和13GB/s,比DDR4 DRAM要慢3倍和11倍。对于随机读写,这种非对称带宽更加明显。(DCPMM读写延迟/带宽非对称)
2.2 Programming Persistent Memory
一方面,相比于需要serialization或者flushing到基于block的存储相比,PM能构建更快的数据结构。另一方面,PM打破了传统的volatile memory和external memory之间的volatile-persistent boundary,也创建了process cache和PM之间的new boundary。使得PM系统的编程更复杂,更易出错。例如,算法必须正确存储data,通过缓存到CPU cache或者使用non-temporal store and memory barrier来保证数据一致性consistency。然而,数据一致性需要存储顺序正确,并且数据永久存储。为了使存储(写大于8 bytes)是原子的,通常需要依赖特定的机制,例如redo/undo logging, copy-on-write, versioning, hybrid memory。这些机制复杂且容易出错。(PM系统编程复杂)
这些挑战可以通过利用PM编程库中的sound programming model来解决。为了尽可能与原先的哈希表实现保持一致,本文使用PMDK来map memory files, allocate PM memory, persist data,使用xfs文件系统(native DAX support: 可以直接获取PM backed files)来组织管理PM数据。(使用PMDK+xfs实验)
3 PERSISTENT HASH TABLES
介绍Level hashing [2018], Clevel hashing [2020], CCEH [2019], Dash [2020], PCLHT [2019], and SOFT [2019],按照以下分类标准:① converted from DRAM counterparts or specifically designed for real PM hardware; 由DRAM对应结构转化还是针对PM硬件设计;② based on various structures like separate chaining, open addressing, or extendible hashing; 基于不同结构;③ using different synchronization mechanisms (lock-based or lock-free); 使用不同同步方法;④ using distinct resizing strategy (dynamic or static); 使用不同resizing策略;⑤ employing different crash-consistency mechanisms 使用不同的崩溃一致性机制 (不同分类标准)
3.1 Level Hashing and Clevel
Level Hashing是针对PM的写优化、可扩展的哈希表,支持低成本的一致性保障和resizing。使用基于共享的2层结构,来实现常数级别的时间复杂度。图1(a)中,只有顶层bucket可寻址,底层的bucket用于存储从顶层收回的内容。类似于PCM友好的哈希表(PFHT),Level Hashing在每个bucket中有多个slots,在往full bucket中插入1个item时允许回收至多1个item。每个key可以从2个哈希计算得到的目标位置中选择,与Cuckoo哈希类似,从而提高load factor。(结构设计)
Level hashing中,当resizing时,创建1个4倍大的哈希表,在底层的bucket中的key-value pairs首先重新哈希到新的表,然后删除旧的底层bucket。这时,新的表成为了顶层,之前的顶层成为了现在的底层。为了实现低成本一致性,在delete, insert, resize操作时通过每个bucket head的bitmap(token),使用log-free方法。Level hashing在并发控制时依赖slot-grained lock,不维护指针,slot中保存真实KV。(扩容策略、一致性、并发控制)
缺点:1. 静态哈希方法,重哈希代价高;2. 没有在真实PM评估。
Clevel是基于Level hashing的lock-free并发哈希表,拥有动态多层结构,支持并发,resizing通过后台线程完成,不会阻塞并发查询。
3.2 CCEH
基于可扩展hash设计来解决Level hashing的缺点,具有低成本动态内存管理、常数级查找时间constant lookup time, 和failure-atomicity guarantee without explicit logging. 在传统的可扩展哈希中,维护一个directory,其中有保存bucket地址的指针。当bucket增加,directory可能消耗大量内存,因此不能reside in CPU cache。因此,CCEH提出在directory与bucket之间的中间层(segment),从而减少用于寻址bucket的指针数量。图1(b)中,directory 条目指向1个segment,其中有固定数量的bucket,通过哈希值的L个最低位LSBs进行索引,而segment是由G个最高位MSBs进行索引。通过把多个bucket放入segment中,可以减少directory的大小。(结构设计)
当bucket满了,插入item导致冲突发生时,与可扩展哈希中split bucket的方法不同,CCEH splits segment从而扩大容量。然而,分裂segment会导致low load factor和更多的PM访问,因为segment中其他bucket可能仍有空闲的slots。因此,CCEH在split之前使用线性探测来提升空间利用率。当split segment完成后,一些item需要移动到新创建的segment。CCEH使用lazy deletion策略来保证旧segment中移动的item不需要立刻清除,因为这些item会被search操作忽略,并被insert操作重写,从而避免extra writes。(扩容策略)
3.3 Dynamic and Scalable Hashing (Dash)
Dash是构建动态可扩展哈希表的整体方法,可以实现可扩展哈希Dash-EH和线性哈希Dash-LH. 与CCEH类似,Dash使用3层结构:directory, segment, bucket。图1(c)中,directory条目指向segment,其中包括64个常规bucket和2个stash bucket用于存储overflowed键值对。这两种bucket有相同的layout,每个bucket 256 bytes,在头部有32 byte metadata,然后有14个键值对。(结构设计)
Dash使用balanced insert, displacement, and stashing 技术来解决CCEH的low load factor问题。在插入key时,首先利用key的哈希值计算segment index s和bucket index b,然后根据metadata中的alloc分配位图在bucket b/b+1中寻找empty slot。如果两个bucket都满了,会触发displacement操作来给新key分配空间。在metadata中维护了membership bitmap从而加速displacement。如果前2个操作都失败,key就插入stash bucket. 如果插入成功,更新原本bucket的overflow flag,否则分裂segment。最坏情况下,Dash需要探测bucket b/b+1, stash bucket,造成更多的cache misses and PM reads。在metadata中也保存指纹FP(key的1 byte哈希值)用于加速negative queries。 Dash使用乐观并发控制,对插入操作使用bucket-level CAS lock。搜索操作lock-free,不过仍需验证返回的key。(平衡插入,并发控制)
3.4 PCLHT
Persistent Cache-Line Hash Table (PCLHT)是在RECIPE中讨论的chaining-based concurrent crash-consistent哈希表,是DRAM-based hash table CLHT的变体。
CLHT是cache友好的,因为bucket大小为64-byte (a common cacheline size). 每个bucket中有8 -byte word用于并发控制、next pointer和最多3个16-byte的键值对。通过保证每次哈希表更新在一般情况下只需要1次cacheline access,从而解决缓存一致性问题cache coherence problem。为了保证非阻塞读能得到正确结果,CLHT针对写操作使用bucket的atomic snapshot。插入和删除操作都使用a single atomic commit point that is ordered by memory fences: 对插入操作,在更新8-byte key之前写入正确value;对删除操作,key写入0. 如果表满,插入失败,CLHT 使用copy-on-write方法来resize table。(CLHT操作介绍)
因为插入、删除、resize操作通过单一原子存储来实现,它们能通过在对应的store指令后插入cacheline flushes和memory fences,从而转化成对应的持久化形式。(加有序变成PCLHT)
3.5 SOFT
SOFT是为了避免数据结构中持久化指针而提出的chaining-based lock-free hash table。SOFT包括2个主要部分:persistent node (PNode) and volatile node (VNode)。PNode包括1个键值对和3个validity bits。VNode包括1个键值对和2个指针:1个指向具有相同键值对的PNode,另1个指向下一个VNode。只有PNode在PM中持久化,所有VNode存储在DRAM中,SOFT没有实现resize操作。当插入key,新的PNode和VNode被分配,VNode会通过CAS与现存的VNode连接。删除操作类似,只是节点被删除。SOFT可以通过扫描PM中的PNodes和重构DRAM中的结构来从系统崩溃中恢复。(链式结构,插入删除恢复操作)
3.6 Summary
在表1中总结这些经常用于PM哈希表设计的哈希表特征,包括数据结构、并发控制方法、内存架构、resizing方法。
Structure. CCEH和Dash使用基于directory-segment的结构,这种3层结构比传统的可扩展哈希更空间高效。Level hashing使用基于共享的2层结构,提供常数级别复杂度的写constant-scale time complexity。PCLHT和SOFT是基于chaining的设计,使用指针来连接buckets或nodes。
Concurrency. 除了SOFT和Clevel,其他哈希表是基于锁的。Level hashing使用slot-grained locking, CCEH使用2层读写锁,PCLHT使用bucket-grained锁。Dash使用乐观锁机制,其中写由bucket-grained lock保护,读是lock-free。
Architecture. SOFT把volatile和persistent nodes分别存储在DRAM和PM。这种混合架构可能达到接近DRAM的性能,但是恢复时间长,依赖于DRAM中需要重建的结构大小。其他4种哈希表把数据存储在PM种,存在较长的读写延迟。
Resizing. CCEH和Dash都通过段分裂来扩展空间。段分裂比全表重新哈希需要更少的PM访问。另外,CCEH使用lazy deletion技术来减少段分裂的成本。PCLHT和Level hashing中,resizing操作会使哈希表大小翻倍,但是bucket-sharing结构能使得在resizing时重用2/3的buckets。Clevel的resizing操作lock-free。SOFT不支持resizing。
4 EXPERIMENTAL EVALUATION
4.1 Environment and Setup
Hardware Configuration.
CPU:Linux server (kernel version 5.0.0), 2个16核2.3GHz的Intel Xeon Gold 5218 CPUs,其中由32 hyperthreads, 32KB L1 instruction cache, 32KB L1 data cache, 1024KB L2 cache. The last level cache is 22MB in size.
内存系统:包括192GB of DDR4 DRAM and 768GB of Optane DCPMM (6 x 128 GB DCPMMs) configured in the App Direct mode. 对多线程性能和NUMA进行评估时使用2个CPU,其他情况使用1个CPU。
Parameters. 除了Level hashing,所用配置都与论文中一样。Level hashing中默认key和value的大小分别为16 bytes和15 bytes。因此a bucket with 3 slots and a 3-byte token can be aligned to 128 bytes (two cachelines).本文中使用8 bytes来存储键值对,因为哈希表只支持这个配置。因此Level hashing中的bucket大小为64 bytes。CCEH使用16KB segments和64-byte buckets(4 slots), and probes at most 4 cachelines (4 buckets, 16 slots, 256 bytes). Dash中a bucket has 14 slots that consume 256 bytes memory, and a segment contains 64 normal buckets and 2 stash buckets. PLCHT是基于chaining的,bucket is aligned to 64 bytes。SOFT也是基于chaining,每个node中有1对键值对,根据需要创建或删除node。
Implementation. 对于CCEH, Level, PCLHT, and SOFT,使用PMDK的libvmem库来管理PM内存。对于Dash,使用原有的实现,用PMDK中的libpmem and libpmemobj提供的接口。对于Clevel,也是用原有的实现,利用PMDK加上C++绑定。对于cacheline flush,为了更高效使用clwb指令。对于哈希函数,使用GCC的std::Hash_bytes,这个方法快速且能提供高质量的哈希。所有的哈希表都不允许重复keys。
Benchmark Framework. 由于容易使用和设计良好的架构,我们使用PiBench框架,并进行适当扩展。它可以通过触发常用操作来收集多种信息throughput, latency, and hardware counters using the Processor Counter Monitor (PCM) library through well-defined interfaces that can invoke common operations of indexing structures, such as insert, search, delete, and update etc. 我们扩展PiBench框架,使其能对其他功能进行评估:load factor, utilization, resize latency, recovery time, and the number of instructions targeting PM (cache flushing and memory fence) 为了评估特定哈希表,只需要实现PiBench定义的接口,封装到共享库中,从而被PiBench运行时加载。另外,我们使用的3个PiBench在workload generation提供的random distributions:uniform, self similar, and Zipfian
Workloads. 我们对不同workload的每种哈希表的单个操作 (insert, positive and negative search, delete)都进行了压力测试stress test。Negative search是指搜索不存在的key。除非另作说明,本文使用以下方法来产生workload。初始化哈希表其容量能容纳16M 键值对,SOFT除外,因为它是按需创建和产生,因此为其初始化16M head nodes,链表的头节点不用于数据存储。
为了衡量insert-only性能,直接插入200M 记录到空哈希表中。为了衡量search、delete、mixed workloads的性能,首先初始化200M items的哈希表(loading phase),然后执行200M 次操作 (measuring phase)。与之前的研究类似,实验的workload包括uniform、skewed distribution (80%的访问20%的key. self similar with a factor of 0.2, which means 80% of accesses focus on 20% of keys)。由于CCEH, Level hashing, PCLHT都不支持变长键值对,因此只考虑固定长度8-byte的键值对。
4.2 Single-threaded Performance
Search. 搜索是哈希表中最基本的操作,因为对哈希表更新都需要先确认某个key是否存在。PCLHT由于设计思想the search operation should not involve any stores, waiting or retries,具有最高的正向搜索性能。另外PCLHT在扩展容量时进行全表重哈希,能保证基于chaining的bucket足够小,从而使得在哈希表满时的搜索性能基本是constant time。我们在运行search-only的workload时收集the last level cache (LLC) misses。在图3(b)中,PCLHT的数量是最少的,平均每个search只需要读1.4 cacheline accesses。另外图3(a)中反映PCLHT读的数据也更少。(搜索正向搜索吞吐量TP最大,读的数据和LLC miss最少)
与之对比,SOFT需要遍历长的链式bucket,导致最大数量的LLC miss (see Figure 3(b)),但是SOFT是从存储在DRAM的VNodes中读取keys,没有PM读(see Figure 3(a))。另外发现SOFT的正向搜索比负向搜索吞吐量throughput更高(1.4x and 2.0x under uniform and skewed distribution respectively). 这是因为SOFT在负向搜索时需要遍历所有nodes,导致了更高的LLC miss (see Figure 3(b)).(正向搜索比负向搜索TP高,不读PM,LLC miss最多)
Dash在metadata中维护了指纹数组,能帮助避免不必要的PM访问。由于这个优化,Dash在负向搜索时吞吐量最高。为了查找已存在的item,Dash最坏情况下需要probe 4 buckets (target bucket, neighbor bucket, and two stash buckets)。(负向搜索TP最高,LLC倒数第二)
而CCEH需要探查5个连续的bucket。这说明Dash最坏情况下需要access 4 XPlines in the XPBuffer,而CCEH最多只需要access 2 XPlines。因此Dash从PM读的数据大约是CCEH的1.3倍(see Figure 3(a)),导致正向搜索的吞吐量略低于CCEH。(正向搜索TP第二)?
Clevel从PM读的数据最多,因为需要探查哈希表的所有层(b2t搜索,还可能重搜),并且dereference pointers后才能获取最终的键值对,因此Clevel的吞吐量最低。(正向搜索TP最低,读数据最多;负向搜索也有指纹优化,为什么最低)?
Insert. 进行插入记录时,首先查找哈希表,确保没有相同的key,因此插入操作的性能依赖于查找操作。
图2(a)中反映,insert-only + uniform workload下,SOFT和CCEH的吞吐量接近。如果剩余空间不够,插入操作会造成resizing。因此resizing代价对insert吞吐量有重要影响。
(SOFT动态结构,插入快,CCEH3层结构,在skew时线性探测降低一点性能;SOFT是其他线程帮写,且写入PM的结构数据量少,插入写数据最少)
Level hashing和PCLHT的吞吐量更低,因为需要耗时的resizing操作,会产生大量的额外PM写。另外,由于Level hashing的查找性能更低,因此其吞吐量小于PCLHT。(中游)
至于CCEH和Dash,有相似的resizing和search性能,但是CCEH的吞吐量更高,因为需要的每次插入cacheline flushes更少(see Figure 3(b))、写入PM的数据更少(see Figure 3(a)). 由于skewed分布产生了重复的keys,这种情况下的吞吐量有点不同。(Dash插入更复杂一点,TP比CCEH低一点)
PCLHT的吞吐量大大提升,因为resizing代价降低(fewer keys to be rehashed)。Clevel的吞吐量最低,因为需要为新插入的item动态分配内存,并且为了保证crash consistency需要对每个item记录很长的log entry。(并发复杂TP最低)
Delete. 删除操作的性能也与查询性能有关,在删除前需要检查key是否存在。删除操作需要多次读,只需要1次写。因此,删除的性能与正向搜索趋势类似。
SOFT在删除key后释放node,链表的大小随着key删除不断缩减,要探查的nodes越少,SOFT的查询性能越高,因此其在uniform和skewed情况下吞吐量都最高。(最好)
PCLHT即使查询性能好,但删除性能仍比SOFT低,这是因为PCLHT只是把key标记为invalid,包含已删除的keys的nodes仍然存在,导致搜索时额外的检查。在uniform下,Dash, CCEH, SOFT的性能差不多。由于查询效率低,Clevel的删除吞吐量也很低。(最差)
正向搜索:PCLHT > CCEH > Dash > SOFT > Level > Clevel
负向搜索:Dash > PCLHT > CCEH > Level > SOFT > Clevel
插入: CCEH ~ SOFT > Dash ~ PCLHT > Level > Clevel
删除: SOFT > Dash > CCEH > PCLHT > Level > Clevel (吞吐量从高到低)
4.3 Multi-threaded Performance
与第4.2节配置一样, write heavy: 80% inserts and 20% searches。Balanced: 50% inserts 和50% searches。Read heavy: 20% inserts and 80% searches。
Search. 从图4(b)(c)(f)(g)中可以看出,当线程数<32时,所有哈希表scale well。PCLHT表现最好,与单线程性能相符。对于skewed下的负向搜索,PCLHT性能很好,在64个线程下达到60Mops/s的吞吐量,1.2x/2.2x/3.1x/4.3/7.1x of Dash/SOFT/Level/CCEH/Clevel。(PCLHT正向搜索最好)
SOFT是动态lock-free哈希方法,搜索操作只需要访问DRAM,保证其性能较好。然而,SOFT也使用指针来连接节点,不会resize哈希表。因此,链表长度会限制搜索吞吐量。(SOFT处于中上游)
对于Dash,threads can proceed without holding any locks for reads,使得其可扩展性较好。Dash使用的指纹技术能提升负向搜索的吞吐量,因为可以filter negative lookups to PM。因此,在uniform情况下,Dash比PCLHT的负向搜索性能要高(see Figure 4(c)). 在skewed情况下,PCLHT比Dash的负向搜索性能稍微好一点,因为访问的数据更少(20%)。(负向搜索PCLHT与Dash差不多)
CCEH比Dash性能低,因为即使对读操作也使用了悲观锁。通常来说,非阻塞读的哈希表能获得更高的搜索性能。(CCEH中下游,最后是Level和Clevel)
Insert. SOFT比其他方法的性能更高。图4(a)(e)中显示,在线程数达到32时,uniform和skewed下的吞吐量分别时Level hashing的14.7倍和13.7倍。SOFT的高性能有4个原因:① Lock-free ② 在数据结构中完全避免保存任何指针 ③ 使用最少指令least amount of flush instructions (Figure 3(b)) ④ 每个操作的指令数接近与理论最小值 The number of fence instructions per operation is close to the theoretical minimum (Figure 3(b))。(SOFT最好,动态)
Dash需要在bucket之间移动item以解决冲突,从而造成了更多的flush和写,其吞吐量略小于CCEH。(两个中游)
Level hashing吞吐量最低,因为需要resizing时整个表被锁住,另外还需要在resizing过程中删除bucket中的item,从而导致额外cache flush和PM写。(最差)
类似的,PCLHT的插入性能也很低,这是因为resizing代价(the second-largest overhead detailed in Section 4.6)。Clevel继承Level hashing的设计,即使在resizing时有lock-free优化,其性能也差不多。(插入多重构,性能也很低)
Delete. 图4(d)(h)中反映,与插入类似,SOFT表现最好。从图2中看出PCLHT比CCEH和Dash的单线程性能要略低,但多线程时要好,因为删除操作包括读和写,PCLHT的读更少,写与CCEH和Dash可比较,因此其bandwidth utilization更加高效。(SOFT>PCLHT> Dash >CCEH)
Mixed. 为了衡量mixed workload下的性能,初始化200M 记录的哈希表,执行200M次不同读写比例的操作。PCLHT性能最好。与insert-only相比,PCLHT在write heavy情况下吞吐量更高。因为在测评前插入了200M记录,free bucket是充足的,且不会触发resizing。当read heavy时,在uniform下PCLHT的吞吐量从19Mops/s to 32Mops/s steadily,在skewed下达到大概47Mops/s. CCEH, Dash, SOFT在write heavy和balanced下趋势相似。
大部分哈希表在32个线程时吞吐量最大。但是,① SOFT在delete-only情况下吞吐量持续增加,因为SOFT查找DRAM中的keys,避免了PM的读访问,另外图3(a)中也显示了SOFT触发PM的写更少。 ② PCLHT does not level off until 64 threads under mixed workload. 因为PCLHT相比于其他方法的读写操作更少,在相同带宽限制下能执行更多操作。
结论: 线程数小于32时,随线程个数增加,吞吐量增大。
正向搜索:PCLHT > Dash ~ SOFT > CCEH > Level > Clevel
负向搜索:Dash ~ PCLHT > CCEH ~ SOFT > Level > Clevel
插入: SOFT > CCEH ~ Dash > PCLHT > Level > Clevel
删除: SOFT> PCLHT > Dash > CCEH > Level > Clevel
Mixed: PCLHT > Dash ~ CCEH ~ SOFT > Level ~ Clevel (吞吐量从高到低)
4.4 DCPMM Scalability
为了评估不同DCPMM个数导致的吞吐量变化情况,重新运行第4.3节中1个DCPMM的多线程测评。定义吞吐率throughput ratio:使用6个DCPMM(interleaved)的吞吐量/单个DCPMM的吞吐量,用于反映DCPMM可扩展性。更高的比率反映更好的可扩展性,最理想的值是6。图6反映不同线程数下的结果,CCEH和Dash在more threads stressing the PM subsystem时吞吐率更高,说明通过增加DCPMM个数可以增加吞吐量。
但是,即使在很多线程情况下DCPMM数量多不一定能提升吞吐量,Level hashing和SOFT就是这种情况。对于insert-only和mixed,Level hashing的吞吐率基本为1,在正向搜索时最大ratio也小于2。SOFT是把部分压力交给DRAM的混合内存设计,其吞吐量对DCPMM个数不太敏感。对于搜索,不管多少线程SOFT的吞吐率始终大概为1.
总之,对于需要PM带宽的哈希表,分配更多DCPMM能有效提升性能。对其他哈希表,we observe only marginal returns with additional DCPMMs installed。对于SOFT这种混合内存设计,只需要配置正确的DCPMM数量,把更多DIMM slots留给DRAM更好,因为DRAM的带宽和容量可能对整体性能贡献更高。多少DCPMM和多少线程这里不做讨论。
可扩展性从好到坏:CCEH ~ Dash > Clevel ~ PCLHT > Level ~ SOFT
4.5 Latency
大规模系统suffer from unpredictable high percentile (tail) latency variations. 尾延迟反映了大部分操作的响应时间,但是有些设计会牺牲尾延迟来获得更高的吞吐量。我们收集uniform下的实验结果,从而排除caching影响和获得更稳定的access pattern。分析了单线程的尾时延,但是没有获得其他启发。因此只在图7中画出多线程的实验结果。
Insert. 除了SOFT所有哈希表都有高延迟,因为SOFT采用lock-free并发控制方法和混合内存架构。the tail latencies of CCEH, Level, Dash, Clevel, PCLHT and SOFT increase to milliseconds (about 1.6ms, 0.2ms, 2.3ms, 46ms, 0.7ms and 0.02ms respectively) at 99.999 percentile。最差情况下,每个方法的延迟大概为8ms, 58751ms (resizing triggered), 16ms, 325ms, 31134ms (resizing triggered), and 42ms。同时,CCEH和Dash的尾延迟超过其他方法,因为它们有结构性修改(段分裂)。
Search. 搜索的尾延迟更依赖于并发控制方法。因此,CCEH有最高的正向延迟,因为使用了悲观锁。对于负向搜索,延迟基本固定,因为负向搜索需要遍历所有bucket。每个方法CCEH, Level, Dash, Clevel, PCLHT and SOFT的正向/负向搜索延迟in the scale of several microseconds (about 18/18µs, 10/12µs, 7/7µs, 18/60µs, 6/7µs, and 9/10µs respectively) at 99.999 percentile。
Delete. 删除操作可能造成多次读。因此,其延迟与搜索代价紧密相关。SOFT的延迟最低,因为在删除记录后清除目标node,使得chaining-based buckets更短。其他方法的延迟为CCEH, Level, Dash, Clevel, PCLHT and SOFT are tens of microseconds (about 34µs, 26µs, 58µs, 67µs, 23µs and 18µs respectively) at 99.999 percentile
多线程插入 SOFT > Level > PCLHT > CCEH > Dash > Clevel
搜索 PCLHT ~ Dash > SOFT > Level > CCEH ~ Clevel
删除 SOFT > PCLHT > Level > CCEH > Dash > Clevel (尾延迟从低到高)
4.6 Resizing
对于需要动态容量扩展的哈希表来说resizing是不可缺少的。使用第4.1节中的insert workload对resizing进行实验。图8中反映,resizing的代价不应被忽略。CCEH为了扩展容量需要两个操作:段分裂的代价大约恒定是37.9𝜇s,与哈希表大小无关;directory doubling的代价随表大小而变化。当插入112M items来触发这个doubling操作花费了4.2ms。Level和PCLHT的resizing代价更高,随着表大小而增长。Dash的代价略高于CCEH。
CCEH的resizing代价最低,因为CCEH每次只需要分裂段,复制到新段中,旧的重复数据在更新时删掉(lazy deletion). Dash的结构与CCEH类似,区别在于Dash在每个bucket中维护32 bytes的metadata,移动到新段的items在旧段中会删除,这需要往PM中写入更多数据。因此Dash中的段分裂更耗时(55.6𝜇s on average),而doubling代价与CCEH差不多。当插入117M items后花费4.2ms来扩展directory。
尽管Level hashing的结构允许其在resizing时减少重哈希的次数,其代价仍比动态哈希表要高。Level hashing中的resizing可以分为3步:① 创建新表 ② 锁定整个表,把items移动到新表的底层 ③ 释放旧的底层表。底层表的bucket用于装顶层表中冲突的items。在resizing后,新的底层可能full而无法存储更多item,因此当新的顶层表中发生冲突时,又要resize。在resize过程中,Level需要删除原本的底层表以维持一致性,从而导致额外的PM写。Level的resize策略导致高成本。当插入172M items后需要58.8s来完成resizing操作。
类似的,PCLHT也需要重哈希整个表,锁定整个哈希表然后创建一个翻倍容量的新表,再重新把所有items哈希到新表中,最后释放旧表空间,而不是像Level hashing那样删除旧表的所有items?。因此,PCLHT的代价比Level hashing低,但是仍旧挺大的,当插入105M items后需要31.1s来resize。在图9中比较resizing与整体执行时间。Level hashing花费了24.8%,PCLHT花费了13.6%,CCEH和Dash分别花费了5.0%和5.8%。由于Clevel使用后台线程进行resizing,与查询线程是并行的,因此其resizing的代价被隐藏了effectively hidden。
Resize耗时从低到高:CCEH 略好于 Dash > PCLHT > Level
4.7 Load Factor
填装因子是哈希表的关键指标:使用的slots/哈希表中slots总数。在图10中使用第4.1节中的insert workload来衡量load factor.
CCEH最低,因为它探测距离短,容易触发段分裂。其默认探测距离是4 cachelines能容纳4个bucket或16个slots,即使段里的其他bucket中有空闲slots,仍有可能发生哈希冲突从而导致过早段分裂。因此CCEH的load factor只能达到默认探测距离的44%。当探测距离设置为64 (256 slots),填装因子可以增加到92%。然而过长的探测距离会论文笔记Persistent Memory Hash Indexes: An Experimental Evaluation
论文笔记Understanding the Idiosyncrasies of Real Persistent Memory
论文笔记Understanding the Idiosyncrasies of Real Persistent Memory
Persistent Memory in Exadata X8M
自用Deep Memory Network 深度记忆网络笔记
Core Data存储数据出错(This NSPersistentStoreCoordinator has no persistent stores (unknown))