一个用 C++ 实现的快速无锁队列

来源:互联网 发布:c语言 整数奇偶排序 编辑:程序博客网 时间:2024/05/01 16:18

http://www.oschina.net/translate/a-fast-lock-free-queue-for-cpp?print


在进程间传递数据很烦人,真心烦人。一步做错,数据就会损坏。(相较于其他读写方式)即使数据排列正确,也更易出错。

一如既往的,有两种方式处理这个问题:简单的方式、麻烦的方式。

简单的方式

使用你使用平台提供的锁(互斥、临界区域,或等效)。这从概念上不难理解,使用上更简单。你无需担心排列问题,库或OS会替你解决。使用锁的唯一问题就是慢(这里的“慢”是相对而言的:一般应用,它的速度足够了)。

The catch

我一直都在寻找这样一个音频程序,这个音频程序的回调(也就是说当设备需要更多的样例来填补缓冲时程序便被调用)运行在另一个线程中。设计这样音频程序的目标是永远也不要让这个程序出现问题。--这就意味着你要尽可能的让你的回调代码以真实程序时间运行在一个常见的操作系统上。所以,系统计算速度越快越好,但是真正的难点在于如何确定所谓程序真是时间也就是起关键性作用的计算时间--也就是说,你代码花费的时间也能是5ms也可能突然的变成300ms。

这意味着什么?这么说吧,就好像启动器不能分配堆内存空间--这是因为这个过程很可能关联着一些系统任务的调度,而这些调度很可能需要一些无关紧要的时间去完成(比如,就好像你必须要话费一定的时间去等待网页与磁盘之间进行的交换)。事实上,我们要尽量避免在内核上进行调用。然而,这还不是问题的关键。

问题的关键在于音频程序的调用线程与主线程之间的同步问题。我们不能使用锁,因为这回有一些不必要的消费(考虑到回调函数的经常调用),这还会引起优先级倒置--你没有去等着一个高优先级的音频线程,而这个音频线程正在等待一个关键性的部分通过后台线程去释放自己的空间,这就会使得音频程序产生问题。

困难的方式

走进无锁编程。

在竞争的情况下,无锁编程是一种编写线程安全代码的方式,保证系统作为一个整体推进。“无等待”编程进一步完善这点:代码被设置,这样不管对方在做什么,每个线程总可以推进。这也避免了使用昂贵的锁。为什么不是每个人都使用无锁编程呢?嗯,它不那么容易用对。任何情况下,不管每个线程在做什么,你必须很小心,绝不能破坏数据。但更难的是顺序相关的保证。考虑这个例子(a和b从0开始):

Thread A    Thread Bb = 42a = 1            print(a)            print(b)

线程B可能会输出的一组值是什么呢?有可能是{0 0 },{1 42},{0 42}和{1 0}, {0 0}, {1 42}中的任何一组,{0 42}(其实是一个取决于时间的结果)可以说得通,但是{1 0}是怎么出现的呢?这是因为只要在线程里出现,编译器都允许在加载和存储时(或者甚至在移除和构造时)进行重组以提高效率。但是当另一个线程开始与第一个交互时,重组就会变得很明显了。

情况变得糟糕了,你可以强行使编译器不对特定的读写进行重组,但是CPU也会允许(并且一直会)重组指令,只要指令在运行,内核中就会出现再一次相同的情况。

内存屏障

幸运的是,你可以通过内存屏障强制某些顺序保证始终下调到CPU级别。不幸的是,要正确使用它们也很棘手。事实上,无所编码的一个主要问题是很容易出现一些这样的漏洞,只能通过特定线程操作的非确定性交错的方式来重现。这意味着无锁算法可能存在一个95%的时间工作正常而另外5%的时间会失败的漏洞。或者它可能在开发者的CPU上总是正常工作,但在另一个CPU上偶尔失败。

这就是无锁编程通常被认为很难的原因:你很容易得到一些看上去正确,但需要更多努力和思考来创造一些能保证在任何情况下都工作的事物。幸好,有一些工具能帮助验证这些实现。其中一个工具叫Relacy:它的工作方式是,在各种可能的排列线程交织中运行(你写的)单元测试,这实在太酷了。

