在C ++ OpenMP中高效并行化线性代数函数

时间:2017-09-06 14:34:00

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

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

由于我需要计算多个不等维度的矩阵运算,所以难以产生,因此我不确定编码计算最简洁的方法。

以下是我的代码。请注意此代码是否有效。我正在使用的矩阵的大小约为700x700 [参见下面的内容]或700x30 [int n]。

另外,我使用armadillo库作为我的顺序代码。可能是使用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倍。

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

提前致谢,

杰克

2 个答案:

答案 0 :(得分:0)

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

答案 1 :(得分:0)

高性能计算的艺术是正确的效率(不良资助从未获得HPC集群配额)

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

为什么呢?这将是HPC杀手:

  

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

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

最后但并非最不重要 - 不需要 [PARALLEL] 日程安排,只需&#34;只需&#34; - [CONCURRENT] - 进程调度在这里已经足够了。没有必要协调任何明确的进程间同步或任何消息传递,并且该进程可能只是为了获得最佳性能而进行编排。

没有&#34; ...快速浏览一下代码......&#34; 会有所帮助

您需要先了解整个过程以及硬件资源,然后才能执行。

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

数学第一,实施下一步:

给定 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 - 对齐 ( B * D ) - ( B * e )
在单一阶段,也是元素最长管道的最高再利用效率部分 B % ( R - ( I * B ) ) 这里有机会重新使用~1000 x(n - 1)缓存命中 B - 列对齐,这应该非常适合L1-DATA缓存占用空间,因此,只需从缓存对齐的循环中实现几秒钟的节省。

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

接下来可能是分布式处理帮助,不是之前。

所以,实验计划设置:

第0步: ~ 0.13 [s] dBdt[700,30] for(){...} 在100测试中使用armadillo-循环

步骤1:手动序列: - 测试最佳缓存对齐代码(不是已发布的代码,但数学等效,缓存行重用优化代码)的奖励 - - 应该有不超过4x [PTIME]代码块2嵌套,其余2为内部,以满足线性代数规则而不会带来毁灭性的好处缓存行对齐(使用重复的 [PSPACE] 数据布局可以使 <= NUMA_cores - 1 中的某些剩余潜力受益更多对于各自的重读策略,FORTRAN-order和C-order都是如此,因为矩阵的大小非常小,每个CPU核心可用的L2- / L1-DATA-cache享受缓存大小的规模扩大)

步骤2: manual-omp(( NUMA_cores - 1 )): - 测试omp是否确实可以产生任何"positive" Amdahl's Law speedup(超出omp-setup开销成本)。仔细处理2-CPU_core 亲缘关系映射可能有助于避免任何非HPC线程引入的任何可能的缓存驱逐破坏了一组配置上的缓存友好布局 - &#34;保留&# 34; -set {{1}},其中所有其他(非HPC进程)应该被亲和映射到最后一个(共享)CPU核心,从而有助于防止那些HPC进程核心保留其缓存行未被任何内核/调度程序注入的非HPC线程驱逐。

(如(2)所示,从HPC最佳实践中得出的安排是,没有任何编译器(即使是配备魔术棒的编译器)也无法实现,所以请不要犹豫如果您的论文需要一些HPC专业知识,那么您的博士生导师可以提供帮助,因为在这个非常昂贵的实验领域中建立试验错误并不容易,而您的主要领域不是线性代数和/或最终的CS-理论/硬件特定的缓存策略优化。)

尾声:

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