2017-04-21 48 views
23

直到现在我在我的项目中使用std::queue。我测量了此队列上特定操作所需的平均时间。使用Boost.Lockfree队列比使用互斥体要慢

时间是在2台机器上测得的:我的本地Ubuntu VM和远程服务器。 使用std::queue,两台机器的平均值几乎相同:〜750微秒。

然后,我将std::queue“升级”为boost::lockfree::spsc_queue,这样我就可以摆脱保护队列的互斥锁。在我的本地虚拟机上,我可以看到巨大的性能增益,平均值现在在200微秒。然而,在远程机器上,平均达到800微秒,比以前要慢。

首先我想这可能是因为远程计算机可能不支持无锁的实现:

Boost.Lockfree page:

并非所有的硬件支持一组相同的原子指令。如果硬件不可用,则可以使用警卫软件进行仿真。然而,这具有失去无锁物业的明显缺点。

要了解这些指令是否受支持,boost::lockfree::queue有一个名为bool is_lock_free(void) const;的方法。 但是,boost::lockfree::spsc_queue没有这样的功能,对我来说,这意味着它不依赖于硬件,并且始终是无锁的 - 在任何机器上。

什么可能是性能损失的原因?


Exmple代码(生产者/消费者)

// c++11 compiler and boost library required 

#include <iostream> 
#include <cstdlib> 
#include <chrono> 
#include <async> 
#include <thread> 
/* Using blocking queue: 
* #include <mutex> 
* #include <queue> 
*/ 
#include <boost/lockfree/spsc_queue.hpp> 


boost::lockfree::spsc_queue<int, boost::lockfree::capacity<1024>> queue; 

/* Using blocking queue: 
* std::queue<int> queue; 
* std::mutex mutex; 
*/ 

int main() 
{ 
    auto producer = std::async(std::launch::async, [queue /*,mutex*/]() 
    { 
     // Producing data in a random interval 
     while(true) 
     { 
      /* Using the blocking queue, the mutex must be locked here. 
      * mutex.lock(); 
      */ 

      // Push random int (0-9999) 
      queue.push(std::rand() % 10000); 

      /* Using the blocking queue, the mutex must be unlocked here. 
      * mutex.unlock(); 
      */ 

      // Sleep for random duration (0-999 microseconds) 
      std::this_thread::sleep_for(std::chrono::microseconds(rand() % 1000)); 
     } 
    } 

    auto consumer = std::async(std::launch::async, [queue /*,mutex*/]() 
    { 
     // Example operation on the queue. 
     // Checks if 1234 was generated by the producer, returns if found. 

     while(true) 
     { 
      /* Using the blocking queue, the mutex must be locked here. 
      * mutex.lock(); 
      */ 

      int value; 
      while(queue.pop(value) 
      { 
       if(value == 1234) 
        return; 
      } 

      /* Using the blocking queue, the mutex must be unlocked here. 
      * mutex.unlock(); 
      */ 

      // Sleep for 100 microseconds 
      std::this_thread::sleep_for(std::chrono::microseconds(100)); 
     } 
    } 

    consumer.get(); 
    std::cout << "1234 was generated!" << std::endl; 
    return 0; 
} 
+1

请考虑增加一个[MCVE],允许您重现性能测量。这将允许一个更实际的答案。 – Zulan

+0

鉴于对这个问题的高度兴趣,真正令人遗憾的是,两种不同系统的性能差异的核心方面是无法回答的。如果问题得到改善,我认为对于具体的实际答案来说,有更多的可能性。 – Zulan

+0

@Zulan我会尽快添加一个具体的例子。 – Bobface

回答

52

锁定自由算法一般比基于锁的算法执行更差。这是他们不常使用的关键原因。

无锁算法的问题在于它们通过允许竞争线程继续竞争来最大化争用。通过解除竞争线程的解锁,避免争用。锁定免费算法,第一个近似值,只有在无法解除竞争线程时才能使用。这很少适用于应用程序级代码。

让我给你一个非常极端的假设。想象一下,典型的现代双核CPU上有四个线程正在运行。线程A1和A2正在操作集合A.线程B1和B2正在操作集合B.

首先,让我们想象集合使用锁。这将意味着如果线程A1和A2(或B1和B2)试图同时运行,其中一个将被该锁阻止。所以,很快,一个A线程和一个B线程将会运行。这些线程运行速度非常快,不会发生竞争。任何时候线程都会争用,冲突的线程将被解除调度。好极了。

现在,设想集合不使用锁。现在,线程A1和A2可以同时运行。这将引起持续的争论。这两个核心之间的高速缓存行将会乒乓。核心间总线可能会饱和。性能会很糟糕。

再次,这是高度夸张​​。但你明白了。您希望避免争用,尽可能避免争论。

不过,现在那里A1和A2是整个系统中唯一的线程再次运行这个思想实验。现在,无锁定集合可能会更好(尽管您可能会发现在这种情况下最好只有一个线程!)。

几乎每个程序员都经历一个阶段,他们认为门锁是坏的,避免锁使代码更快。最终,他们意识到这是这使事情慢和锁,正确使用,最大限度地减少争用。

+7

非常好,规范的答案。当时间窗口过去后,我会给予奖励。 – sehe

+1

嗯,我认为我目前处于同一阶段,我倾向于避免锁:p,是否有任何其他资源/书我可以阅读这些主题被触动? –

+0

@AbhinavGauniyal我被问到很多,我还没有找到。我曾经和许多经历过我多年前同样痛苦过程的人说过话。 –