回到内存屏障:在CPU的最底层,每个核都拥有自己的缓存,并且这些缓存必须彼此之间保持同步(一种最终统一)。这是由CPU内核之间的一种内部信息机制来完成的(这样看来很慢?确实是的。所以这种机制经过高度优化,猜一猜它是怎么实现的,就是更多的缓存!)。因为两个内核在同事执行代码,所以会导致一些有意思的的内存加载/存储顺序(比如上面的例子)。内存屏障做的事情就是,在这个信息机制的顶层建立一些更强壮的顺序确保机制。我发现的最强大的内存屏障是acquire and release(这里翻译没啥意思,后面也会用这个名次)。“release” 内存屏障告诉CPU,如果屏障后的写操作对其他核变为可见,那么屏障前的写操作也必须保持可见。这种转变是在其他核读取数据(这些数据是“写屏障”之后写入的)之后,执行读屏障的时候进行的。换句话说,如果一个线程B可以看见另一个线程A在一个写屏障之后写入的新值,那么在执行一个读屏障之后(在线程B上),所有在写屏障之前A线程上进行的写操作都会对B线程可见。一个不错的功能: “acquire and release” 语义,类似于x86上的停止操作指令, 就是每个写操作都隐含release语义,每个读操作都隐含acquire语义。但是你仍然需要手动添加屏障,因为 “acquire and release” 语义不允许编译器重新排列内存加载顺序(并且它也会生成在其他处理器架构上同样正确的汇编代码,即使这些处理器没有强壮的内存排序模型)。

如果这令你很困惑,好吧,我承认确实很困惑。但是不要放弃希望,因为这里有一些资源,它们很好地解释了这个问题。从Jeff Preshing's very helpful articles on the subject这篇文章开始是不错的,这个页面上还有一些其他链接的列表: this answer on Stack Overflow

一个等待释放的单一的生产者,单一的消费者队列

自从我显然发现不可抗拒的突然转向时(译者:且这么理解),我,当然,建立了自己的无锁数据结构,我为了一个单一的生产者、单一的消费者体系结构尽力想求得一个等待释放的队列(意味着只有两个线程参与)。如果你立刻需要一个安全使用多线程的数据结构,你需要找到另一种实现(MSVC++带有一个)。我选择限制是因为它很大程度上操作要比释放全部并发线程简单,以及这就是我所有我需要的。

我看了一些目前的书籍(特别是liblfds),但是我不太满意内存管理(我想要一个不分配任何内存给所有关键线程的队列,使得它适合实时编程),所以,我忽略了关于只做无锁编程的大量建议除非你已经是这一领域的专家(如何成为一个这样的专家呢?),并能够成功地实现一个队列!

设计如下:

一个连续的环形缓冲区(buffer)被用来储存队列中的元素。这样允许内存被分配在最前面,并且可能提供更好的缓存使用。我把这个缓冲区叫做“区块”(block)。

为了使得队列能够动态增长,并且当区块变的太小时(总之不是无锁的)不需要复制所有已经存在的元素到新的区块里,许多的区块(有独自的大小)会被链接在一起形成环形相关联的列表。这样就形成了队列的队列。

元素正在被插入的区块叫做“尾区块”。元素正在被消耗的区块叫做“头区块”。相同地,在每一个区块里有一个“头"索引和一个“尾”索引。头索引指示了下一个将被读取的满的位置,尾索引指示了下一个将被插入的空的位置。如果这两个索引是相等的,那么这个区块是空的(一个位置会被清空当队列是满的,这样为了避免当一个满的区块和一个空的区块都有相同头和尾索引时产生的不明确性)。

为了保持一致性,两个线程操作同一数据结构,我们可以利用生产线程在队列中总是运行在一个方向上的实际情况和同样可以用来说明的消费者线程,这意味着,即使一个变量值在给定的线程中是过时的,我们也知道它可能的所在的范围。例如,当我们从一个阻塞出队,我们可以检查尾部索引的值(由其它线程拥有)来比较紧靠着前面的索引(该线程自己出队,并因此总是最新的),我们可以为尾部索引从CPU缓存获得一个旧值,其后入队线程可以增加更多元素,但是,我们知道尾部绝不会倒退——更多元素会被增加,只要尾部不等于前面我们检查过的,保证至少有一个元素出队。

