【问题标题】:Efficient parallelisation of a linear algebraic function in C++ OpenMPC++ OpenMP 中线性代数函数的高效并行化
【发布时间】:2017-09-06 14:33:59
【问题描述】:

我几乎没有并行编程经验,想知道是否有人可以快速浏览一下我编写的一些代码,看看是否有任何明显的方法可以提高计算效率。

之所以出现困难,是因为我需要计算多个维度不等的矩阵运算,因此我不确定最简洁的计算编码方式。

下面是我的代码。请注意,此代码确实有效。我正在使用的矩阵的尺寸约为 700x700 [参见下面的 int s] 或 700x30 [int n]。

另外,我将犰狳库用于我的顺序代码。使用 openMP 进行并行化但保留犰狳矩阵类可能比默认使用标准库慢;有没有人对此有意见(在我花几个小时大修之前!)?

double start, end, dif;

int i,j,k;      // iteration counters
int s,n;        // matrix dimensions

mat B; B.load(...location of stored s*n matrix...) // input objects loaded from file
mat I; I.load(...s*s matrix...);
mat R; R.load(...s*n matrix...);
mat D; D.load(...n*n matrix...);

double e = 0.1; // scalar parameter

s = B.n_rows; n = B.n_cols;

mat dBdt; dBdt.zeros(s,n); // object for storing output of function

// 100X sequential computation using Armadillo linear algebraic functionality

start = omp_get_wtime();

for (int r=0; r<100; r++) {
    dBdt = B % (R - (I * B)) + (B * D) - (B * e);
}

end = omp_get_wtime();
dif = end - strt;
cout << "Seq computation: " << dBdt(0,0) << endl;
printf("relaxation time = %f", dif);
cout << endl;

// 100 * parallel computation using OpenMP

omp_set_num_threads(8);


for (int r=0; r<100; r++) {

//          parallel computation of I * B 
#pragma omp parallel for default(none) shared(dBdt, B, I, R, D, e, s, n) private(i, j, k) schedule(static)
    for (i = 0; i < s; i++) {
        for (j = 0; j < n; j++) {
            for (k = 0; k < s; k++) {
                dBdt(i, j) += I(i, k) * B(k, j);
            }
        }
     }

//          parallel computation of B % (R - (I * B)) 
#pragma omp parallel for default(none) shared(dBdt, B, I, R, D, e, s, n) private(i, j) schedule(static)
    for (i = 0; i < s; i++) {
        for (j = 0; j < n; j++) {
            dBdt(i, j)  = R(i, j) - dBdt(i, j);
            dBdt(i, j) *= B(i, j);
            dBdt(i, j) -= B(i, j) * e;
        }
    }

//          parallel computation of B * D 
#pragma omp parallel for default(none) shared(dBdt, B, I, R, D, e, s, n) private(i, j, k) schedule(static)
   for (i = 0; i < s; i++) {
        for (j = 0; j < n; j++) {
            for (k = 0; k < n; k++) {
                dBdt(i, j) += B(i, k) * D(k, j);
            }
        }
    }    
}

end = omp_get_wtime();
dif = end - strt;
cout << "OMP computation: " << dBdt(0,0) << endl;
printf("relaxation time = %f", dif);
cout << endl;

如果我超线程 4 个核心,我会得到以下输出:

Seq computation: 5.54926e-10
relaxation time = 0.130031
OMP computation: 5.54926e-10
relaxation time = 2.611040

这表明虽然两种方法产生相同的结果,但并行公式比顺序公式慢大约 20 倍。

对于这种大小的矩阵,这个“可变维度”问题所涉及的开销可能超过并行化的好处。任何见解将不胜感激。

提前致谢,

杰克

