Answers:
跳过列表更适合并发访问/修改。Herb Sutter写了一篇关于并发环境中数据结构的文章。它具有更深入的信息。
二进制搜索树最常用的实现是红黑树。修改树时,并发问题常常需要重新平衡。重新平衡操作可能会影响树的大部分,这将需要在许多树节点上使用互斥锁。将节点插入跳过列表的位置更加本地化,只有直接链接到受影响节点的节点才需要锁定。
乔恩·哈罗普斯的最新评论
我读了弗雷泽和哈里斯的最新论文《并发编程无锁》。如果您对无锁数据结构感兴趣,那真是好东西。本文重点讨论事务性存储和理论上的多字比较交换MCAS。由于没有硬件支持它们,因此两者都在软件中进行了仿真。他们给我留下了深刻的印象,他们完全能够在软件中构建MCAS。
我没有发现事务性内存特别吸引人,因为它需要垃圾收集器。另外,软件事务存储器还受到性能问题的困扰。但是,如果硬件事务性存储器变得普遍,我将感到非常兴奋。最后,它仍在研究中,并且将在未来十年左右的时间内不再用于生产代码。
在8.2节中,他们比较了几种并发树实现的性能。我将总结他们的发现。值得下载pdf,因为它在第50、53和54页上有一些非常有用的图形。
首先,您不能公平地将随机数据结构与可以提供最坏情况保证的数据结构进行比较。
跳过列表等效于随机平衡的二进制搜索树(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我的意思是,您可以自己运行这些。
此外,除了给出的答案(易于实现,再加上与平衡树相当的性能)。我发现实现有序遍历(向前和向后)要简单得多,因为跳过列表实际上在其实现内部具有一个链表。
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延续的一种特殊情况。
在实践中,我发现我的项目上的B树性能比跳过列表要好。跳跃列表似乎更容易理解,但实施的B树是不是该努力。
我知道的一个优势是,一些聪明的人已经弄清楚了如何实现仅使用原子操作的无锁并发跳过列表。例如,Java 6包含ConcurrentSkipListMap类,如果您发疯了,可以阅读源代码。
但是写一个并发的B树变体也不是很难-我看过别人做过-如果您先行拆分并合并节点以防万一,当您沿着树走时,则不必担心死锁,并且一次只需要在树的两个级别上保持锁。同步开销会更高一些,但B树可能更快。
从Wikipedia文章中引用:
Θ(n)操作迫使我们按升序访问每个节点(例如打印整个列表),从而提供了以最佳方式对跳过列表的级别结构进行幕后非随机化的机会,将跳过列表移至O(log n)搜索时间。[...]我们最近未对其执行[任何此类]Θ(n)操作的跳过列表,无法提供与更传统的平衡树数据结构相同的绝对最坏情况性能保证,因为它始终是可能的(尽管可能性很小),用于构建跳过列表的硬币翻转会产生平衡差的结构
编辑:所以这是一个折衷方案:跳过列表使用较少的内存,有可能退化为不平衡树的风险。
跳过列表是使用列表实现的。
存在用于单链和双链列表的无锁解决方案-但是没有无锁解决方案直接将CAS用于任何O(logn)数据结构。
但是,您可以使用基于CAS的列表来创建跳过列表。
(请注意,使用CAS创建的MCAS允许任意数据结构,并且已经使用MCAS创建了概念证明红黑树)。
因此,虽然很奇怪,但它们却非常有用:-)
跳过列表确实具有锁定剥离的优势。但是,残缺时间取决于如何确定新节点的级别。通常,这是使用Random()完成的。在56000个单词的字典中,跳过列表比张开树花费更多的时间,而树比哈希表花费更多的时间。前两个不能匹配哈希表的运行时。同样,哈希表的数组也可以以并发方式进行锁定剥离。
当需要参考位置时,将使用“跳过列表”和类似的有序列表。例如:在申请中的下一个日期之前查找航班。
内存二进制搜索展开树很棒,并且使用频率更高。