跳过列表与二进制搜索树


Answers:


257

跳过列表更适合并发访问/修改。Herb Sutter写了一篇关于并发环境中数据结构的文章。它具有更深入的信息。

二进制搜索树最常用的实现是红黑树。修改树时,并发问题常常需要重新平衡。重新平衡操作可能会影响树的大部分,这将需要在许多树节点上使用互斥锁。将节点插入跳过列表的位置更加本地化,​​只有直接链接到受影响节点的节点才需要锁定。


乔恩·哈罗普斯的最新评论

我读了弗雷泽和哈里斯的最新论文《并发编程无锁》。如果您对无锁数据结构感兴趣,那真是好东西。本文重点讨论事务性存储和理论上的多字比较交换MCAS。由于没有硬件支持它们,因此两者都在软件中进行了仿真。他们给我留下了深刻的印象,他们完全能够在软件中构建MCAS。

我没有发现事务性内存特别吸引人,因为它需要垃圾收集器。另外,软件事务存储器还受到性能问题的困扰。但是,如果硬件事务性存储器变得普遍,我将感到非常兴奋。最后,它仍在研究中,并且将在未来十年左右的时间内不再用于生产代码。

在8.2节中,他们比较了几种并发树实现的性能。我将总结他们的发现。值得下载pdf,因为它在第50、53和54页上有一些非常有用的图形。

  • 锁定跳过列表的速度非常快。随着并发访问的数量,它们的扩展性非常好。这就是使跳过列表变得特别的原因,其他基于锁的数据结构在压力下容易崩溃。
  • 无锁定跳过列表始终比锁定跳过列表更快,但仅勉强。
  • 事务性跳过列表始终比锁定和非锁定版本慢2-3倍。
  • 在并发访问下锁定红黑树发出的嘶哑声。每个新的并发用户的性能都会线性下降。在两种已知的锁定红黑树实现中,一种在树重新平衡期间本质上具有全局锁定。另一个使用花哨的(复杂的)锁升级,但仍然没有显着超出执行全局锁的版本。
  • 无锁的红黑树不存在(不再适用,请参阅更新)。
  • 事务性红黑树与事务性跳过列表具有可比性。这是非常令人惊讶和非常有希望的。事务性内存,尽管速度较慢,但​​更容易编写。它可以像快速搜索和替换非并行版本一样容易。

更新
这里是有关无锁树的论文:使用CAS的无锁红黑树
我没有深入研究它,但从表面上看似乎很牢固。


3
更不用说在非简并的跳过列表中,大约50%的节点应该只有一个链接,这使得插入和删除非常有效。
Adisak,

2
重新平衡不需要互斥锁。参见cl.cam.ac.uk/research/srg/netos/lock-free
Jon Harrop 2010年

3
@乔恩,是和不是。没有已知的无锁红黑树实现。弗雷泽(Fraser)和哈里斯(Harris)展示了如何实现基于事务性内存的红黑树及其性能。事务性内存在研究领域仍然非常多,因此在生产代码中,一棵红黑树仍然需要锁定该树的大部分。
deft_code 2010年

4
@deft_code:英特尔最近宣布通过TSX在Haswell上实现事务存储。在您提到的那些无锁数据结构中,这可能证明很有趣。
迈克·贝利

2
我认为Fizz的答案是最新的(从2015年开始),而不是这个答案(2012年),因此现在应该是首选的答案。
fnl

81

首先,您不能公平地将随机数据结构与可以提供最坏情况保证的数据结构进行比较。

跳过列表等效于随机平衡的二进制搜索树(RBST),在Dean和Jones的“探索跳过列表和二进制搜索树之间的对偶”中有更详细的说明。

相反,您还可以使用确定性跳过列表,以确保最坏情况下的性能,请参见。Munro等。

与上述说法相反,您可以拥有在并行编程中运行良好的二进制搜索树(BST)的实现。以并发为中心的BST的潜在问题是,很难像获得红黑(RB)树那样获得平衡方面的保证。(但是“标准”(即,随机排序的,跳过列表)也无法为您提供这些保证。)在始终保持平衡与良好(且易于编程)并发访问之间存在折衷,因此通常使用宽松的 RB树当需要良好的并发性时。放松之处在于不立即重新平衡树。有关过时的调查(1998年),请参阅Hanke的“并发红黑树算法的性能” [ps.gz]

对此的最新改进之一是所谓的色度树(基本上,您具有一定的权重,例如,黑色将为1,红色将为零,但您也可以在两者之间使用值)。彩色树如何抵制跳过列表?让我们看看布朗等。“无阻塞树的通用技术”(2014年)必须说:

我们的算法具有128个线程,比Bronson等人的基于锁的AVL树的性能好于Java的非阻塞跳过列表13%至156%。降低了63%至224%,而使用软件事务存储(STM)的RBT降低了13至134倍

