首頁 > 軟體

c++矩陣計算效能對比:Eigen和GPU解讀

2022-12-17 14:00:51

生成隨機矩陣

生成隨機矩陣有多種方式,直接了當的方式是使用顯式迴圈的方式為矩陣的每個元素賦隨機值。

#include <iostream>
#include <random>

using namespace std;

// 生成亂數
double GenerateRandomRealValue()
{
    std::random_device rd;
    std::default_random_engine eng(rd());
    std::uniform_real_distribution<double> distr(1, 10);
    return distr(eng);
}

int main()
{
        // 3d矩陣
    double a[3][3];
    for (int i = 0; i < 3; ++i) {
        for (int j = 0;  j < 3; ++j) {
            a[i][j] = GenerateRandomRealValue();
        }
    }

    return 0;
}

另一種方式是使用Eigen庫,它提供了矩陣運算的庫。

生成隨機矩陣:

#include "Eigen/Dense"
#include <functional>

using namespace std;
using namespace Eigen;

MatrixXd Generate2DMatrixByEigen()
{
        // 直接使用內建的Random,產生均勻分佈隨機矩陣
    MatrixXd m = MatrixXd::Random(3,3);
    
    // 也可以呼叫自定義的亂數生成函數填充資料
    // MatrixXd m = MatrixXd::Zero(3,3).unaryExpr(std::bind(GenerateRandomRealValue));
    return m;
}

計算矩陣點積

使用顯式迴圈計算

直接上程式碼:

void CalcMatrixDotForLoop(const vector<vector<double>>& a, const vector<vector<double>>& b)
{
    std::chrono::high_resolution_clock::time_point t1 = std::chrono::high_resolution_clock::now();
    if (a[0].size() != b.size()) {
        cout << "error:" << a.size() << "," << b[0].size() << endl;
        return;
    }

    vector<vector<double>> c;
    vector<double> c_row(b[0].size());
    for (int i = 0; i < a.size(); ++i) {
        for (int j = 0; j < b[0].size(); ++j) {
            for (int k = 0; k < b.size(); ++k) {
                c_row[j] += a[i][k] * b[k][j];
            }
        }
        c.emplace_back(c_row);
    }
    std::chrono::high_resolution_clock::time_point t2 = std::chrono::high_resolution_clock::now();
    std::chrono::duration<double, std::milli> time_span = t2 - t1;
    std::cout << "Loop takes " << time_span.count() << " msn";

    // cout << "matrix c:n";
    // for (int i = 0; i < c.size(); ++i) {
    //     for (int j = 0; j < c[0].size(); ++j) {
    //         cout << c[i][j] << ",";
    //     }
    //     cout << endl;
    // }
}

使用Eigen庫

程式碼:

void ModeEigen(const int a_row, const int a_col, const int b_row, const int b_col)
{
    std::chrono::high_resolution_clock::time_point t1 = std::chrono::high_resolution_clock::now();
    auto c = a * b;
    std::chrono::high_resolution_clock::time_point t2 = std::chrono::high_resolution_clock::now();
    std::chrono::duration<double, std::milli> time_span = t2 - t1;
    std::cout << "Eigen takes " << time_span.count() << " msn";
    // cout << "matrix c:n" << c << endl;
}

使用GPU

程式碼片斷:

auto t_begin = std::chrono::high_resolution_clock::now();

t1 = std::chrono::high_resolution_clock::now();
cudaMalloc((void**)&da,size);
cudaMalloc((void**)&db,size);
cudaMalloc((void**)&dc,size);
t2 = std::chrono::high_resolution_clock::now();
time_span = t2 - t1;
std::cout << "GPU malloc takes " << time_span.count() << " msn";

t1 = std::chrono::high_resolution_clock::now();
cudaMemcpy(da,a,size,cudaMemcpyHostToDevice);
cudaMemcpy(db,b,size,cudaMemcpyHostToDevice);
t2 = std::chrono::high_resolution_clock::now();
time_span = t2 - t1;
std::cout << "cudaMemcpy takes " << time_span.count() << " msn";

t1 = std::chrono::high_resolution_clock::now();
dim3 dg(32,32);
dim3 dbs((n+dg.x-1)/dg.x,(n+dg.y-1)/dg.y);
mextix<<<dbs,dg>>>(da,db,dc,n);
t2 = std::chrono::high_resolution_clock::now();
time_span = t2 - t1;
std::cout << "gpu takes " << time_span.count() << " msn";

t1 = std::chrono::high_resolution_clock::now();
cudaMemcpy(c,dc,size,cudaMemcpyDeviceToHost);
t2 = std::chrono::high_resolution_clock::now();
time_span = t2 - t1;
std::cout << "cudaMemcpy back takes " << time_span.count() << " msn";

cudaFree(da);
cudaFree(db);
cudaFree(dc);

auto t_end = std::chrono::high_resolution_clock::now();
time_span = t_end - t_begin;
std::cout << "GPU total takes " << time_span.count() << " msn";

結果分析

經過測試,得到以下結論:

  • 對於CPU上矩陣運算來說,使用Eigen遠遠優於顯式迴圈(我只使用了單執行緒,你當然可以嘗試多執行緒,但程度複雜度會明顯上升)
  • 對於小規模矩陣來說,Eigen庫要快於GPU(資料在host和device之間的拷貝消耗了大量的時間)
  • 對於較大規模矩陣來說,GPU的優勢才顯現出來(資料運算時間超過了拷貝耗時,運算量越大,GPU並行的優勢也越明顯)

總之:

  • 絕對避免使用顯式迴圈,使用Eigen庫
  • 對於一般的應用來說,使用Eigen庫足夠應付大多數場景,畢竟CPU機器要比GPU機器廉價且普遍
  • 對於涉及大量的矩陣運算,包括機器學習等,GPU才是真正的用武之地

總結

以上為個人經驗,希望能給大家一個參考,也希望大家多多支援it145.com。


IT145.com E-mail:sddin#qq.com