【发布时间】:2019-01-01 06:58:34
【问题描述】:
我有一个从主线程推送到它的“作业”(函数指针和数据)队列,然后通知工作线程弹出数据并运行它。
这些功能非常基本,如下所示:
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;
};
但是我有一个大问题,push() 真的很慢。工作线程的速度超过了主线程,在我的测试中添加作业是主线程所做的所有事情。 (工作线程执行 20 个 4x4 矩阵旋转,这些旋转相互馈送并在最后打印,因此它们没有被优化掉)随着可用工作线程的数量,这似乎变得更糟。如果每个“作业”更大,比如 100 个矩阵运算,这个负数就会消失,线程数更多 == 更好,但我在实践中给它的作业要小得多。
最热门的调用是互斥锁和notify_one(),它们各自占用了 40% 的时间,其他一切似乎都可以忽略不计。此外,互斥锁很少等待,它几乎总是可用的。
我不确定我应该在这里做什么,是否有明显或不那么明显的优化可以帮助,或者我犯了一个错误?任何见解将不胜感激。
(如果可能有帮助,我会采取一些指标,它们不计算创建线程所需的时间,即使对于数十亿个作业,模式也是相同的)
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
【问题讨论】:
-
批量作业。与其一次添加一份工作,不如一次添加一大堆。使用每个工作者的作业队列,并让生成每个作业的主线程将其添加到每个工作者的作业队列中。还有许多其他可能的变化,这完全取决于个人情况。
-
条件变量在存在争用时具有显着性。当你抓住
pop()中的锁时,不要wait如果ready为真 -
@Chad - 哦,我以为它在尝试等待之前检查了谓词。我尝试围绕它添加另一个检查,但不幸的是没有任何改进。
-
@SamVarshavchik - 我将尝试添加每个工作人员的作业队列。我最初避免使用它,因为它使加入变得更加困难,但在这种情况下它可能是值得的。批处理也
-
1) 有什么理由必须扩展到 96 个线程?为什么不使用具有与可用内核相同数量的线程的线程池? 2)您希望工作需要多少毫秒?如果作业很短,最好进入无锁队列,而不是使用重量级的互斥锁/cv 同步。
标签: c++ multithreading optimization