使用这些担保,和一些内存分界线去阻止像被增加在元素前面(或者被认为是前加)事实上是被增加到队列尾部,设计一个简单算法来安全地入队和出队的元素在所有可能的线程交织下是可能的,这是伪代码:

# EnqueueIf room in tail block, add to tailElse check next block    If next block is not the head block, enqueue on next block    Else create a new block and enqueue there    Advance tail to the block we just enqueued to# DequeueRemember where the tail block isIf the front block has an element in it, dequeue itElse    If front block was the tail block when we entered the function, return false    Else advance to next block and dequeue the item there

单个固定尺寸的块的入队和出队算法是比较简单的:

1#向一个块的入队(假设我们已经检查了一个块中有空间)
2复制/移动元素进入块的连续存储空间
3队尾下标增长(需要重新设置队尾)
4 
5#从一个块的出队(假设我们已经检查了非空)
6从一个块的连续存储空间复制/移动元素到一个输出参数
7队尾下标增长(需要重新设置队尾)
显然,这掩盖了一些东西(比如可能存在的内存屏障),但是如果你希望检查底层错误细节的话, 实际代码也不会非常复杂。 

再思考这个数据结构,它对于区分消费者线程或生产者线程所拥有的变量(例如:写入独占变量)是非常有帮助的。对于一个给定的线程,他所拥有的变量永远不会过时。一个线程拥有的变量被另一个线程读取到的可能只是一个旧值,但是通过小心的使用内存屏障,但我们从一个并不拥有这个变量的线程读取时,我们能够保证其余的数据内容至少是新的。 

允许队列可能被任意线程创建或销毁(两个相互独立的生产者和消费者线程),一个完整的内存屏障(memory_order_seq_cst)用于构造函数的结尾和析构函数的开始; 这样可以有效的迫使所有CPU内核有效同步。显然,生产者和消费者必须在 析构函数可以安全调用之前已经停止使用队列。


给我代码

如果没有可靠的(已被测试的)实现,设计又有什么用呢?:-)

我已经 在GitHub发布了我的实现。 自由的fork它吧!它由两个头部组成,一个是给队列的,还有一个取决于是否包含一些辅助参数。

它具有几个优异的特性:

  • 与 C++11兼容 (支持移动对象而不是做拷贝)
  • 完全通用 (任何类型的模板化容器) -- 就像std::queue,你从不需要自己给元素分配内存 (这将你从为了管理正在排队的元素而去写锁无关的内存管理单元的麻烦中解脱出来)
  • 以连续的块预先分配内存
  • 提供 atry_enqueue方法,该方法保证不去分配内存 (队列以初始容量起动)
  • 也提供了一个enqueue方法,该方法能够根据需要动态的增长队列的大小
  • 不采用比较-交换循环;这意味着 enqueue和dequeue是O(1)复杂度 (不计算内存分配)
  • 对于x86设备, 内存屏障编译为空指令,这意味着enqueue与dequeue仅仅只是简单的loads和stores序列 (以及 branches)
  • 在 MSVC2010+ 和 GCC 4.7+下编译 (而且应该工作于任何支持 C++11 的编译器)


应注意的是,此代码只能工作于能处理对齐的整数和原生指针长度的负载/存储原子的CPU;幸运的是,这包括所有的现代处理器(包括 ARM,x86 / x86-64,和PowerPC)。它不能工作于 DEC Alpha(这玩意内存排序能力保证最弱)。

我发布的代码和算法遵循简化的BSD授权协议。你需要自己承担使用风险;特别是,无锁编程是一个专利的雷区,这代码很可能违反了专利(我还没查验)。需要提出的是,我是自己胡乱写出来的算法和实现,与任何现有的无锁队列无关。


性能测试和无误较正

