论文导读 | 并发数据结构与并发控制

随着处理器单核性能的提升越来越困难,人们更多的通过多核并发的方式来提升性能。而并发会带来很多的问题,如数据冲突,负载均衡等等。本文分享了两边关于并发处理的相关研究,希望对大家有所帮助。

原文Constant-Time Snapshots with Applications to Concurrent Data Structures发表在PPoPP。

问题背景:

多处理器设备在大规模计算中的广泛使用使得对高效的并发数据结构的研究也越来越受重视。并发数据结构主要可以分为使用锁来避免竞争和通过无锁的方式避免竞争两种。很多使用了并发数据结构的应用需要连续的查询一大段区间或者对同一数据结构的多个随机区域进行查询,例如从一个队列中筛选出满足给定条件的元素或者多个线程同时查询一个哈希表等。这些操作可以很容易的通过加一个粗粒度的锁来实现,但是这样会带来严重的竞争。一些并发数据结构通过放弃原子性来保证效率,还有一些方法通过对查询进行额外限制来解决。一个更常见的方法是保存一个只读的快照来解决查询冲突。这些查询可以通过保存一个快照并在快照上读取必要信息的方法来回答。快照是数据库实现多版本和恢复的常用方法,但是已有的研究或者限制了编程模型,或者使用锁来实现,容易产生冲突,或者是无锁的但运行时间没有保证。这篇文章提出了一种基于快照的方案,并且可以保证在有界的时间内执行单个操作。

算法设计:

这个方法是基于CAS原语实现的。CAS是由硬件提供的原子操作,形式为CAS(&T,A,B),表示若T的值为A,则将T修改为B,否则不变。返回这一次赋值是否成功。CAS原语可以用来实现无锁的方案,例如在并发的索引结构中的应用。但是不能直接实现一些很复杂的计算。

考虑这样一个问题模型:现在需要通过单链表的方式维护一个值的所有历史版本,支持对历史值的查询。使用CAS原语可以实现对这个值的更新,但是无法直接维护历史版本信息。可以通过封装一个支持维护多版本的“CAS类”来实现。伪代码如下:

在Camera类中,维护了一个时间戳timestamp,提供takeSnapshot函数来实现快照。实现方式为,对于每一次更新记录并存储他的时间戳,在进行查询操作的时候,获取这一次查询操作之前的更新操作的时间戳,并将这个时间戳加一。这个获取到的时间戳所对应的数据就是这一次快照的结果。查询操作使用这个时间戳访问这个封装这个数值的类对象,获取到需要的信息。多个查询可能会同时调用一个Camera类的takesnapshot接口获取快照。而实际上并不需要每一个查询操作对应一个时间戳,同时到来的查询操作可以共享这个时间戳,即这些操作只要有一个成功将时间戳加一即可。

每个CAS类支持三个操作,读取最新数据,读取历史版本,以及类CAS原语的更新操作。具体实现见伪代码。

通过这个CAS类,可以很方便的实现一些数据结构的多版本,例如队列,链表,二叉搜索树等等,详细实现见原文。核心思想是使用CAS类实现上述数据结构中的指针的多版本。

算法优化:

1. 在只有结构会发生变化,而每个节点上的数据不会发生改变时,直接使用前述方法会单独维护一个指针的CAS类以及原节点,需要多一次跳转。可以通过将存储在节点上的数据也放在CAS类中。

2. 如果查询不频繁,则会有很多更新拥有相同的时间戳,其中只有最新的版本是有用的。可以在每次更新的时候,检查最顶上的节点的时间戳和当前更新是否相同,如果相同则替换来保证每个时间戳只存储了一个值。实现细节见原文。

3. 虽然只使用了CAS,但是CAS操作之间仍然可能发生竞争(一直赋值失败)。在需要保证CAS成功的地方,使用指数回退的方式等待然后执行,可以显著减少竞争。

内存回收使用使用EBR(Epoch based reclamation)的一种变型DEBRA。

实验:

作者使用java和c++分别做了实现。见下图,其中VcasCT-64和VcasBST-64是本文的方案。CT(balanced

non-blocking chromatic tree)是一种二叉搜索树,BST实现的是the non-blocking binary

search trees (NBBST)。RQ指的是范围查询。分别实现了在不同操作模式下的吞吐量、空间消耗等。此外还有并行加速比、算法本身带来的额外开销等的测试。

原文Sortledton:a Universal, Transactional Graph Data Structure发表在PVLDB。

问题背景:

在图系统中主要由有三种任务:

1. 图分析,典型问题有page rank。

2. 图模式匹配,典型问题有三角形计数。

3. 图遍历,典型问题有单源最短路。

三种任务对应三种不同的执行模式。已有的工作往往不能同时处理好这三类问题,本文提出了一种在三种任务上都有很好表现的系统。这些相关工作中,Teseo是一个可以同时处理好这三类问题的系统。但是和本问题提出的方法不同,Teseo是基于CSR-like的底层结构实现的,而本文的方法是基于邻接链实现的。一些相关工作的分类见下图。

问题分析:

图问题中,常见的访存模式有四种。

1. 顺序访问所有节点和他们的邻居,例如page rank。

连续存储适合顺序访问邻居的模式,CSR-like结构在这种模式下效果更好。但是本文作者认为,虽然CSR在这种模式下表现更好,但是这种访存模式在所有问题中的占比并不是太大,而且这种结构相较链式结构的提升并没有特别大,所以使用CSR-like结构综合来看未必会带来提升。在并发场景下,链式结构更容易维护,所以本文选择了基于链式结构的实现。

2. 顺序访问一个点的邻边,例如三角形计数问题。

这个访存模式下的应用往往需要对两个点的临边进行求交,所以需要一个能维护有序集的结构。通过实验发现基于B+Tree的实现和基于跳表的实现在性能上没有太大区别,而跳表不需要全局再平衡,在并发场景下表现会更好。

3/4. 根据算法随机访问一些信息、随机访问所有节点和他们的邻居

对于这两种访存不是本次分享讨论的重点,有兴趣的读者可以自行查阅该文章的相关部分。

图系统上的事务:

图事务的一个特点是写操作简单,读操作复杂。通常有以下几类

1. 运行时间长且复杂的只读操作。

2. 简单且用时短的写操作,且写操作的作用域不会很大。

3. 复杂的读写操作,且作用域很大,不可预测。

因此一个理想的图系统应该

1. 将只读查询和写操作分开。

2. 优化作用域有限但是频繁使用的写操作。

系统设计:

在底层存储上选择邻接链表,点的存储使用两层结构,第一层存到第二层的指针,第二层存一系列长度逐个倍增的数组,临边的存储选择unrolled skip list,见下图。

在并发控制上,注意到图事务的一个特点是写操作简单,读操作复杂。并且通常,图事务只包含边的增删操作,既只会有两个版本。根据上述特征,本文提出了一种在已有的一些关系型系统的多版本协议(MVCC和两阶段锁)基础上改进的并发控制模型。对于读写事务:两阶段锁协议,先执行读操作,确认一致性,然后再获取时间戳,用这个时间戳执行所有写操作。对于只读事务:ROMVCC,先获取读锁,读取需要的版本后释放锁。读锁不会有太大性能损失因为单个读操作时序时间不会太长。

实验:

Fig 是随机的一个一个插入边的结果,Fig 10是根据构造的顺序插入的结果。GraphOne使用无锁方案,所以不受数据分布的影响,而本文的方法是基于锁实现的,在特殊分布的数据下受竞争影响严重。

下图是上述实验中的延迟和内存消耗情况。更多的实验见原文。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值