3

这是我对英特尔 TBB 流图性能进行基准测试的尝试。这是设置:

  • 一个广播节点发送continue_msgN后继节点
    ( a broadcast_node<continue_msg>)
  • 每个后继节点执行需要t几秒钟的计算。
  • 串行执行时的总计算时间为Tserial = N * t
  • 如果使用所有内核,理想的计算时间是Tpar(ideal) = N * t / C,其中C是内核数。
  • 加速比定义为Tpar(actual) / Tserial
  • 我在 16 核 PC 上使用 gcc5 测试了代码。

以下是显示加速作为单个任务(即主体)处理时间的函数的结果:

t = 100 microsecond  ,   speed-up =  14
t  = 10 microsecond  ,   speed-up =   7
t  =  1 microsecond  ,   speed-up =   1

与轻量级任务(其计算时间不到 1 微秒)一样,并行代码实际上比串行代码慢。

以下是我的问题:

1 ) 这些结果是否符合英特尔 TBB 基准?
2 ) 当有数千个任务每个花费不到 1 微秒的时间时,是否有比流程图更好的范例?

4

3 回答 3

4

并行的开销

你的成本模型是错误的。

理想的并行计算时间为:

Tpar(ideal) = N * t / C + Pstart + Pend

哪里Pstart是开始你的并行需要多长时间,Pend是完成它所用的时间。Pstart几十毫秒的数量级并不罕见。

我不确定您是否熟悉 OpenMP(尽管了解它是件好事),但是,就我们的目的而言,它是一种在线程团队之间划分工作的线程模型。下图显示了与线程组大小相关的一些命令的开销:

OpenMP 线程开销

要点是让你的并行性(这parallel for条线)可能很昂贵,并且随着线程数量的增加而增长。结束并行性(barrier线)具有相似的成本。

事实上,如果你看一下 TBB 的教程,第 3.2.2 节(“自动分块”)说:

注意:通常一个循环需要至少一百万个时钟周期才能使 parallel_for 提高其性能。例如,在 2 GHz 处理器上花费至少 500 微秒的循环可能会受益于 parallel_for。

实现更好的速度

加速此类代码的最佳方法是仅在有大量操作时并行执行操作和/或调整执行工作的线程数,以便每个线程都有很多事情要做。在 TBB 中,您可以实现类似的行为,如下所示:

#include <tbb/parallel_for.h>

// . . .
if(work_size>1000)
  tbb::serial::parallel_for( . . . );
else
  tbb::parallel_for( . . . );
// . . . 

您希望调整1000到足够高的数字,以便您开始看到并行性的收益。

您还可以减少线程数,因为这会在一定程度上减少开销:

tbb::task_scheduler_init init(nthread);

TBB 还执行动态负载平衡(参见此处)。如果您希望循环迭代/任务具有广泛的运行时间分布,这很好,但如果预期的运行时间相同,则表示不必要的开销。我不确定 TBB 是否有静态调度,但可能值得研究。

如果人们最终没有对 TBB 做出坚定的承诺,那么在 OpenMP 中,您会执行以下操作:

#pragma omp parallel for if(work_size>1000) num_threads(4) schedule(static)
于 2018-01-03T17:44:44.287 回答
2

广告 1)

这是一个很好的例子,细节很重要。Tpar(ideal) = N * t / C与其说是现实中可能发生的事情,不如说是一个愿望。

英特尔在将他们的硬件技术改造为发布软件工具方面确实做得很好,这可以从他们对自己的处理器微架构魔法的超详细知识中受益。没有其他人可以为 Intel CPU 做得更好,没有其他人可以轻松地移植它,以在其他 CPU 微架构上提供任何类似的性能(所以要小心你的实际 CPU,如果它是云抽象的,则更多)

为什么开销严格的阿姆达尔定律?

为什么?因为这些开销决定的不仅仅是内核数量。

关键是,随着“有用”的有效负载大小越来越小,开销(即使是那些非常小的开销,如在超级优化工具中,如 TBB 是不可能的)——这些开销总是累积到纯 -[SERIAL]问题计算图的一部分。