除了折腾在相当长的一段时间的设计,我(X86)测试了一个简单的稳定性测试使用数十亿随机操作的算法。 当然,这有助于鼓舞信心,但不能证明什么的正确性。 为了确保它是正确的,我的测试也使用了Relacy,跑了一个简单的测试来测试所有可能的交错。没有发现错误;但是,事实证明这个简单的测试是不全面的,因为通过使用一组不同的随机运行,我发现了一个错误(当然我最后修正了这些)。

我只在x86-64架构的机器上测试此队列,内存占用是相当宽裕(少)的。如有人乐意在其他架构机器上测试这些代码,告诉我吧。快速稳定性的测试代码我放在了这儿 。

在性能方面,它是非常快的,真的非常的块。在我的测试中,能够达到约每秒大于12Million组的并发入队/出队的操作(如果队列中没有数据出队线程获取数据之前必须等待入队线程)。虽然在我实现我的队列之后,我发现另一个发布在 Intel的网站上的单消费者/单生产者模板队列(作者是Relacy);他实现的队列速度大致是我的两倍,但是他并没有实现我所实现的全部功能,并且他仅工作在X86平台(这种条件下,“两倍快”意味着这两种不同的入队/出队实现在时间上相差非常小)。 

更新于16天前 

我花了一下时间修正我的实验,分析和优化代码,使用 Dmitry的单生产者/单消费者自由锁队列(发布在 Intel网站)作为比较参照。目前我的实现相对更快一些,特别是涉及到多元素入队的时候(我的实现使用连续的块替代分开的元素链接方式)。注意不同的编译器会给出不同的结果,甚至相同的的编译器在不同的硬件平台上也显著的表现出速度有所不同。64位的版本通常比32位版本的快。因为某些原因,我的队列实现在Linode上,使用GCC编译器会更快。这里是完整测试结果:

00132-bit, MSVC2010, on AMD C-50 @ 1GHz
002------------------------------------
003                  |        Min        |        Max        |        Avg
004Benchmark         |   RWQ   |  SPSC   |   RWQ   |  SPSC   |   RWQ   |  SPSC   | Mult
005------------------+---------+---------+---------+---------+---------+---------+------
006Raw add           | 0.0039s | 0.0268s | 0.0040s | 0.0271s | 0.0040s | 0.0270s | 6.8x
007Raw remove        | 0.0015s | 0.0017s | 0.0015s | 0.0018s | 0.0015s | 0.0017s | 1.2x
008Raw empty remove  | 0.0048s | 0.0027s | 0.0049s | 0.0027s | 0.0048s | 0.0027s | 0.6x
009Single-threaded   | 0.0181s | 0.0172s | 0.0183s | 0.0173s | 0.0182s | 0.0173s | 0.9x
010Mostly add        | 0.0243s | 0.0326s | 0.0245s | 0.0329s | 0.0244s | 0.0327s | 1.3x
011Mostly remove     | 0.0240s | 0.0274s | 0.0242s | 0.0277s | 0.0241s | 0.0276s | 1.1x
012Heavy concurrent  | 0.0164s | 0.0309s | 0.0349s | 0.0352s | 0.0236s | 0.0334s | 1.4x
013Random concurrent | 0.1488s | 0.1509s | 0.1500s | 0.1522s | 0.1496s | 0.1517s | 1.0x
014 
015Average ops/s:
016    ReaderWriterQueue: 23.45 million
017    SPSC queue:        28.10 million
018 
01964-bit, MSVC2010, on AMD C-50 @ 1GHz
020------------------------------------
021                  |        Min        |        Max        |        Avg
022Benchmark         |   RWQ   |  SPSC   |   RWQ   |  SPSC   |   RWQ   |  SPSC   | Mult
023------------------+---------+---------+---------+---------+---------+---------+------
024Raw add           | 0.0022s | 0.0210s | 0.0022s | 0.0211s | 0.0022s | 0.0211s | 9.6x
025Raw remove        | 0.0011s | 0.0022s | 0.0011s | 0.0023s | 0.0011s | 0.0022s | 2.0x
026Raw empty remove  | 0.0039s | 0.0024s | 0.0039s | 0.0024s | 0.0039s | 0.0024s | 0.6x
027Single-threaded   | 0.0060s | 0.0054s | 0.0061s | 0.0054s | 0.0061s | 0.0054s | 0.9x
028Mostly add        | 0.0080s | 0.0259s | 0.0081s | 0.0263s | 0.0080s | 0.0261s | 3.3x
029Mostly remove     | 0.0092s | 0.0109s | 0.0093s | 0.0110s | 0.0093s | 0.0109s | 1.2x
030Heavy concurrent  | 0.0150s | 0.0175s | 0.0181s | 0.0200s | 0.0165s | 0.0190s | 1.2x
031Random concurrent | 0.0367s | 0.0349s | 0.0369s | 0.0352s | 0.0368s | 0.0350s | 1.0x
032 
033Average ops/s:
034    ReaderWriterQueue: 34.90 million
035    SPSC queue:        32.50 million
036 
03732-bit, MSVC2010, on Intel Core 2 Duo T6500 @ 2.1GHz
038----------------------------------------------------
039                  |        Min        |        Max        |        Avg
040Benchmark         |   RWQ   |  SPSC   |   RWQ   |  SPSC   |   RWQ   |  SPSC   | Mult
041------------------+---------+---------+---------+---------+---------+---------+------
042Raw add           | 0.0011s | 0.0097s | 0.0011s | 0.0099s | 0.0011s | 0.0098s | 9.2x
043Raw remove        | 0.0005s | 0.0006s | 0.0005s | 0.0006s | 0.0005s | 0.0006s | 1.1x
044Raw empty remove  | 0.0018s | 0.0011s | 0.0019s | 0.0011s | 0.0018s | 0.0011s | 0.6x
045Single-threaded   | 0.0047s | 0.0040s | 0.0047s | 0.0040s | 0.0047s | 0.0040s | 0.9x
046Mostly add        | 0.0052s | 0.0114s | 0.0053s | 0.0116s | 0.0053s | 0.0115s | 2.2x
047Mostly remove     | 0.0055s | 0.0067s | 0.0056s | 0.0068s | 0.0055s | 0.0068s | 1.2x
048Heavy concurrent  | 0.0044s | 0.0089s | 0.0075s | 0.0128s | 0.0066s | 0.0107s | 1.6x
049Random concurrent | 0.0294s | 0.0306s | 0.0295s | 0.0312s | 0.0294s | 0.0310s | 1.1x
050 
051Average ops/s:
052    ReaderWriterQueue: 71.18 million
053    SPSC queue:        61.02 million
054 
05564-bit, MSVC2010, on Intel Core 2 Duo T6500 @ 2.1GHz
056----------------------------------------------------
057                  |        Min        |        Max        |        Avg
058Benchmark         |   RWQ   |  SPSC   |   RWQ   |  SPSC   |   RWQ   |  SPSC   | Mult
059------------------+---------+---------+---------+---------+---------+---------+------
060Raw add           | 0.0007s | 0.0097s | 0.0007s | 0.0100s | 0.0007s | 0.0099s | 13.6x
061Raw remove        | 0.0004s | 0.0015s | 0.0004s | 0.0020s | 0.0004s | 0.0018s | 4.6x
062Raw empty remove  | 0.0014s | 0.0010s | 0.0014s | 0.0010s | 0.0014s | 0.0010s | 0.7x
063Single-threaded   | 0.0024s | 0.0022s | 0.0024s | 0.0022s | 0.0024s | 0.0022s | 0.9x
064Mostly add        | 0.0031s | 0.0112s | 0.0031s | 0.0115s | 0.0031s | 0.0114s | 3.7x
065Mostly remove     | 0.0033s | 0.0041s | 0.0033s | 0.0041s | 0.0033s | 0.0041s | 1.2x
066Heavy concurrent  | 0.0042s | 0.0035s | 0.0067s | 0.0039s | 0.0054s | 0.0038s | 0.7x
067Random concurrent | 0.0142s | 0.0141s | 0.0145s | 0.0144s | 0.0143s | 0.0142s | 1.0x
068 
069Average ops/s:
070    ReaderWriterQueue: 101.21 million
071    SPSC queue:        71.42 million
072 
07332-bit, Intel ICC 13, on Intel Core 2 Duo T6500 @ 2.1GHz
074--------------------------------------------------------
075                  |        Min        |        Max        |        Avg
076Benchmark         |   RWQ   |  SPSC   |   RWQ   |  SPSC   |   RWQ   |  SPSC   | Mult
077------------------+---------+---------+---------+---------+---------+---------+------
078Raw add           | 0.0014s | 0.0095s | 0.0014s | 0.0097s | 0.0014s | 0.0096s | 6.8x
079Raw remove        | 0.0007s | 0.0006s | 0.0007s | 0.0007s | 0.0007s | 0.0006s | 0.9x
080Raw empty remove  | 0.0028s | 0.0013s | 0.0028s | 0.0018s | 0.0028s | 0.0015s | 0.5x
081Single-threaded   | 0.0039s | 0.0033s | 0.0039s | 0.0033s | 0.0039s | 0.0033s | 0.8x
082Mostly add        | 0.0049s | 0.0113s | 0.0050s | 0.0116s | 0.0050s | 0.0115s | 2.3x
083Mostly remove     | 0.0051s | 0.0061s | 0.0051s | 0.0062s | 0.0051s | 0.0061s | 1.2x
084Heavy concurrent  | 0.0066s | 0.0036s | 0.0084s | 0.0039s | 0.0076s | 0.0038s | 0.5x
085Random concurrent | 0.0291s | 0.0282s | 0.0294s | 0.0287s | 0.0292s | 0.0286s | 1.0x
086 
087Average ops/s:
088    ReaderWriterQueue: 55.65 million
089    SPSC queue:        63.72 million
090 
09164-bit, Intel ICC 13, on Intel Core 2 Duo T6500 @ 2.1GHz
092--------------------------------------------------------
093                  |        Min        |        Max        |        Avg
094Benchmark         |   RWQ   |  SPSC   |   RWQ   |  SPSC   |   RWQ   |  SPSC   | Mult
095------------------+---------+---------+---------+---------+---------+---------+------
096Raw add           | 0.0010s | 0.0099s | 0.0010s | 0.0100s | 0.0010s | 0.0099s | 9.8x
097Raw remove        | 0.0006s | 0.0015s | 0.0006s | 0.0018s | 0.0006s | 0.0017s | 2.7x
098Raw empty remove  | 0.0024s | 0.0016s | 0.0024s | 0.0016s | 0.0024s | 0.0016s | 0.7x
099Single-threaded   | 0.0026s | 0.0023s | 0.0026s | 0.0023s | 0.0026s | 0.0023s | 0.9x
100Mostly add        | 0.0032s | 0.0114s | 0.0032s | 0.0118s | 0.0032s | 0.0116s | 3.6x
101Mostly remove     | 0.0037s | 0.0042s | 0.0037s | 0.0044s | 0.0037s | 0.0044s | 1.2x
102Heavy concurrent  | 0.0060s | 0.0092s | 0.0088s | 0.0096s | 0.0077s | 0.0095s | 1.2x
103Random concurrent | 0.0168s | 0.0166s | 0.0168s | 0.0168s | 0.0168s | 0.0167s | 1.0x
104 
105Average ops/s:
106    ReaderWriterQueue: 68.45 million
107    SPSC queue:        50.75 million
108 
10964-bit, GCC 4.7.2, on Linode 1GB virtual machine (Intel Xeon L5520 @ 2.27GHz)
110-----------------------------------------------------------------------------
111                  |        Min        |        Max        |        Avg
112Benchmark         |   RWQ   |  SPSC   |   RWQ   |  SPSC   |   RWQ   |  SPSC   | Mult
113------------------+---------+---------+---------+---------+---------+---------+------
114Raw add           | 0.0004s | 0.0055s | 0.0005s | 0.0055s | 0.0005s | 0.0055s | 12.1x
115Raw remove        | 0.0004s | 0.0030s | 0.0004s | 0.0030s | 0.0004s | 0.0030s | 8.4x
116Raw empty remove  | 0.0009s | 0.0060s | 0.0010s | 0.0061s | 0.0009s | 0.0060s | 6.4x
117Single-threaded   | 0.0034s | 0.0052s | 0.0034s | 0.0052s | 0.0034s | 0.0052s | 1.5x
118Mostly add        | 0.0042s | 0.0096s | 0.0042s | 0.0106s | 0.0042s | 0.0103s | 2.5x
119Mostly remove     | 0.0042s | 0.0057s | 0.0042s | 0.0058s | 0.0042s | 0.0058s | 1.4x
120Heavy concurrent  | 0.0030s | 0.0164s | 0.0036s | 0.0216s | 0.0032s | 0.0188s | 5.8x
121Random concurrent | 0.0256s | 0.0282s | 0.0257s | 0.0290s | 0.0257s | 0.0287s | 1.1x
122 
123Average ops/s:
124    ReaderWriterQueue: 137.88 million
125    SPSC queue:        24.34 million