【问题讨论】:

  • 我不清楚你到底想做什么?所以犰狳代码有效并且做了它应该做的事情?然后使用它。如果您正确设置犰狳,它会尽可能快(缓存、simd、多线程;在内部使用BLAS),可能只是缺少矩阵重新排序(不确定是否已完成或支持)。
  • 在每个并行循环之后都有一个栅栏,即所有线程都等到最后一个循环结束。您有 3 个单独的并行部分,它们闻起来像是执行时间中一个很大的有效非并行部分。另外 700x700 不是一个很大的矩阵,也许你需要增加尺寸才能看到并行运行的好处
  • 我希望通过在具有 4 个以上内核的 HPC 上运行模拟来提高计算速度。内部 BLAS 实现是否自动使用所有可用内核?
  • @jack.l 恕我直言,应该清楚,过程中不存在任何矩阵链BDIB 都不是矩阵链。最好专注于核心问题。
  • @user3666197 好的。那是我的事,因为我没有仔细评估他的代码并提到了两次。

标签: c++ matrix parallel-processing openmp armadillo


【解决方案1】:

如果您使用的编译器可以纠正错误的循环嵌套并融合循环以改善非并行构建的内存局部性,openmp 可能会禁用这些优化。按照其他人的建议,您应该考虑使用优化的库,例如 mkl 或 acml。通常随发行版提供的默认 gfortran blas 不是多线程的。

【讨论】:

  • 抱歉,我不得不查找其中的一些术语。 Armadillo BLAS 确实支持 MKL 功能是否正确? (software.intel.com/en-us/articles/…) 在哪种情况下,您认为最优化的解决方案是简单地使用内置的 arma 功能?
【解决方案2】:

HPC 的艺术就是效率(可怜的授权永远不会获得 HPC 集群配额)

  • 所以首先希望您的进程永远不会从文件中重新读取

为什么?这将是 HPC 杀手:

我需要重复这个计算数千次

公平地说,这条评论增加了全面审查该方法并重新设计未来解决方案的总体需求,而不是依赖一些技巧,而是确实从您的案例特定安排中获益。

最后但同样重要的是 - [PARALLEL] 调度不是必需的,因为“just”-[CONCURRENT]-进程调度在这里就足够了。无需编排任何显式的进程间同步或任何消息传递,并且可以编排流程以获得可能的最佳性能。


没有“...快速浏览一下代码...”会有所帮助

您需要先了解您的整个流程以及硬件资源,它将在其上执行。

CPU 类型将告诉您用于高级技巧的可用指令集扩展,L3-/L2-/L1-缓存大小 + 缓存行大小将帮助您决定对廉价数据访问的最佳缓存友好重用(而不是支付数百 [ns],如果一个人可以在 几个 [ns] 上更智能地操作,而不是从尚未驱逐的 NUMA-core-local 副本)


首先是数学,其次是实现:

如给定dBdt = B % ( R - (I * B) ) + ( B * D ) - ( B * e )

仔细观察,任何人都应该准备好实现 HPC/缓存对齐优先级和错误循环陷阱:

dBdt = B % ( R - ( I * B ) )   ELEMENT-WISE OP B[s,n]-COLUMN-WISE
     +               ( B * D ) SUM.PRODUCT  OP B[s,n].ROW-WISE MUL-BY-D[n,n].COL
     -               ( B * e ) ELEMENT-WISE OP B[s,n].ROW-WISE MUL-BY-SCALAR

 ROW/COL-SUM.PRODUCT OP -----------------------------------------+++++++++++++++++++++++++++++++++++++++++++++
 ELEMENT-WISE OP ---------------------------------------------+  |||||||||||||||||||||||||||||||||||||||||||||
 ELEMENT-WISE OP ----------------------+                      |  |||||||||||||||||||||||||||||||||||||||||||||
                                       |                      |  |||||||||||||||||||||||||||||||||||||||||||||
                                       v                      v  vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
 dBdt[s,n]        =   B[s,n]           % /   R[s,n]           - /  I[s,s]                  . B[s,n]           \ \
     _________[n]         _________[n]   |       _________[n]   |      ________________[s]       _________[n]  | |
    |_|       |          |_|       |     |      |_|       |     |     |________________|        | |       |    | |
    | .       |          | .       |     |      | .       |     |     |                |        | |       |    | |
    | .       |          | .       |     |      | .       |     |     |                |        | |       |    | |
    | .       |          | .       |     |      | .       |     |     |                |        | |       |    | |
    | .       |   =      | .       |   % |      | .       |   - |     |                |   .    | |       |    | |
    | .       |          | .       |     |      | .       |     |     |                |        | |       |    | |
    | .       |          | .       |     |      | .       |     |     |                |        | |       |    | |
    | .       |          | .       |     |      | .       |     |     |                |        | |       |    | |
 [s]|_________|       [s]|_________|     |   [s]|_________|     |  [s]|________________|     [s]|_|_______|    | |
                                         \                      \                                             / /

                      B[s,n]              D[n,n]          
                          _________[n]        _________[n]
                         |_________|         | |       |   
                         | .       |         | |       |  
                         | .       |         | |       |  
                         | .       |         | |       |  
                  +      | .       |   .  [n]|_|_______|   
                         | .       |      
                         | .       |      
                         | .       |             
                      [s]|_________|      


                      B[s,n]                
                          _________[n]      
                         |_| . . . |        
                         | .       |        
                         | .       |        
                         | .       |        
                  -      | .       |    *  REGISTER_e
                         | .       |        
                         | .       |        
                         | .       |        
                      [s]|_________|        

