使用Boost.Lockfree队列比使用互斥体慢

到目前为止,我在项目中使用的是boost::lockfree::queue。 我测量了此队列上特定操作所需的平均时间。

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

然后,我将boost::lockfree::queue“升级”到bool is_lock_free(void) const;,这样我就可以摆脱保护队列的互斥锁了。 在我的本地VM上,我可以看到巨大的性能提升,现在平均为200微秒。 但是,在远程计算机上,平均时间达到800微秒,比以前慢。

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

在Boost.Lockfree页面上:

并非所有硬件都支持同一组原子指令。 如果它在硬件中不可用,则可以使用防护在软件中对其进行仿真。 然而,这具有丢失无锁属性的明显缺点。

要了解是否支持这些说明,boost::lockfree::queue具有称为bool is_lock_free(void) const;的方法。但是,boost::lockfree::spsc_queue没有这样的功能,对我而言,这意味着它不依赖硬件,并且在任何计算机上始终是无锁的。

性能下降的原因可能是什么?


示例代码(生产者/消费者)

// 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;
}
thesys asked 2020-08-11T20:00:49Z
2个解决方案
104 votes

与基于锁的算法相比,无锁算法通常性能较差。 这是关键的原因,它们的使用频率不高。

无锁算法的问题在于,它们通过允许竞争线程继续竞争来最大化争用。 锁通过调度竞争线程来避免争用。 仅在无法对竞争线程进行调度的情况下,才应使用无锁算法,这是一个近似的方法。 这很少适用于应用程序级代码。

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

首先,让我们想象一下集合使用锁。 这意味着如果线程A1和A2(或B1和B2)尝试同时运行,则其中一个将被锁阻塞。 因此,很快就会有一个A线程和一个B线程在运行。 这些线程将非常快速地运行,并且不会竞争。 每当线程尝试竞争时,冲突的线程都会被调度。 好极了。

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

再次,这被高度夸大了。 但是你明白了。 您要避免争用,而不要经历太多争执。

但是,现在再次运行此思想实验,其中A1和A2是整个系统上的唯一线程。 现在,无锁集合可能更好(尽管您可能会发现在这种情况下仅拥有一个线程会更好!)。

几乎每个程序员都经历了一个阶段,他们认为锁是不好的,避免锁会使代码运行得更快。 最终,他们意识到争用会导致事情变慢并锁定(正确使用)可以最大程度地减少争用。

David Schwartz answered 2020-08-11T20:01:28Z
0 votes

我不能说在所有可能的情况下,boost无锁队列都比较慢。 以我的经验,push(const T&item)试图制作副本。 如果要构造tmp对象并将其推入队列,那么性能会受到打击。 我认为库只需要重载版本push(T && item)即可使可移动对象更高效。 在添加新功能之前,您可能必须使用C ++ 11之后提供的指针,纯类型或智能指针。 这是队列的一个相当有限的方面,而我只使用无锁队列很少变化。

Kemin Zhou answered 2020-08-11T20:01:49Z
translate from https://stackoverflow.com:/questions/43540943/using-boost-lockfree-queue-is-slower-than-using-mutexes