简而言之,我实现的队列令人惊奇的快,而且确实还包括任何它自身数据结构所占的开销。

测试代码参见 这里(用最高优化选项编译运行)。

更新 
2013-5-20的更新:我改变了队列内存的预分配基准,并增加了一个针对 Facebook's folly::ProducerConsumerQueue(这个是有点快,但不能根据需要增加)的比较。下面是比较结果:
64-bit, GCC 4.7.2, on Linode 1GB virtual machine (Intel Xeon L5520 @ 2.27GHz)-----------------------------------------------------------------------------                  |-----------  Min ------------|------------ Max ------------|------------ Avg ------------|Benchmark         |   RWQ   |  SPSC   |  Folly  |   RWQ   |  SPSC   |  Folly  |   RWQ   |  SPSC   |  Folly  | xSPSC | xFolly------------------+---------+---------+---------+---------+---------+---------+---------+---------+---------+-------+-------Raw add           | 0.0003s | 0.0018s | 0.0003s | 0.0003s | 0.0018s | 0.0003s | 0.0003s | 0.0018s | 0.0003s | 5.52x | 0.96xRaw remove        | 0.0003s | 0.0030s | 0.0004s | 0.0003s | 0.0030s | 0.0004s | 0.0003s | 0.0030s | 0.0004s | 8.58x | 1.28xRaw empty remove  | 0.0009s | 0.0061s | 0.0006s | 0.0010s | 0.0061s | 0.0006s | 0.0010s | 0.0061s | 0.0006s | 6.40x | 0.61xSingle-threaded   | 0.0034s | 0.0053s | 0.0033s | 0.0034s | 0.0053s | 0.0033s | 0.0034s | 0.0053s | 0.0033s | 1.54x | 0.97xMostly add        | 0.0042s | 0.0046s | 0.0042s | 0.0043s | 0.0046s | 0.0042s | 0.0042s | 0.0046s | 0.0042s | 1.09x | 0.99xMostly remove     | 0.0042s | 0.0049s | 0.0043s | 0.0042s | 0.0051s | 0.0043s | 0.0042s | 0.0050s | 0.0043s | 1.20x | 1.02xHeavy concurrent  | 0.0025s | 0.0100s | 0.0024s | 0.0028s | 0.0101s | 0.0026s | 0.0026s | 0.0100s | 0.0025s | 3.88x | 0.97xRandom concurrent | 0.0265s | 0.0268s | 0.0262s | 0.0273s | 0.0287s | 0.0284s | 0.0269s | 0.0280s | 0.0271s | 1.04x | 1.01xAverage ops/s:    ReaderWriterQueue: 142.60 million    SPSC queue:        33.86 million    Folly queue:       181.16 million

无锁编程的未来

我认为多线程代码会变得更加普遍,越来越多的库将会利用无锁编程的速度优势(同时从应用程序开发者身上将暴力的、倾向出错的勇气转移出去)。不过,我想知道,到底多核心共享内存架构会走多远——对高度并行的CPU来说,关于高速缓存一致性协议和记忆障碍的说明似乎并不是成正比的好(性能明智)。可被本地线程写的不可变共享内存在未来是可能的。听起来像是函数式编程的工作!

0 0
原创粉丝点击