因此,随着我们的有效载荷越来越小[PARALLEL],它们的 { setup | 每个核心调度实际上确实花费的成本,将在某个时刻变得高于任何“下一个”受益于反比例因子1 / numCOREs,该因子仅适用于网络[PARALLEL]计算路径的线性持续时间,但所有这些附加组件开销加起来和扩展[SERIAL]计算路径的速度比任何增长numCOREs都可以补偿的速度更快,并且加速增长低于 << 1x
量子点


广告 2)

这是在上面的游乐场设置中,一个最小的痛苦游戏

鉴于想要加速 about ~ 4,000 CPU uops ~ <= 1 [us],如果尝试这样做,则不得在所有延迟和附加开销上花费一纳秒,假设最终加速仍然至少 >= 1x

如果我们不相信童话故事,那么寻找的方法是让 FPGA 用于 PoC 原型设计和 ASIC/SoC 用于生产级部署。

如果您的项目经济无法处理所有相关成本,请忘记免费获得任何魔法。它的成本。总是。但如果你的业务领域或研究资金可以应付,这是一个可以去的方向。


奖励:向量化代码可能会在某些 CPU 上崩溃(最好避免这种情况)

在 Quant 建模中,性能就是金钱,所以让我也分享一个最近的已知问题,来自对微有效负载的极其严格的性能调整(在组装中弄脏了手)。如果在您的 Quant 建模工作中进行代码性能优化,希望它可以节省任何不需要的问题:

英特尔超线程损坏勘误表 (SKZ7/SKW144/SKL150/SKX150/SKZ7/KBL095/KBW095) 使用 AH/BH/CH/DH 寄存器的短循环可能会导致不可预测的系统行为。

问题:
在复杂的微架构条件下,少于 64 条指令的短循环使用 AH、BH、CH 或 DH 寄存器以及它们相应的更宽寄存器(例如 AH 的 RAX、EAX 或 AX)可能会导致不可预测的系统行为。只有当同一物理处理器上的两个逻辑处理器都处于活动状态时,才会发生这种情况。

暗示:
由于这个错误,系统可能会遇到不可预知的系统行为。此勘误表可能会影响 ... 来宾操作系统。

参考:
https://caml.inria.fr/mantis/view.php?id=7452 http://metadata.ftp-master.debian.org/changelogs/non-free/i/intel-microcode/unstable_changelog https:// www.intel.com/content/www/us/en/processors/core/desktop-6th-gen-core-family-spec-update.html https://www.intel.com/content/www/us/en /processors/core/7th-gen-core-family-spec-update.html https://www.intel.com/content/www/us/en/processors/xeon/xeon-e3-1200v6-spec-update。 html https://www.intel.com/content/www/us/en/processors/xeon/xeon-e3-1200v5-spec-update.html https://www.intel.com/content/www/us/ zh/products/processors/core/6th-gen-x-series-spec-update.html

于 2018-01-03T17:38:47.153 回答
1

@Richard 有正确的答案(TBB 教程讨论了调度开销摊销的概念),通常我会将此作为评论,但我想补充一件事。

TBB 对任务使用“贪婪调度”。如果存在由先前任务的执行创建的任务(从技术上讲,如果任务返回任务指针),则该任务是线程上运行的下一个任务。这有两个好处:

  1. 上一个任务可能已经加载或生成了下一个任务正在使用的数据,这有助于数据局部性。
  2. 我们跳过选择下一个要运行的任务的过程(无论它是否在本地线程的队列中,都可以从另一个线程中窃取)。这大大减少了调度开销。

tbb::flow_graph使用相同的想法;如果一个节点有一个或多个后继者,则在其执行完成时,选择这些后继者之一作为下一个运行。

这样做的缺点是,如果你想改变这种行为(以“广度优先”而不是“深度优先”的顺序执行节点),你必须跳过一些障碍。它还会让您在调度开销和位置损失方面付出代价。

于 2018-01-04T19:25:15.457 回答