C++ 如何提高将数据推送到互斥锁队列的性能

C++ 如何提高将数据推送到互斥锁队列的性能,c++,multithreading,optimization,C++,Multithreading,Optimization,我有一个“作业”队列(函数指针和数据)从主线程推送到它上面,然后通知工作线程弹出数据并运行它 这些函数非常基本,如下所示: class JobQueue { public: // usually called by main thread but other threads can use this too void push(Job job) { { std::lock_guard<std::mutex> lock(mute

我有一个“作业”队列(函数指针和数据)从主线程推送到它上面,然后通知工作线程弹出数据并运行它

这些函数非常基本,如下所示:

class JobQueue {
public: 
    // usually called by main thread but other threads can use this too
    void push(Job job) {
        {
            std::lock_guard<std::mutex> lock(mutex);   // this takes 40% of the thread's time (when NOT sync'ing)
            ready = true;
            queue.emplace_back(job);
        }
        cv.notify_one();   // this also takes another 40% of the thread's time
    }

    // only called by worker threads
    Job pop() {
        std::unique_lock<std::mutex> lock(mutex);
        cv.wait(lock, [&]{return ready;});
        Job job = list.front();
        list.pop_front();
        return job;
    }

private:
    std::list<Job>            queue;
    std::mutex                mutex;
    std::condition_variable   cv;
    bool                      ready;
};


(所有工作线程都有相同的模式,绿色表示执行,红色表示等待同步)

我建议使用事件处理程序

事件有两种类型:

  • 新工作来了
  • 工人完成工作
主线程维护一个仅由主线程访问的作业队列(因此没有互斥锁)

当作业到达时,它被放置在作业队列中。 当工作人员完成作业时,会弹出一个作业并将其传递给该工作人员

在启动时和没有作业可用时,您还需要一个空闲工作队列


您还需要一个事件处理程序。这些都是很棘手的,所以最好使用经过良好测试的库,而不是使用自己的库。我使用boost::asio

TL;DR:在每项任务中做更多的工作。(每次可能会从队列中删除多个当前任务,但还有许多其他可能性。)

你的任务(计算)太小了。4x4矩阵乘法只是几个倍数和加法~60-70次行动。其中20个一起完成并不贵,大约1500个(流水线)算术运算。线程切换的成本(包括唤醒一个等待cv的线程,然后是实际的上下文切换)可能比这要高——可能要高得多

此外,同步的成本(互斥和cv的操作)非常昂贵,特别是在争用的情况下,尤其是在硬件本机同步操作比算术操作昂贵得多的多核系统上(因为多核之间的缓存一致性强制)

这就是为什么你观察到,当每个任务执行100个矩阵运算时,问题会减少,从20个矩阵运算增加到100个矩阵运算:工人们太频繁地回到井里做更多的事情,导致了争用,而他们只有20个MMs要做。。。给他们100个任务可以让他们放慢速度,从而减少争用


(在一条评论中,您指出只有一个供应商,这基本上消除了作为队列争用源的供应商。但即使在那里,在cv锁下,可以排队的任务数量越多越好-达到阻止工人执行任务的限制。)

批量处理作业。与其一次增加一份工作,不如一次增加一整批工作。使用每工作者作业队列,并让生成每个作业的主线程将其添加到每工作者作业队列。还有许多其他可能的变化,这一切都取决于个人情况。当存在争用时,条件变量是多余的。如果在
pop()
@Chad()中抓取锁时ready为true,则不要
wait
-哦,我以为它在尝试等待之前检查了谓词。我尝试在其周围添加另一个检查,但遗憾的是没有任何改进。@SamVarshavchik-我将尝试添加每个工作人员的作业队列。我最初避免了它,因为它使加入变得更加困难,但在这种情况下,它可能是值得的。批处理too1)您有什么理由必须扩展到96个线程吗?为什么不使用一个线程池,它的线程数与可用的内核数相同?2) 一个作业需要多少毫秒?如果作业非常短,那么最好进入无锁队列,而不是使用重载互斥体/cv同步。
Time to calc 2000000 matrice rotations
(20 rotations x 100000 jobs)
threads   0:       149 ms  << no-bool baseline
threads   1:       151 ms  << single threaded w/pool
threads   2:        89 ms
threads   3:       120 ms
threads   4:       216 ms
threads   8:       269 ms
threads  12:       311 ms  << hardware hint
threads  16:       329 ms
threads  24:       332 ms
threads  96:       336 ms