我想使用多线程来加速我的程序,但不确定哪种方式是最佳的。
假设我们有10000个小任务,完成其中一个任务可能只需0.1秒。现在我有一个12核的CPU,我想使用12个线程来加快速度。
据我所知,有两种方法:
1.Tasks Pool
总有12个线程在运行,每个线程在完成当前工作后从任务池中获得一个新任务。
2.独立任务
通过将10000个任务分成12个部分,每个线程在一个部分上工作。
问题是,如果我使用任务池,当多个线程尝试访问任务池时,锁定/解锁是浪费时间。但第二种方式并不理想,因为有些线程提前完成,总时间取决于最慢的线程。
我想知道你是如何处理这种工作以及其他任何最好的方法吗?谢谢。
编辑:请注意,数字10000仅作为示例,实际上,它可能是1e8或更多任务,每个任务0.1也是平均时间。
EDIT2:感谢您的所有答案:]了解各种选项很好。
答案 0 :(得分:4)
因此,两种方法之间的中途是分别说100个批次的100个任务,并让核心从任务池中一次挑选100个任务。
也许如果您为单个任务在单个核心中执行时间的随机性建模,并获得互斥锁定时间的估计,您可能能够找到最佳批量大小。
但是如果没有太多工作,我们至少会遇到以下问题:
最慢的线程最多只能占用100 * .1 = 10s。
答案 1 :(得分:2)
问题中建议的两种方式都表现良好且彼此相似(在具有可预测且相对长的任务持续时间的简单情况下)。如果目标系统类型已知且可用(并且性能确实是最受关注的),则应根据原型设计和测量来选择方法。
对于与核心数匹配的最佳线程数,不一定会损害自己。如果这是一个普通的服务器或桌面系统,那么将会有各种各样的系统进程,你可能会看到你的12个线程在处理器之间不同地浮动,这会损害内存缓存。
您还应该检查一些关键的非衡量因素:这些小任务是否需要执行任何资源?这些资源是否会造成额外的潜在延迟(阻塞)或竞争?是否有其他应用程序争夺CPU能力?应用程序是否需要增长以适应不同的执行环境,任务类型或用户交互模型?
如果对所有人的回答都是否定的,那么您可以测量和考虑一些其他方法。
仅使用10或11个线程。你会观察到一个小的减速,甚至是 一个小的加速(额外的核心将服务于OS进程,所以 与12相比,其余的线程亲和力将变得更加稳定 线程)。系统上的任何并发交互活动都会看到 大提升响应能力。
正好创建12个线程,但显式设置了不同的处理器 每个关联掩码,在线程和处理器之间施加1-1映射。 这在最简单的近乎大学的情况下是好的 除了CPU和共享内存之外没有其他资源 参与;你将看不到线程的慢性迁移 流程。缺点是 算法与特定机器紧密耦合;在另一台机器上 它可能表现得如此糟糕,从根本上完成(因为一个 无关的实时任务 永远阻止你的一个主题。)
创建12个线程并均匀分割任务。有每个线程 一旦超过40%,就会降低自己的优先级 超过80%的负荷。这将改善您的内部负载平衡 过程,但如果您的应用程序竞争,它将表现不佳 与其他CPU绑定流程。
答案 2 :(得分:2)
任务池始终是最佳解决方案。这不仅仅是最佳时间,也是代码的可理解性。您永远不应强迫您的任务符合与核心具有相同数量的子任务的完全无关的标准 - 您的任务与此无关(通常),并且当您更换机器等时这种分离不会扩展它需要开销来协作将结果组合成最终任务的子任务,并且通常只是简单地完成一项简单的任务。
但是你不应该担心使用任务池的锁。 如果您确定有必要,可以使用无锁队列。但首先确定。如果您关心时间,请使用适当的方法来加速您的任务,并将您的努力付诸实施,从而获得最大的收益。描述您的代码。为什么你的任务需要0.1秒?他们使用效率低下的算法吗?可以循环展开帮助吗?如果您通过性能分析找到代码中的热点,您可能会发现锁是最不用担心的。如果您发现所有内容都尽可能快地运行,并且您希望通过删除锁定获得额外的秒数,请使用您最喜欢的搜索引擎搜索“lockfree queue”和“waitfree queue”。比较和交换使原子列表变得容易。
答案 3 :(得分:1)
100毫秒/任务 - 按原样堆放它们 - 池开销将是无关紧要的。
OTOH ..
1E8任务@ 0.1s / task = 10,000,000秒 = 2777.7小时 = 115.7天
这远远超过补丁周二重启的时间间隔。
即使您在Linux上运行此操作,也应该批量输出并将其刷新到磁盘,使作业可以重新启动。
是否涉及数据库?如果是这样,你应该告诉我们!
答案 4 :(得分:1)
每个工作线程可能有自己的小任务队列,其容量不超过一个或两个内存页面。当队列大小变小(容量的一半)时,它应该向某个管理器线程发送信号,以便用更多任务填充它。如果队列是按批次组织的,那么只要当前批次不为空,工作线程就不需要输入关键部分。避免关键部分将为您提供额外的实际工作周期。每个队列两个批次就足够了,在这种情况下,一个批处理可以占用一个内存页面,因此队列需要两个。
内存页面的重点是线程不必跳过整个内存来获取数据。如果所有数据都在一个地方(一个内存页面),则可以避免缓存未命中。