考虑到这一点,高效的 HPC 循环看起来会大不相同

取决于实际 CPU 缓存,
循环自然可以非常有效地协同处理-B-row-aligned ( B * D ) - ( B * e )
在单个阶段,也是基于元素最长管道的最高重用效率部分B % ( R - ( I * B ) ) 这里有机会重用 B-column-aligned 的 ~ 1000 x ( n - 1 ) 缓存命中,这应该非常适合 L1-DATA-cache 占用空间,因此实现仅从缓存对齐的循环中就可以节省数秒的时间。


这个缓存友好的循环对齐完成后,

下一个可能是分布式处理帮助,不是以前。

所以,实验计划设置:

第 0 步:基本事实:~ 0.13 [s] for dBdt[700,30] 在 100 次测试循环中使用犰狳

第 1 步: 手动序列:- 测试最佳缓存对齐代码的奖励(不是发布的代码,而是数学等效的缓存行重用优化代码 - - 应该不超过 4xfor(){...} 代码块 2 个嵌套,其余 2 个在里面,满足线性代数规则而不会带来破坏性好处缓存行对齐(在 [PTIME] 中使用重复的 [PSPACE] 数据布局(包括 FORTRAN 顺序和一个 C 顺序,用于各自的重读策略),因为矩阵的大小非常小,每个 CPU 内核可用的 L2-/L1-DATA-cache 享受缓存大小的规模增长)

第 2 步: 手动 omp(&lt;= NUMA_cores - 1):- 测试 omp 是否确实可以产生任何 "positive" Amdahl's Law speedup(超出 omp-setup 管理费用)。一个小心的 process-2-CPU_core affinity-mapping 可能有助于避免任何可能的缓存驱逐由任何非 HPC 线程破坏一组配置上的缓存友好布局 - “保留” - 集( NUMA_cores - 1 ),所有其他(非 HPC 进程)应该被关联映射到最后一个(共享)CPU 内核,从而有助于防止那些 HPC 进程内核保留其缓存行未被任何内核驱逐/调度程序注入的非 HPC 线程。

(如 (2) 中所示,有一些源自 HPC 最佳实践的安排,没有任何编译器(甚至配备了魔杖的编译器)能够实现,所以请不要犹豫如果您的论文需要一些 HPC 专业知识,请您的博士导师帮忙,因为在这个非常昂贵的实验领域中建立试错并不容易,而且您的主要领域不是线性代数和/或终极 CS-理论/硬件特定的缓存策略优化。)


结语:

以不恰当的方式使用智能工具只会带来额外的开销(任务拆分/连接 + 内存转换(原子锁定更糟糕(阻塞/围栏/屏障最糟糕)))。

【讨论】:

    猜你喜欢
    • 2016-01-22
    • 2013-01-10
    • 2015-08-02
    • 2012-03-27
    • 1970-01-01
    • 2021-04-28
    • 2013-09-19
    • 1970-01-01
    • 2017-04-03
    相关资源
    最近更新 更多