如何加速稀疏数组的加法

时间:2015-06-11 17:44:42

标签: c++ matrix graph parallel-processing

出于数据模拟的目的,我正在寻找一种有效的方法来对稀疏矩阵进行加权求和。 基本上我有一个Nx x Ny x Nz双值的数据立方体,其中Nx和Ny大约为4000,Nz是几百万。所有Nx×Ny子矩阵都非常稀疏(大约40个数据块)。 weighted sum 我现在想通过将所有矩阵相加并对它们进行加权来减少Z方向上的数据立方体。该过程如图所示。对于我的模拟,所有矩阵都保持不变,只有权重会发生变化并生成不同的Nx x Ny数据集。

这就是我所尝试的:C ++中稀疏矩阵的简单实现,以及简单的总结。

#ifndef SPARSEARRAY3D_H
#define SPARSEARRAY3D_H

#include <vector>

struct data{
    unsigned short int x;
    unsigned short int y;
    int z;
    double value;
};

class sparsearray3d
{
public:
    sparsearray3d();
    void createRandomData(int Nx, int Ny, int Nz, int bwidthX, int bwidthY);
    void sumData();
    int Nx,Ny,Nz;

    std::vector<data> dd;
    std::vector<std::vector<double> > image;
    std::vector<double> weights;
};

#endif // SPARSEARRAY3D_H

sparsearray3d.cpp

#include "sparsearray3d.h"
#include <stdlib.h>     /* srand, rand */
#include <stdio.h>      /* printf, scanf, puts, NULL */


sparsearray3d::sparsearray3d()
{
    this->Nx = 0;
    this->Ny = 0;
    this->Nz = 0;
}

void sparsearray3d::createRandomData(int Nx, int Ny, int Nz, int bwidthX = 5, int bwidthY = 5)
{
    // create random data
    this->weights.resize(Nz);

    this->image.resize( Nx , std::vector<double>( Ny , 0. ) );

    this->Nx = Nx;
    this->Ny = Ny;
    this->Nz = Nz;

    for(int i=0; i<Nz; ++i)
    {
        int x0 = rand() % (Nx-bwidthX);
        int y0 = rand() % (Ny-bwidthY);

        this->weights.push_back((double) rand() / (RAND_MAX));

        for(int j=0; j<bwidthX; ++j)
        {
            for(int k=0; k<bwidthY; ++k)
            {
                this->dd.push_back({x0+j,y0+k,i,((double) rand() / (RAND_MAX))});
            }
        }
    }
    printf("Vector size: %4.2f GB \n", this->dd.size()*sizeof(data) * 1E-9);

}

void sparsearray3d::sumData()
{
    std::vector<data>::iterator it;
    #pragma omp parallel for
    for(it = this->dd.begin(); it < this->dd.end(); ++it)
    {
        this->image[it->y][it->x] += it->value * this->weights[it->z];
    }
}

的main.cpp

#include <iostream>
#include "sparsearray3d.h"
#include <sys/time.h>

using namespace std;

int main()
{

struct timeval start, end;

sparsearray3d sa;
gettimeofday(&start, NULL);
sa.createRandomData(4096, 4096, 2000000, 4, 16);
gettimeofday(&end, NULL);
double delta = ((end.tv_sec  - start.tv_sec) * 1000000u +
         end.tv_usec - start.tv_usec) / 1.e6;

cout << "random array generation: " << delta << endl;
gettimeofday(&start, NULL);
sa.sumData();
gettimeofday(&end, NULL);
delta = ((end.tv_sec  - start.tv_sec) * 1000000u +
         end.tv_usec - start.tv_usec) / 1.e6;
cout << "array addition: " << delta << endl;
return 0;
}

这已经很好了,上面的例子在这里运行~0.6s。 我想知道的第一件事是,为什么#pragma omp parallel for的速度只提高了2倍,尽管使用了4个CPU。

这个问题似乎非常适合大规模并行化。 Cuda / OpenCL能在这里提供帮助吗?但是,我在某处读到,Cuda / OpenCL的矩阵添加效率不高。 (虽然我没有NVIDIA卡)。 或者,我读一点关于图及其与矩阵的关系。用一些图算法可以解决这个问题吗?

编辑: 我试图给艾根一枪;但是,我没能创建大量的矩阵。下面的代码需要比我的代码更多的内存(并且N~20000000失败,因为我的内存不足)。不确定我做得对,但这就是我从特征文档中理解它的方式。

#include <vector>
#include <eigen3/Eigen/Sparse>

int main()
{
int N=100000;
std::vector<Eigen::SparseMatrix<double> > data;

data.resize(N);

for (int i=0; i<N; ++i)
{
    data[i].resize(4096,4096);
    data[i].reserve(4*16);
}

return 0;
}

另外,以下列方式总结稀疏矩阵比我的代码要慢得多:

Eigen::SparseMatrix<double> sum(4096,4096) ;
sum.reserve(4096*4096);
for(int i=0; i<N; ++i)
    sum+=data[i];

1 个答案:

答案 0 :(得分:1)

你所处理的是相对一般的线性代数矩阵 - 向量乘法的情况(一些相关的计算机科学缩写来搜索&#34; DGEMM&#34;或&#34; SpMV&# 34)。因此,您的第一个选择是尝试高度优化的并行线性代数库,如英特尔 MKL ,另请参阅: Parallel linear algebra for multicore system

其次,如果您想自己优化和并行化您的算法,那么您可能首先需要学习一些相关的文章(例如:   - http://cscads.rice.edu/publications/pdfs/Williams-OptMultiCore-SC07.pdf   - http://pcl.intel-research.net/publications/ics26-liuPS.pdf

第三,如果你不想要或没有时间浏览书籍,文章或图书馆,但想要自己从头开始试验所有事情,就要考虑很少的事情:

  1. 你所拥有的实际上是细粒度并行性(每次迭代太快==太小),所以标准化&#34;每次迭代&#34;线程调度开销可能相对难以“#m; ammortize&#34;”。
  2. 对于细粒度并行性,最好尝试 SIMD 并行化(特别是因为你有可能&#34; FMA ==融合乘法添加&# 34;这里)。在 OpenMP4 .x(由ICC和GCC4.9 +支持)中,您有#pragma omp simd,这对于simd缩减非常有效。
  3. 但正确的选择实际上是简化/反向设计你的例子,并使用for循环x(#pragma omp for)和nexted循环y(#pragma omp simd)使其显式循环。那么你将拥有 2级并行性,这将使你的状态更好。
  4. 一旦你成功完成上述所有工作,你很快就会受到内存缓存/延迟,内存带宽或不规则访问模式的束缚 - 内存墙的3个面 。在您当前的实现中,我希望您受到带宽的限制,但是真正优化的稀疏矩阵矩阵产品往往受到给定3个面的各种组合的限制。这是阅读一些文章的必要之处,并考虑&#34;循环阻塞&#34;,&#34;阵列结构&#34;,&#34;预取和流媒体&#34;主题将变得非常相关。
  5. 最后(也许它是第一项),我不认为你真的拥有&#34;稀疏矩阵&#34; - 这里的数据结构,因为你真的没有尝试过避免&#34;零&#34; (他们仍然坐在你的矩阵向量中)。我不知道你是否已经采用了它的数据结构(所以你的例子只是简化了实际代码......)。

    平行线性代数是计算机科学中的一个重要课题;每隔一年,一些专家就会产生新的情报。并且每个下一代CPU和有时候的协处理器都经过了更好的优化,以加速并行化线性代数内核。所以有很多东西要探索。