4

我想使用多线程来加速我的程序,但不确定哪种方式是最佳的。

假设我们有 10000 个小任务,完成其中一个可能只需要 0.1 秒。现在我有一个 12 核的 CPU,我想用 12 个线程让它更快。

据我所知,有两种方法:

1.任务池

总是有 12 个线程在运行,每个线程在完成当前工作后从任务池中获取一个新任务。

2.单独的任务

通过将 10000 个任务分成 12 个部分,每个线程在一个部分上工作。

问题是,如果我使用任务池,当多个线程尝试访问任务池时,锁定/解锁是浪费时间。但是第二种方式并不理想,因为一些线程提前完成,总时间取决于最慢的线程。

我想知道你是如何处理这种工作的,还有其他最好的方法吗?谢谢你。

编辑:请注意,数字 10000 只是举例,在实践中,它可能是 1e8 或更多任务,每个任务 0.1 也是平均时间。

EDIT2:感谢您的所有回答 :] 很高兴知道各种选项。

4

5 回答 5

5

因此,这两种方法之间的一种中间方式是分成 100 批,每批 100 个任务,让核心一次从任务池中挑选一批 100 个任务。

也许如果您对单个任务的单个内核中的执行时间的随机性进行建模,并获得互斥锁时间的估计值,您可能能够找到最佳批量大小。

但是如果没有太多的工作,我们至少有以下引理:

最慢的线程最多只能比其他线程多 100*.1 = 10 秒。

于 2012-04-03T22:38:53.013 回答
3

任务池始终是这里的最佳解决方案。这不仅是最佳时间,也是代码的可理解性。您永远不应强迫您的任务符合完全不相关的标准,即具有与核心相同数量的子任务 - 您的任务与此无关(通常),并且当您更换机器等时,这种分离不会扩展. 为最终任务协作合并子任务中的结果需要开销,并且通常会使简单的任务变得困难。

但是你不应该担心任务池锁的使用。 如果您确定有必要,可以使用无锁队列。但首先确定。如果您关心时间,请使用适当的方法来加快您的任务,并将您的努力放在您将获得最大收益的地方。分析您的代码。为什么你的任务需要 0.1 秒?他们是否使用了低效的算法?循环展开有帮助吗?如果您通过 profiling 找到代码中的热点,您可能会发现锁是您最不必担心的问题。如果您发现一切都在尽可能快地运行,并且您希望额外的一秒钟来移除锁,请使用您最喜欢的搜索引擎在互联网上搜索“lockfree queue”和“waitfree queue”。比较和交换使原子列表变得容易。

于 2012-04-04T16:01:46.767 回答
2

问题中建议的两种方式都将表现良好并且彼此相似(在简单的情况下,任务持续时间可预测且相对较长)。如果目标系统类型已知且可用(并且如果性能确实是最重要的问题),则应根据原型设计和测量来选择方法。

对于与内核数量匹配的最佳线程数,不一定要对自己产生偏见。如果这是一个普通的服务器或桌面系统,会有各种系统进程在这里启动,你可能会看到你的 12 个线程在处理器之间浮动,这会损害内存缓存。

您还应该检查一些关键的非衡量因素:这些小任务是否需要任何资源来执行?这些资源是否会带来额外的潜在延迟(阻塞)或竞争?是否有其他应用程序争夺 CPU 能力?是否需要扩展应用程序以适应不同的执行环境、任务类型或用户交互模型?

如果所有问题的答案是否定的,这里有一些您可以衡量和考虑的其他方法。

  • 仅使用 10 或 11 个线程。您会观察到小幅减速,甚至小幅加速(额外的内核将为 OS 进程提供服务,因此与 12 个线程相比,其余的线程关联将变得更加稳定)。系统上的任何并发交互活动都将大大提高响应能力。

  • 准确创建 12 个线程,但为每个线程显式设置不同的处理器关联掩码,以在线程和处理器之间施加 1-1 映射。这在最简单的接近学术的情况下是很好的,除了 CPU 和共享内存之外没有其他资源。您不会看到跨进程的线程长期迁移。缺点是算法与特定机器紧密耦合;在另一台机器上,它可能表现得非常糟糕,以至于根本无法完成(因为一个不相关的实时任务永远阻塞了你的一个线程)。

  • 创建 12 个线程,平均分配任务。让每个线程在超过 40% 和超过 80% 负载时再次降级自己的优先级。这将改善您的进程内部的负载平衡,但如果您的应用程序与其他受 CPU 限制的进程竞争,它将表现不佳。

于 2012-04-04T05:28:38.303 回答
1

每个工作线程可能有自己的小任务队列,容量不超过一两个内存页。当队列大小变小(容量的一半)时,它应该向某个管理器线程发送一个信号以填充更多任务。如果队列是按批次组织的,那么只要当前批次不为空,工作线程就不需要进入临界区。避免关键部分将为您提供额外的实际工作周期。每个队列两个批次就足够了,在这种情况下,一个批次可以占用一个内存页,因此队列占用两个。

内存页的要点是线程不必跳过整个内存来获取数据。如果所有数据都在一个位置(一个内存页),则可以避免缓存未命中。

于 2012-04-04T11:41:41.083 回答
1

100 毫秒/任务 - 按原样堆积它们 - 池开销将是微不足道的。

哦..

1E8 个任务 @ 0.1 秒/任务 = 10,000,000 秒 = 2777.7r 小时 = 115.7 天

这远远超过补丁星期二重新启动之间的间隔。

即使您在 Linux 上运行它,您也应该批量输出并将其刷新到磁盘,以使作业可以重新启动。

是否涉及数据库?如果是这样,你应该告诉我们!

于 2012-04-03T22:49:25.337 回答