使用多线程加速(std::async、std::thread 还是?)
Posted
技术标签:
【中文标题】使用多线程加速(std::async、std::thread 还是?)【英文标题】:Speed up using multithreading (std::async, std::thread, or?) 【发布时间】:2015-08-09 19:36:13 【问题描述】:(g++ 4.6.3,cygwin,Windows 10)
我不确定是否有某种方法可以使用多线程机制来加速以下程序(我对此很陌生):
// ab.h
class A
// Member variables
...
// Member functions
A();
~A();
int foo_1();
void foo_2(std::vector<int>);
...
class B
...
void schedule(std::vector<A>& va);
...
// b.cc
...
void B::schedule(std::vector<A>& va)
std::vector<int> vc;
vc.resize(va.size());
for (... /* i from 0 to vc.size() */)
...
vc[i] = va[i].foo_1();
...
for (... /* i from 0 to va.size() */)
...
va[i].foo_2(vc);
...
// 5 more pairs of "for" loops like the above block
...
// main.cc
int main()
...
std::vector<A> va;
// va.size() can be some large like 1000000
...
B b;
int simTime = 1000000000; // some large number of iterations
for (int clock = 0; clock != simTime; ++clock)
b.schedule(va);
...
return 0;
所以基本上,我有一堆A
类型的对象,它们随着clock
的增长而“前进”并同时相互通信。我的担忧是:
-
我刚刚开始使用
std::async
和std::get()
重写我的每个for
循环对。这有效率吗?我从某处听说std::async
最适合涉及长时间处理(如 I/O)的函数,因为构造/破坏线程的开销不可忽略(?)。但是,我的 foo_1
和 foo_2
函数并没有那么“大”。
如果构造/销毁线程的成本很高,那么最好创建一堆仅在开始时才需要的线程。但就我而言,这将是多个“对象线程”(我想这是不可能的)而不是“成员函数线程”(?)。是否可以只创建一个线程来服务一个对象,但以后只“附加”其不同的成员函数而没有构造/破坏开销?如果有,怎么做?
我的代码运行时间很长(即使经过我自己的一些优化),而有一个强大的 8 核服务器...
【问题讨论】:
只看上面,你似乎有点不必要地复制了一个向量vc
va.size()
次。您的问题可能适用于线程池(手写或类似 microsofts ppl)。有关编写线程池的不完整指南,请参阅 this question。
这不是一个有效的问题。您说,有些事情是错误的,“因为执行需要很长时间”,但您不知道确切问题是什么(或者即使有问题根本我>)。你也想,“使用你不熟悉的多线程机制”可能会解决这个问题。我们应该做什么?调查你糟糕的设计并从头开始编写新的 API?来吧。分析您的代码并找到真正的问题,然后返回。这不是调试/分析服务。
使用多线程并不一定意味着提高性能。特别是如果实际上不超过一个 CPU。
@Mateusz Grzejek 是的,这不是调试/分析服务(是什么让您认为它是?)。我只是想就使用多线程加速我的程序的可能性寻求一些建议,不管我自己的错误,当然,这些错误由我自己决定。我不会听取您的建议,对其进行测试,如果可能由于我自己的错误而无法正常工作,我不会再责怪您。对于在给定代码上使用多线程的任何帮助,我将不胜感激。
【参考方案1】:
在您的 CPU 上拥有 8 个内核并且只使用一个内核似乎是一种资源浪费。所以你的问题是完全有道理的。由于您仅提供有关性能问题的少量信息,因此我只能为您提供一些一般性想法。
多线程不一定是所有性能问题的最佳解决方案
如果您创建线程,则需要同步访问共享信息以避免数据竞争。如果需要许多这样的同步,则存在线程之间发生争用的风险(即浪费时间等待其他线程准备好资源)。这很容易让您失去多线程的好处。
在您的特定情况下,两个循环都访问向量va
的相同元素。不幸的是,foo_1()
和 foo2()
都没有声明为 const,这意味着它们可以修改向量的元素。所以你必须仔细检查这一点。
多线程提高吞吐量,而不是执行时间
如果您使用全部 8 个内核,您会注意到每个内核都会比执行相同的非线程代码慢一点。幸运的是,如果每个线程相互独立并且没有争用,整体吞吐量会更优(直白地说:如果2个线程执行80%的非线程,那么两者加起来仍然是160%的非线程) .
注意:如果您创建的线程数超出了 CPU 的处理能力,那么额外的线程将不得不等待,并且会产生额外的线程管理开销。 thread::hardware_concurrency()
提供了一个有用的指导(请记住,您的应用程序并不是唯一可能创建线程的应用程序)。
武器的选择:线程池 vs. std::async
如果您在开始时创建一个线程池,好处是您有许多线程准备触发。这可以避免在时间紧迫的情况下创建线程开销。请记住,只有在硬件支持的线程范围内(除非您有很多等待或 IO 延迟),这种好处才是真正的。
另一方面,有了std::async
,您就掌握了C++ 实现。例如,MSVC 上的some experimentation 表明,微软的 async 显然确实重用了它创建的线程,以避免创建开销。这种方法减少了太多的中间线程,并且在某些情况下优于线程池。
结论:
由于性能取决于您的算法(主要)、CPU 线程、操作系统、编译器+标准库,我强烈建议您进行一些基准测试/分析以选择最佳方法。
【讨论】:
已经有简单的 C++11 线程池的实现,比如this。 @Youka 非常其实很简单。除非在简单的自制项目中,否则我不鼓励使用此代码。线程池不仅仅是一个包含一堆线程和一个方法的对象 - 它是更复杂的东西。线程间通信呢?死锁检测?任务中断?链接线程池甚至不是简单。以上是关于使用多线程加速(std::async、std::thread 还是?)的主要内容,如果未能解决你的问题,请参考以下文章