编辑要添加:Pugh的基于锁的跳过列表,在Fraser和Harris(2007)“并发编程无锁”中作了基准测试,接近于他们自己的无锁版本(在此处的最高答案中充分强调了这一点),也针对良好的并发操作进行了调整,请参见。Pugh的“并发维护跳过列表”,尽管以相当温和的方式进行。尽管如此,2009年有一篇较新的论文“简单的乐观跳过列表算法”Herlihy等人提出了一个比普格的锁更简单的并发跳过列表实现方法,他批评普格没有提供足够的说服力来证明它们的正确性。撇开这个(也许太古怪的)忧虑,Herlihy等人。表明他们的基于跳转列表的更简单的基于锁的实现实际上无法像JDK的无锁实现那样扩展规模,但仅适用于高争用(50%插入,50%删除和0%查找)... Fraser哈里斯根本没有测试;弗雷泽(Fraser)和哈里斯(Harris)仅测试了75%的查找,12.5%的插入和12.5%的删除(在具有约500K元素的跳过列表中)。Herlihy等人的较简单实现。在他们测试的低争用情况下(70%的查询,20%的插入,10%的删除),它也接近于JDK的无锁解决方案;当他们使跳过列表足够大时(即从200K增加到2M个元素),他们实际上击败了这种情况的无锁解决方案,因此任何锁争用的可能性都可以忽略不计。如果Herlihy等人,那就太好了。已经克服了对Pugh的证明的困扰,也测试了他的实现,但是可惜他们没有这样做。

EDIT2:我发现了所有基准测试的母题(2015年发布):Gramoli的“关于同步的知识比你想了解的要多。Synchrobench,测量同步对并行算法的影响”:这是与此问题相关的摘录图像。

在此处输入图片说明

“ Algo.4”是上述Brown等人的前身(2011年,旧版本)。(我不知道2014版的好坏如何)。上面提到的Herlihy是“ Algo.26”;如您所见,它被更新浪费了,并且在这里使用的Intel CPU上要比原始纸张上的Sun CPU上要糟得多。“ Algo.28”是JDK的ConcurrentSkipListMap;与其他基于CAS的跳过列表实现相比,它的表现不如人们希望的那样。竞争激烈的获胜者是Crain等人描述的基于锁的算法“ !! 2”(!!)。在“争用场所的二叉搜索树”和“Algo.30”是“旋转skiplist”从“为多核对数的数据结构”。”。请注意,Gramoli是所有这三项优胜者算法论文的合著者。“ Algo.27”是Fraser跳过列表的C ++实现。

Gramoli的结论是,搞砸基于CAS的并发树实现比搞砸类似的跳过列表要容易得多。基于这些数字,很难不同意。他对此的解释是:

设计无锁树的困难源于原子地修改多个引用的困难。跳过列表由通过后继指针相互链接的塔组成,其中每个节点均指向紧接其下的节点。它们通常被认为与树类似,因为每个节点在后继塔中及其下方都有一个后继,但是,主要区别在于,向下的指针通常是不可变的,因此简化了节点的原子修改。如图[上图]所示,这种区别可能是跳过列表在竞争激烈的情况下胜过树的原因。

克服这种困难是Brown等人最近工作中的主要关注点。他们 在构建多记录LL / SC复合“基元”时有一个单独的完整论文(2013年),名为“非阻塞数据结构的实用基元”,它们称为LLX / SCX,它们本身是使用(机器级)CAS来实现的。布朗等。在其2014年(但未在2011年)并发树实现中使用此LLX / SCX构建块。

我认为在这里也许也值得总结“无热点” /竞争友好(CF)跳过列表的基本概念。它从轻松的RB树(以及类似的相似数据结构)中补充了一个基本思想:塔不再在插入时立即建立,而是延迟到争用较少的时候。相反,删除高塔会引起很多争执。早在Pugh于1990年发表的并发跳过列表文件中就可以观察到这一点,这就是为什么Pugh在删除时引入了指针反转的原因(可惜今天维基百科的跳过列表页面至今仍未提及这一点)。CF跳过列表使此步骤更进一步,并延迟删除高层塔楼的高层。CF跳过列表中的两种延迟操作均由(基于CAS的)单独的类似于垃圾收集器的线程执行,该线程的作者称之为“自适应线程”。

Synchrobench代码(包括所有经过测试的算法)可在以下网址获得:https : //github.com/gramoli/synchrobench。最新的布朗等。可以在http://www.cs.toronto.edu/~tabrown/chromous/ConcurrentChromaticTreeMap.java上获得实现(以上未包含)有人可以使用32+核计算机吗?J / K我的意思是,您可以自己运行这些。


12

此外,除了给出的答案(易于实现,再加上与平衡树相当的性能)。我发现实现有序遍历(向前和向后)要简单得多,因为跳过列表实际上在其实现内部具有一个链表。


