如何加快稀疏数组加法

Posted

技术标签:

【中文标题】如何加快稀疏数组加法【英文标题】:How to speed up sparse array addition 【发布时间】:2015-06-11 17:44:41 【问题描述】:

出于数据模拟的目的,我正在寻找一种有效的方法来对稀疏矩阵进行加权求和。 基本上我有一个 Nx x Ny x Nz 双值的数据立方体,其中 Nx 和 Ny 大约是 4000,而 Nz 是几百万。所有 Nx x Ny 子矩阵都非常稀疏(大约 40 个整数的数据块)。 我现在想通过将所有矩阵相加并加权它们来减少 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.6 秒。 我想知道的第一件事是,为什么 #pragma omp parallel for 尽管使用了 4 个 CPU,但速度却只有 2 倍左右。

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

编辑: 我试图给 Eigen 一个机会;但是,我未能创建大量矩阵。下面的代码比我的代码需要更多的内存(N~20000000 失败,因为我的内存用完了)。不确定我做得对,但这就是我从 eigen 文档中理解的方式。

#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];

【问题讨论】:

你为什么不使用库(可能是 Boost、Eigen 等)? 查看编辑(本征耗尽了我的记忆,而且似乎很慢,至少在我使用它的(很可能是错误的)方式上) 也许您可以创建项目列表矩阵?这些矩阵的每个元素只是列表(或数组或其他东西),其中包含每个稀疏矩阵的 i,j 项。这些方法允许您对每个项目进行独立于其他项目的总和,并且 GPU 上的计算可以为您提供很好的加速。您还调整了 dd "this->dd.resize" 的大小,然后添加了新元素。确定吗? PS对不起我的英语不好 谢谢,我更正了代码。我一开始没有使用 pushback,这就是为什么我仍然有 dd.resize 的原因。我会试试你的方法。 除了我的一般性和有意抽象的答案之外,您还可以在这里查看一个(在许多类似的)漂亮的 Z Boson 回复:***.com/questions/30838959/…。请注意,我有点“不建议”使用内在函数(因为可移植性)进行 simdization + 这篇文章确实不是关于您的问题陈述,但这仍然是考虑 2 级并行性以及如何处理的好方法记忆墙。 【参考方案1】:

您处理的是线性代数矩阵向量乘法的相对一般情况(要搜索的一些相关计算机科学缩写是“DGEMM”或“SpMV”)。因此,您的第一个选择是尝试高度优化的并行线性代数库,例如英特尔 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 )

第三,如果您不想或没有时间浏览书籍、文章或图书馆,但想自己从头开始尝试一切,则需要考虑几件事:

    您所拥有的实际上是细粒度 并行性(每次迭代都太快 == 太小),因此标准化的“每次迭代”线程调度开销可能相对难以“摊销”。 对于细粒度并行,最好尝试 SIMD 并行化(尤其是因为您有可能在此处使用“FMA==fused multiply add”)。在 OpenMP4.x(由 ICC 和 GCC4.9+ 支持)中,您有 #pragma omp simd ,它非常适合减少 simd。 但正确的选择实际上是简化/逆向工程您的示例,并使用 x 的 for 循环 (#pragma omp for) 和 y 的下一个循环 (#pragma omp simd) 使其显式循环。然后您将拥有 2 级并行度,这将使您的状态更好。 一旦您成功完成上述所有操作,您很快就会受到内存缓存/延迟、内存带宽或不规则访问模式的限制 - 类似于 内存墙 的 3 个面.在您当前的实现中,我希望您受到带宽的限制,但是真正优化的稀疏矩阵矩阵产品往往受到给定 3 个面的各种组合的限制。这是需要阅读一些文章的地方,考虑到“循环阻塞”、“数组结构”、“预取和流式传输”主题将变得非常相关。

最后(也许是第一项),我不认为您在这里真的有“稀疏矩阵”感知数据结构,因为您并没有真正尝试避免“零”(它们仍然位于您的矩阵中-向量)。我不知道你是否已经采用了你的数据结构(所以你的例子只是简化了真实的代码..)。

并行线性代数是计算机科学中的一个大课题;每年都有一些专家提出新的情报。每个下一代 CPU 和有时协处理器都经过更好的优化,以加速并行化线性代数内核。所以有很多东西可以探索。

【讨论】:

以上是关于如何加快稀疏数组加法的主要内容,如果未能解决你的问题,请参考以下文章

有哪些方法可以加快 Pytorch 中大型稀疏数组(约 100 万 x 100 万,密度约 0.0001)的数据加载速度?

Scipy CSR 矩阵逐元素加法

理解JS里的稀疏数组与密集数组

稀疏数组

:稀疏数组和队列 -- 稀疏数组链式存储压缩介绍队列

如何高效存储大型稀疏数组