对数时间并行减少

2023-12-27

Given n部分和 可以在 log2 并行步骤中对所有部分和进行求和。例如,假设有八个线程,有八个部分和:s0, s1, s2, s3, s4, s5, s6, s7。这可以减少log2(8) = 3像这样的连续步骤;

thread0     thread1    thread2    thread4
s0 += s1    s2 += s3   s4 += s5   s6 +=s7
s0 += s2    s4 += s6
s0 += s4

我想使用 OpenMP 执行此操作,但我不想使用 OpenMPreduction条款。我已经提出了一个解决方案,但我认为也许可以使用 OpenMP 找到更好的解决方案task clause.

这比标量加法更通用。让我选择一个更有用的案例:数组缩减(参见here https://stackoverflow.com/questions/20413995/reducing-on-array-in-openmp/20421200#20421200, here https://stackoverflow.com/questions/16789242/fill-histograms-array-reduction-in-parallel-with-openmp-without-using-a-critic, and here https://stackoverflow.com/questions/30943452/openmp-c-parallel-for-loop-with-reduction-afterwards-best-practice/31002943#31002943有关数组缩减的更多信息)。

假设我想对数组进行数组缩减a。下面是一些为每个线程并行填充私有数组的代码。

int bins = 20;
int a[bins];
int **at;  // array of pointers to arrays
for(int i = 0; i<bins; i++) a[i] = 0;
#pragma omp parallel
{
    #pragma omp single   
    at = (int**)malloc(sizeof *at * omp_get_num_threads());        
    at[omp_get_thread_num()] = (int*)malloc(sizeof **at * bins);
    int a_private[bins];
    //arbitrary function to fill the arrays for each thread
    for(int i = 0; i<bins; i++) at[omp_get_thread_num()][i] = i + omp_get_thread_num();
}

此时,我有一个指向每个线程的数组的指针数组。现在我想将所有这些数组加在一起并将最终总和写入a。这是我想出的解决方案。

#pragma omp parallel
{
    int n = omp_get_num_threads();
    for(int m=1; n>1; m*=2) {
        int c = n%2;
        n/=2;
        #pragma omp for
        for(int i = 0; i<n; i++) {
            int *p1 = at[2*i*m], *p2 = at[2*i*m+m];
            for(int j = 0; j<bins; j++) p1[j] += p2[j];
        }
        n+=c;
    }
    #pragma omp single
    memcpy(a, at[0], sizeof *a*bins);
    free(at[omp_get_thread_num()]);
    #pragma omp single
    free(at);
}

让我尝试解释一下这段代码的作用。假设有八个线程。让我们定义+=运算符表示对数组求和。例如s0 += s1 is

for(int i=0; i<bins; i++) s0[i] += s1[i]

那么这段代码就可以了

n   thread0     thread1    thread2    thread4
4   s0 += s1    s2 += s3   s4 += s5   s6 +=s7
2   s0 += s2    s4 += s6
1   s0 += s4

但这段代码并不像我想要的那样理想。

一个问题是存在一些隐式障碍,需要所有线程同步。这些障碍应该是没有必要的。第一个障碍是填充数组和进行缩减之间。第二个障碍是在#pragma omp for声明中的削减。但我不能使用nowait子句用这个方法来消除障碍。

另一个问题是有几个线程不需要使用。例如有八个线程。缩减的第一步只需要四个线程,第二步需要两个线程,最后一步只需要一个线程。但是,此方法将涉及减少中的所有八个线程。尽管如此,其他线程无论如何也不做太多事情,应该直接进入屏障并等待,所以这可能不是什么大问题。

我的直觉是使用 omp 可以找到更好的方法task条款。不幸的是我对此缺乏经验task条款以及我迄今为止所做的所有努力都比我现在失败的减少效果更好。

有人可以建议一个更好的解决方案来减少对数时间,例如使用OpenMP 的task clause?


我找到了解决障碍问题的方法。这减少了异步。唯一剩下的问题是它仍然将不参与归约的线程放入繁忙循环中。此方法使用类似堆栈的东西将指针推入关键部分中的堆栈(但从不弹出它们)(这是关键之一关键部分没有隐式障碍 https://stackoverflow.com/a/10447326/2542702。堆栈是串行操作的,而减少是并行的。

这是一个工作示例。

#include <stdio.h>
#include <omp.h>
#include <stdlib.h>
#include <string.h>

void foo6() {
    int nthreads = 13;
    omp_set_num_threads(nthreads);
    int bins= 21;
    int a[bins];
    int **at;
    int m = 0;
    int nsums = 0;
    for(int i = 0; i<bins; i++) a[i] = 0;
    #pragma omp parallel
    {
        int n = omp_get_num_threads();
        int ithread = omp_get_thread_num();
        #pragma omp single
        at = (int**)malloc(sizeof *at * n * 2);
        int* a_private = (int*)malloc(sizeof *a_private * bins);

        //arbitrary fill function
        for(int i = 0; i<bins; i++) a_private[i] = i + omp_get_thread_num();

        #pragma omp critical (stack_section)
        at[nsums++] = a_private;

        while(nsums<2*n-2) {
            int *p1, *p2;
            char pop = 0;
            #pragma omp critical (stack_section)
            if((nsums-m)>1) p1 = at[m], p2 = at[m+1], m +=2, pop = 1;
            if(pop) {
                for(int i = 0; i<bins; i++) p1[i] += p2[i];
                #pragma omp critical (stack_section)
                at[nsums++] = p1;
            }
        }

        #pragma omp barrier
        #pragma omp single
        memcpy(a, at[2*n-2], sizeof **at *bins);
        free(a_private);
        #pragma omp single
        free(at);
    }
    for(int i = 0; i<bins; i++) printf("%d ", a[i]); puts("");
    for(int i = 0; i<bins; i++) printf("%d ", (nthreads-1)*nthreads/2 +nthreads*i); puts("");
}

int main(void) {
    foo6();
}

我仍然觉得使用任务可以找到更好的方法,该方法不会使线程不在繁忙的循环中使用。


实际上,使用递归分而治之的方法来干净地实现任务是非常简单的。这几乎是textbook https://en.wikibooks.org/wiki/OpenMP/Tasks code.

void operation(int* p1, int* p2, size_t bins)
{
    for (int i = 0; i < bins; i++)
        p1[i] += p2[i];
}

void reduce(int** arrs, size_t bins, int begin, int end)
{
    assert(begin < end);
    if (end - begin == 1) {
        return;
    }
    int pivot = (begin + end) / 2;
    /* Moving the termination condition here will avoid very short tasks,
     * but make the code less nice. */
#pragma omp task
    reduce(arrs, bins, begin, pivot);
#pragma omp task
    reduce(arrs, bins, pivot, end);
#pragma omp taskwait
    /* now begin and pivot contain the partial sums. */
    operation(arrs[begin], arrs[pivot], bins);
}

/* call this within a parallel region */
#pragma omp single
reduce(at, bins, 0, n);

据我所知,没有不必要的同步,也没有对关键部分进行奇怪的轮询。它也可以自然地处理与您的排名数不同的数据大小。我发现它非常干净且易于理解。所以我确实认为这是better比你的两个解决方案。

但让我们看看它在实践中的表现*。为此我们可以使用Score-p http://www.vi-hps.org/projects/score-p/ and Vampir http://vampir.eu:

*bins=10000 so the reduction actually takes a little bit of time. Executed on a 24-core Haswell system w/o turbo. gcc 4.8.4, -O3. I added some buffer around the actual execution to hide initialization/post-processing

该图显示了水平时间轴上应用程序内任何线程所发生的情况。树的实现从上到下:

  1. omp for loop
  2. omp critical一种任务分配。
  3. omp task

这很好地展示了具体的实现是如何实际执行的。现在看来,尽管存在不必要的同步,但 for 循环实际上是最快的。但这种性能分析仍然存在一些缺陷。例如,我没有固定线程。在实践中,NUMA(非均匀内存访问)非常重要:核心是否在其自己的缓存/其自己的套接字内存中拥有此数据?这就是任务解决方案变得不确定的地方。在简单比较中没有考虑重复之间非常显着的差异。

如果归约操作在运行时变得可变,那么任务解决方案将变得比同步 for 循环更好。

The critical解决方案有一些有趣的方面,被动线程不会持续等待,因此它们更有可能消耗 CPU 资源。这可能对性能不利,例如在涡轮模式的情况下。

请记住,task通过避免生成立即返回的任务,解决方案具有更大的优化潜力。这些解决方案的执行方式也很大程度上取决于特定的 OpenMP 运行时。英特尔的运行时似乎在任务方面表现更差。

我的建议是:

  • 用最优算法实现最可维护的解决方案 复杂
  • 衡量代码的哪些部分对运行时真正重要
  • 根据实际测量分析瓶颈是什么。根据我的经验,这更多的是关于 NUMA 和调度,而不是一些不必要的障碍。
  • 根据您的实际测量进行微观优化

线性解

这是线性时间线proccess_data_v1 from 这个问题 https://stackoverflow.com/q/16789242/620382.

OpenMP 4 缩减

所以我想到了OpenMP缩减。棘手的部分似乎是从at循环内的数组没有副本。我用以下方法初始化工作数组NULL第一次只需移动指针:

void meta_op(int** pp1, int* p2, size_t bins)
{
    if (*pp1 == NULL) {
        *pp1 = p2;
        return;
    }
    operation(*pp1, p2, bins);
}

// ...

// declare before parallel region as global
int* awork = NULL;

#pragma omp declare reduction(merge : int* : meta_op(&omp_out, omp_in, 100000)) initializer (omp_priv=NULL)

#pragma omp for reduction(merge : awork)
        for (int t = 0; t < n; t++) {
            meta_op(&awork, at[t], bins);
        }

令人惊讶的是,这看起来不太好:

top is icc 16.0.2, bottom is gcc 5.3.0, both with -O3.

两者似乎都实现了串行化的减少。我试着调查一下gcc / libgomp,但我并不能立即明白发生了什么。从中间代码/反汇编来看,他们似乎将最终合并包装在一个GOMP_atomic_start/end- 这似乎是一个全局互斥体。相似地icc将调用包装到operation in a kmpc_critical。我认为成本高昂的定制缩减操作没有太多优化。传统的缩减可以通过硬件支持的原子操作来完成。

注意每个operation速度更快,因为输入是在本地缓存的,但由于序列化,整体速度较慢。同样,由于差异很大,这并不是一个完美的比较,并且早期的屏幕截图使用了不同的gcc版本。但趋势很明显,而且我也有缓存效果的数据。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

对数时间并行减少 的相关文章

  • 使用 C# 登录《我的世界》

    我正在尝试为自己和一些朋友创建一个简单的自定义 Minecraft 启动器 我不需要启动 Minecraft 的代码 只需要登录的实际代码行 例如 据我所知 您过去可以使用 string netResponse httpGET https
  • 如何在多线程C++ 17程序中交换两个指针?

    我有两个指针 pA 和 pB 它们指向两个大的哈希映射对象 当pB指向的哈希图完全更新后 我想交换pB和pA 在C 17中 如何快速且线程安全地交换它们 原子 我是 c 17 的新手 2个指针的原子无等待交换可以通过以下方式实现 inclu
  • 以编程方式读取 SQL Server 查询计划建议的 SQL 特定执行的索引?

    如果我在 SSMS 中运行此命令 set showplan xml on GO exec some procedure arg1 arg2 arg3 GO set showplan xml off GO 我获得查询执行中涉及的完整调用堆栈的
  • GetType() 在 Type 实例上返回什么?

    我在一些调试过程中遇到了这段代码 private bool HasBaseType Type type out Type baseType Type originalType type GetType baseType GetBaseTyp
  • ComboBox DataBinding 导致 ArgumentException

    我的几个类对象 class Person public string Name get set public string Sex get set public int Age get set public override string
  • IdentityServer 4 对它的工作原理感到困惑

    我阅读和观看了很多有关 Identity Server 4 的内容 但我仍然对它有点困惑 因为似乎有很多移动部件 我现在明白这是一个单独的项目 它处理用户身份验证 我仍然不明白的是用户如何注册它 谁存储用户名 密码 我打算进行此设置 Rea
  • 如何在C(Linux)中的while循环中准确地睡眠?

    在 C 代码 Linux 操作系统 中 我需要在 while 循环内准确地休眠 比如说 10000 微秒 1000 次 我尝试过usleep nanosleep select pselect和其他一些方法 但没有成功 一旦大约 50 次 它
  • 如何判断计算机是否已重新启动?

    我曾经使用过一个命令行 SMTP 邮件程序 作为试用版的限制 它允许您在每个 Windows 会话中最多接收 10 封电子邮件 如果您重新启动计算机 您可能还会收到 10 个以上 我认为这种共享软件破坏非常巧妙 我想在我的应用程序中复制它
  • 查看 NuGet 包依赖关系层次结构

    有没有一种方法 文本或图形 来查看 NuGet 包之间的依赖关系层次结构 如果您使用的是新的 csproj 您可以在此处获取所有依赖项 在项目构建后 项目目录 obj project assets json
  • C# 数据表更新多行

    我如何使用数据表进行多次更新 我找到了这个更新 1 行 http support microsoft com kb 307587 my code public void ExportCSV string SQLSyntax string L
  • 从客户端访问 DomainService 中的自定义对象

    我正在使用域服务从 Silverlight 客户端的数据库中获取数据 在DomainService1 cs中 我添加了以下内容 EnableClientAccess public class Product public int produ
  • Python 属性和 Swig

    我正在尝试使用 swig 为一些 C 代码创建 python 绑定 我似乎遇到了一个问题 试图从我拥有的一些访问器函数创建 python 属性 方法如下 class Player public void entity Entity enti
  • 启动时的 Excel 加载项

    我正在使用 Visual C 创建 Microsoft Excel 的加载项 当我第一次创建解决方案时 它包含一个名为 ThisAddIn Startup 的函数 我在这个函数中添加了以下代码 private void ThisAddIn
  • 在 NaN 情况下 to_string() 可以返回什么

    我使用 VS 2012 遇到了非常令人恼火的行为 有时我的浮点数是 NaN auto dbgHelp std to string myFloat dbgHelp最终包含5008角色 你不能发明这个东西 其中大部分为0 最终结果是 0 INF
  • 使 Guid 属性成为线程安全的

    我的一个类有一个 Guid 类型的属性 该属性可以由多个线程同时读写 我的印象是对 Guid 的读取和写入不是原子的 因此我应该锁定它们 我选择这样做 public Guid TestKey get lock testKeyLock ret
  • 堆栈是向上增长还是向下增长?

    我在 C 中有这段代码 int q 10 int s 5 int a 3 printf Address of a d n int a printf Address of a 1 d n int a 1 printf Address of a
  • Objective-C / C 给出枚举默认值

    我在某处读到过关于给枚举默认值的内容 如下所示 typedef enum MarketNavigationTypeNone 0 MarketNavigationTypeHeirachy 1 MarketNavigationTypeMarke
  • 灵气序列解析问题

    我在使用 Spirit Qi 2 4 编写解析器时遇到一些问题 我有一系列键值对以以下格式解析
  • 如何使用 C++11 using 语法键入定义函数指针?

    我想写这个 typedef void FunctionPtr using using 我该怎么做呢 它具有类似的语法 只不过您从指针中删除了标识符 using FunctionPtr void 这是一个Example http ideone
  • OpenCV SIFT 描述符关键点半径

    我正在深入研究OpenCV的SIFT描述符提取的实现 https github com Itseez opencv blob master modules nonfree src sift cpp 我发现了一些令人费解的代码来获取兴趣点邻域

随机推荐

  • 将 ruby​​ 哈希值转换为 html 列表

    我正在尝试解析这样的 yaml 文件 a a1 a2 b b1 b11 b2 我得到这样的哈希值 a gt a1 gt nil a2 gt nil b gt b1 gt b11 gt nil b2 gt nil 我想把它变成一个列表 ul
  • Fastapi 中的速率限制

    如何在 Fastapi 应用程序中对 API 端点请求进行速率限制 我需要对每个用户每秒 5 个请求的 API 调用进行速率限制 超过该限制会阻止该特定用户 60 秒 在main py中 def get application gt Fas
  • Python - 从串行端口数据逐行读取到可用的列表中

    我的目标是编写一个代码 该代码将无限期地监听和读取串行端口 每隔几秒就会产生此输出 串口输出 aaaa abcd 0 0 0 printf d n data 0 2387 printf d n data 1 14 9 244 44 108
  • Mongoose 按日期查询

    我想用这样的文档结构查询 mongoDB var ExampleSchema mongoose Schema createdAt type Date default Date now validUntil Date name String
  • 具有自定义高度的自定义 UINavigationBar 会导致 UIBarButtonItem 的位置错误

    我创建了自己的 UINavigationBar 子类 以便启用高于 44 像素的自定义背景 我通过重写这两种方法来做到这一点 void drawRect CGRect rect self backgroundImage drawInRect
  • 使用java对整数进行加密

    我正在尝试使用 java security 和 javax crypto 加密 java 中的一些整数 问题似乎是 Cipher 类仅加密字节数组 我无法直接将整数转换为字节字符串 或者可以吗 做这个的最好方式是什么 我应该将整数转换为字符
  • Angular Fire - 没有 InjectionToken 的提供者(angularfire2.app.options)

    Context 我正在与Ionic 和 Angular Angularfire 和 Firebase 我已经做了一个连接成功 to the Firestore数据库我能够操纵数据 规格 Ionic CLI 6 18 1 Ionic Fram
  • Three.js 不拉伸网格纹理(图像) - 使其覆盖其容器

    我有一个容器 我使用 Three js 和网格应用图像 这就是我将网格应用到场景的方式 this els el el image el querySelector ch image lt size of container image is
  • Keras - 如何使用 KerasRegressor 执行预测?

    我是机器学习新手 我正在尝试处理 Keras 来执行回归任务 我已经实现了这段代码 基于this http machinelearningmastery com regression tutorial keras deep learning
  • 如何执行缺失值的 RMSE?

    我有一个巨大的数据集 有 679 行和 16 列 其中有 30 的缺失值 因此 我决定使用 impute 包中的函数 impute knn 来估算缺失值 并得到一个包含 679 行和 16 列但没有缺失值的数据集 但现在我想使用 RMSE
  • 未为子资源调用 JAX-RS DynamicFilter

    根据文档 应该可以使用DynamicFeature https docs oracle com javaee 7 api javax ws rs container DynamicFeature html对于资源和子资源 作为效果 我希望每
  • 从关闭的 NetworkStream 读取不会导致任何异常

    我正在尝试创建一个相当简单的客户端服务器应用程序 但为了进行通信 我想使用二进制序列化对象 通信本身看起来相当不错 但是当我关闭客户端的流时 服务器并没有真正注意到它并继续尝试读取流 服务器端 Server 类 在单独的线程中执行 监听连接
  • _AppStart.cshtml、PackageManager、WebMatrix

    我认为将 SimpleMembersihp 添加到 MVC4 Web 是一件简单的事情 并非如此 模板化代码 例如 C 非常适合支持它 但 web config 大多是不可知的 缺乏配置任何特定安全机制的元素 我正在关注 Scott All
  • 在 XSL 翻译中更改 XML 文件的命名空间

    所以我有一个输入文件 它在默认命名空间中使用我公司的命名空间 xmlns companyURL 但我希望我的输出文件使用默认命名空间以外的其他内容 xmlns cmp companyURL 所以我使用以下方法构建我的文件cmp命名空间 但我
  • numpy 数组到文件,np.savetxt

    当我使用 np savetxt file txt arr1 arr2 arr3 时 将多个 numpy 数组保存到文件的最佳方法是什么 数组按列保存 而不是按行保存 因此很难导入到 Excel 中 如何以更标准的方式保存数组 Thanks
  • 如何屏蔽文本中的信用卡号掩码?

    我的网站上有一个表格 我的客户用此表格向我发送消息 有时他们会在消息上写下信用卡号码 所以这非常关键 我想屏蔽这些信用卡号码 但卡号当然不会定期出现 示例1 1111222233334444 示例2 4444 3333 2222 1111
  • 更改传单中标记的大小

    我在传单的地图上有一个标记 var centerMarker L marker centerPoint title unselected bindLabel schools i 0 centerMarker on click selectM
  • 为什么应该在 Android 中使用自定义内容提供程序?

    使用自定义内容提供商有哪些优势 为什么这样的内容提供者优于包装 SQL 查询的普通类 内容提供程序可以从其他进程中使用 并且是 Android 上的某些机制 例如全局搜索 所需要的 还有一些可用的类可以帮助您处理内容提供 者 从而节省您管理
  • .NET 6:如何在控制台应用程序启动中使用方法重载?

    NET 6 在控制台应用程序 Startup 类中提供了样板删除功能 我尝试运行这个简单的测试代码 Console WriteLine Hello World static void Test int a int b static void
  • 对数时间并行减少

    Given n部分和 可以在 log2 并行步骤中对所有部分和进行求和 例如 假设有八个线程 有八个部分和 s0 s1 s2 s3 s4 s5 s6 s7 这可以减少log2 8 3像这样的连续步骤 thread0 thread1 thre