1
一个bin树的顺序遍历不是这么简单:“ def func(node):func(left(node)); op(node); func(right(node))”吗?
Claudiu

6
当然,如果要在一个函数调用中全部遍历,则为true。但是,如果要像std :: map中那样进行迭代器样式遍历,则变得更加烦人。
伊万·特兰

@Evan:不是使用只能使用CPS编写的功能语言。
乔恩·哈罗普

@Evan :def iterate(node): for child in iterate(left(node)): yield child; yield node; for child in iterate(right(node)): yield child;?=)。非本地控制iz awesom .. @Jon:用CPS编写是很痛苦的,但也许您是指延续?生成器基本上是python延续的一种特殊情况。
Claudiu's

1
@Evan:是的,只要在修改过程中将node参数从树中切出,它就会起作用。C ++遍历具有相同的约束。
deft_code 2010年

10

在实践中,我发现我的项目上的B树性能比跳过列表要好。跳跃列表似乎更容易理解,但实施的B树是不是努力。

我知道的一个优势是,一些聪明的人已经弄清楚了如何实现仅使用原子操作的无锁并发跳过列表。例如,Java 6包含ConcurrentSkipListMap类,如果您发疯了,可以阅读源代码。

但是写一个并发的B树变体也不是很难-我看过别人做过-如果您先行拆分并合并节点以防万一,当您沿着树走时,则不必担心死锁,并且一次只需要在树的两个级别上保持锁。同步开销会更高一些,但B树可能更快。


4
我认为您不应该将Binary Tree称为B-Tree,而是一个完全不同的DS,名称为
Shihab Shahriar Khan

8

Wikipedia文章中引用:

Θ(n)操作迫使我们按升序访问每个节点(例如打印整个列表),从而提供了以最佳方式对跳过列表的级别结构进行幕后非随机化的机会,将跳过列表移至O(log n)搜索时间。[...]我们最近未对其执行[任何此类]Θ(n)操作的跳过列表,无法提供与更传统的平衡树数据结构相同的绝对最坏情况性能保证,因为它始终是可能的(尽管可能性很小),用于构建跳过列表的硬币翻转会产生平衡差的结构

编辑:所以这是一个折衷方案:跳过列表使用较少的内存,有可能退化为不平衡树的风险。


这就是反对使用跳过列表的原因。
Claudiu

7
引用MSDN,“ [100个1级元素]的机会恰好是1,267,650,600,228,229,401,496,703,205,376中的1”。
peterchen

8
您为什么要说它们使用更少的内存?
乔纳森

1
@peterchen:我明白了,谢谢。那么确定性跳过列表不会发生这种情况吗?@Mitch:“跳过列表使用较少的内存”。跳过列表如何使用比平衡二叉树更少的内存?看起来他们在每个节点和重复的节点中都有4个指针,而树只有2个指针,没有重复。
乔恩·哈罗普

1
@Jon Harrop:级别1的节点每个节点仅需要一个指针。每个更高级别的节点每个节点仅需要两个指针(一个指向下一个节点,一个指向其下一级),尽管当然,第3级节点意味着您对该一个值总共使用了5个指针。当然,这仍然会占用大量内存(如果您想要一个无用的跳过列表并具有较大的数据集,则比二进制搜索还要多)...但是我认为我缺少了某些东西...
Brian

2

跳过列表是使用列表实现的。

存在用于单链和双链列表的无锁解决方案-但是没有无锁解决方案直接将CAS用于任何O(logn)数据结构。

但是,您可以使用基于CAS的列表来创建跳过列表。

(请注意,使用CAS创建的MCAS允许任意数据结构,并且已经使用MCAS创建了概念证明红黑树)。

因此,虽然很奇怪,但它们却非常有用:-)


5
“没有任何无锁解决方案直接将CAS用于任何O(logn)数据结构”。不对。有关反例,请参阅 cl.cam.ac.uk/research/srg/netos/lock-free
Jon Harrop 2010年

-1

跳过列表确实具有锁定剥离的优势。但是,残缺时间取决于如何确定新节点的级别。通常,这是使用Random()完成的。在56000个单词的字典中,跳过列表比张开树花费更多的时间,而树比哈希表花费更多的时间。前两个不能匹配哈希表的运行时。同样,哈希表的数组也可以以并发方式进行锁定剥离。

当需要参考位置时,将使用“跳过列表”和类似的有序列表。例如:在申请中的下一个日期之前查找航班。

内存二进制搜索展开树很棒,并且使用频率更高。

跳过列表Vs播放树Vs哈希表运行时字典查找op


我快速浏览了一下,您的结果似乎显示SkipList比SplayTree快。
Chinasaur

将随机化假定为跳过列表的一部分会产生误导。如何跳过元素至关重要。随机添加了概率结构。
user568109 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.