CUDA编程(四): CPU与GPU的矩阵乘法对比

CUDA编程(六): 利用好shared memory
CUDA编程(五): 并行规约优化
CUDA编程(四): CPU与GPU的矩阵乘法对比
CUDA编程(三): GPU架构了解一下!
CUDA编程(二): Ubuntu下的CUDA10.x环境搭建
CUDA编程(一): 老黄和他的核弹们

目录

  • 前言
  • 计时函数
  • CPU代码
  • GPU代码
  • 最后

前言

在上一篇的最后, 我提到了一个矩阵乘法, 这次与CPU进行对比, 从中可以很明显GPU在并行计算上的优势.


计时函数

在贴出代码之前, 来看下我常用的计时函数, 可以精确到微秒级. 首先头文件是#include. 结构体为:

struct timeval{
    long tv_sec; /*秒*/
    long tv_usec; /*微秒*/
};

来看下使用的小栗子:

struct timeval start, end;
double timeuse;
int sum = 0;

gettimeofday (&start, NULL);
for (int i = 0; i < 10000; i++){
    sum += I;
}
gettimeofday (&end, NULL);

timeuse = end.tv_sec - start.tv_sec + (end.tv_usec - start.tv_usec)/1000000.0;
printf("Use Time:%f\n",timeuse);

CPU代码

CPU也是可以多线程操作的, 所以我第一反应也是多线程进行计算, 但是结果并不理想, 因为线程操作也是有不少开销的, 所以在计算量有限的情况下, 没有线程操作的单线程反而更快. 单线程代码较为简单, 如下:

#include 
#include 
#include     
#include 

#define w 8000

struct Matrix
{
    int width;
    int height;
    float *elements;
};

void matMul(float * M, float * N, float * P, int width){
    for (int i = 0; i < width; i++){
        for (int j = 0; j < width; j++){
            float sum = 0;
            for (int k = 0; k < width; k++){
                float a = M[i * width + k];
                float b = N[k * width + j];
                sum += a * b;
            }
            P[i * width + j] = sum;
        }
    }
}

int main(){
    int width = w;
    int height = w; 
    
    float * m = (float *)malloc (width * height * sizeof (float));
    float * n = (float *)malloc (width * height * sizeof (float));
    float * p = (float *)malloc (width * height * sizeof (float));

    for (int i = 0; i < width * height; i++){
        m[i] = 1.0;
        n[i] = 2.0;
    }

    struct timeval t1,t2;
    gettimeofday(&t1,NULL);
    double timeuse;

    matMul(m, n, p, w);

    gettimeofday(&t2,NULL);
    timeuse = t2.tv_sec - t1.tv_sec + (t2.tv_usec - t1.tv_usec)/1000000.0;
    printf("Use Time:%f\n",timeuse);

    return 0;
}

多线程部分代码较长, 思路其实并不复杂, 每个线程负责部分计算, 比方说双线程就一个计算偶数行, 一个计算奇数行:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define LOG_
#define SIZE 8000

int * A, * B; // 计算矩阵
int * result, * result2, * result3, * result4; // 结果矩阵
/*
int A[SIZE][SIZE];
int B[SIZE][SIZE];
int result[SIZE][SIZE];
int result2[SIZE][SIZE];
int result3[SIZE][SIZE];
int result4[SIZE][SIZE];
*/

int size; // 矩阵阶数
pthread_t tid2[2]; // 双线程id
pthread_t tid3[3]; // 三线程id
pthread_t tid4[4]; // 四线程id

/* 双线程函数 */
void twoThread1(){
    int i, j, k;
    for (i = 0; i < size; I++)
    for (j = 0; j < size; j++)
    for (k = 0; k < size; k++){
        if (i % 2 == 0)
            result2[i * size + j] += A[i * size + k] * B[k * size + j];
//            result2[i][j] += A[i][k] * B[k][j];
    }
}

void twoThread2(){
    int i, j, k;
    for (i = 0; i < size; I++)
    for (j = 0; j < size; j++)
    for (k = 0; k < size; k++){
        if (i % 2 != 0)
            result2[i * size + j] += A[i * size + k] * B[k * size + j];
//            result2[i][j] += A[i][k] * B[k][j];
    }
}
/* 双线程函数 end */

/* 三线程函数 */
void threeThread1(){
    int i, j, k;
    for (i = 0; i < size; I++)
    for (j = 0; j < size; j++)
    for (k = 0; k < size; k++){
        if (i % 3 == 0)
            result3[i * size + j] += A[i * size + k] * B[k * size + j];
//            result3[i][j] += A[i][k] * B[k][j];
    }
}

void threeThread2(){
    int i, j, k;
    for (i = 0; i < size; I++)
    for (j = 0; j < size; j++)
    for (k = 0; k < size; k++){
        if (i % 3 != 0 && i % 2 != 0)
            result3[i * size + j] += A[i * size + k] * B[k * size + j];
//            result3[i][j] += A[i][k] * B[k][j];
    }
}

void threeThread3(){
    int i, j, k;
    for (i = 0; i < size; I++)
    for (j = 0; j < size; j++)
    for (k = 0; k < size; k++){
        if (i % 3 != 0 && i % 2 == 0)
            result3[i * size + j] += A[i * size + k] * B[k * size + j];
//            result3[i][j] += A[i][k] * B[k][j];
    }
}
/* 三线程函数 end */

/* 四线程函数 */
void fourThread1(){
    int i, j, k;
    for (i = 0; i < size; I++)
    for (j = 0; j < size; j++)
    for (k = 0; k < size; k++){
        if (i % 2 == 0 && i % 4 != 0)
            result4[i * size + j] += A[i * size + k] * B[k * size + j];
//            result4[i][j] += A[i][k] * B[k][j];
    }
}

void fourThread2(){
    int i, j, k;
    for (i = 0; i < size; I++)
    for (j = 0; j < size; j++)
    for (k = 0; k < size; k++){
        if (i % 4 == 0)
            result4[i * size + j] += A[i * size + k] * B[k * size + j];
//            result4[i][j] += A[i][k] * B[k][j];
    }
}

void fourThread3(){
    int i, j, k;
    for (i = 0; i < size; I++)
    for (j = 0; j < size; j++)
    for (k = 0; k < size; k++){
        if (i % 2 != 0 && i % 3 == 0)
            result4[i * size + j] += A[i * size + k] * B[k * size + j];
//            result4[i][j] += A[i][k] * B[k][j];
    }
}

void fourThread4(){
    int i, j, k;
    for (i = 0; i < size; I++)
    for (j = 0; j < size; j++)
    for (k = 0; k < size; k++){
        if (i % 2 != 0 && i % 3 != 0)
            result4[i * size + j] += A[i * size + k] * B[k * size + j];
//            result4[i][j] += A[i][k] * B[k][j];
    }
}
/* 四线程函数 end */


int main(){
    int i, j, k, m, n; // 循环变量
    struct timeval t1, t2; 
    double timeuse; // 计时

    char sizeChars[8]; // 阶数写入字符串
    char timeChars[16]; // 耗时写入字符串

    // 申请空间, 计算矩阵和结果矩阵
    A = (int *)malloc (sizeof (int) * SIZE * SIZE);
    B = (int *)malloc (sizeof (int) * SIZE * SIZE);
    result = (int *)malloc (sizeof (int) * SIZE * SIZE);
    result2 = (int *)malloc (sizeof (int) * SIZE * SIZE);
    result3 = (int *)malloc (sizeof (int) * SIZE * SIZE);
    result4 = (int *)malloc (sizeof (int) * SIZE * SIZE);

    for (i = 0; i < SIZE; I++)
    for (j = 0; j < SIZE; j++){
        /*
        A[i][j] = 1;
        B[i][j] = 2;
        result[i][j] = 0;
        result2[i][j] = 0;
        result3[i][j] = 0;
        result4[i][j] = 0;
        */
        A[i * SIZE + j] = 1;
        B[i * SIZE + j] = 2;
        result[i * SIZE + j] = 0;
        result2[i * SIZE + j] = 0;
        result3[i * SIZE + j] = 0;
        result4[i * SIZE + j] = 0;
    }

    int fd;
    fd = open ("./pthreadTime.txt", O_WRONLY | O_CREAT);
    lseek(fd, 0, SEEK_SET);

    for (size = 200; size <= SIZE; size += 200){
        printf ("当前阶数: %d\n", size);
        sprintf (sizeChars, "%d, ", size);
        write (fd, sizeChars, strlen (sizeChars));

#ifdef LOG
        printf ("A矩阵: \n");
        for (i = 0; i < size; i++){
            for (j = 0; j < size; j++){
                printf ("%d ", A[i * size + j]);
//                printf ("%d ", A[i][j]);
            }
            printf ("\n");
        }

        printf ("B矩阵: \n");
        for (i = 0; i < size; i++){
            for (j = 0; j < size; j++){
                printf ("%d ", B[i * size + j]);
//                printf ("%d ", B[i][j]);
            }
            printf ("\n");
        }
#endif 

        /* 单线程 */
        gettimeofday (&t1, NULL);

        for (i = 0; i < size; I++)
        for (j = 0; j < size; j++)
        for (k = 0; k < size; k++){
            result[i * size + j] += A[i * size + k] * B[k * size +j];
//            result[i][j] += A[i][k] * B[k][j];
        }

        gettimeofday(&t2, NULL);
        timeuse = t2.tv_sec - t1.tv_sec + (t2.tv_usec - t1.tv_usec)/1000000.0;

#ifdef LOG
        printf ("单线程结果矩阵: \n");
        for (i = 0; i < size; i++){
            for (j = 0; j < size; j++){
                printf ("%d ", result[i * size + j]);
//                printf ("%d ", result[i][j]);
            }
            printf ("\n");
        }
#endif

        printf("单线程耗时: %fs\n", timeuse);
        sprintf (timeChars, "%lf, ", timeuse);
        write (fd, timeChars, strlen (timeChars));


        for (i = 0; i < size; I++)
        for (j = 0; j < size; j++){
            result[i * size + j] = 0;
            result2[i * size + j] = 0;
            result3[i * size + j] = 0;
            result4[i * size + j] = 0;
            /*
            result[i][j] = 0;
            result2[i][j] = 0;
            result3[i][j] = 0;
            result4[i][j] = 0;
            */
        }
        /* 单线程 end */

        /* 双线程 */
        gettimeofday (&t1, NULL);
        pthread_create (&tid2[0], NULL, (void *)twoThread1, NULL);
        pthread_join (tid2[0], NULL);
        pthread_create (&tid2[1], NULL, (void *)twoThread2, NULL);
        pthread_join (tid2[1], NULL);

        gettimeofday (&t2, NULL);
        timeuse = t2.tv_sec - t1.tv_sec + (t2.tv_usec - t1.tv_usec)/1000000.0;

#ifdef LOG
        printf ("双线程结果矩阵: \n");
        for (i = 0; i < size; i++){
            for (j = 0; j < size; j++){
                printf ("%d ", result2[i * size + j]);
//                printf ("%d ", result2[i][j]);
            }
            printf ("\n");
        }
#endif

        printf("双线程耗时: %fs\n", timeuse);
        sprintf (timeChars, "%lf, ", timeuse);
        write (fd, timeChars, strlen (timeChars));

        for (i = 0; i < size; I++)
        for (j = 0; j < size; j++){
            result[i * size + j] = 0;
            result2[i * size + j] = 0;
            result3[i * size + j] = 0;
            result4[i * size + j] = 0;
            /*
            result[i][j] = 0;
            result2[i][j] = 0;
            result3[i][j] = 0;
            result4[i][j] = 0;
            */
        }
        /* 双线程 end */

        /* 三线程 */
        gettimeofday (&t1, NULL);
        pthread_create (&tid3[0], NULL, (void *)threeThread1, NULL);
        pthread_join (tid3[0], NULL);
        pthread_create (&tid3[1], NULL, (void *)threeThread2, NULL);
        pthread_join (tid3[1], NULL);
        pthread_create (&tid3[2], NULL, (void *)threeThread3, NULL);
        pthread_join (tid3[2], NULL);

        gettimeofday (&t2, NULL);
        timeuse = t2.tv_sec - t1.tv_sec + (t2.tv_usec - t1.tv_usec)/1000000.0;

#ifdef LOG
        printf ("三线程结果矩阵: \n");
        for (i = 0; i < size; i++){
            for (j = 0; j < size; j++){
                printf ("%d ", result3[i * size + j]);
            }
            printf ("\n");
        }
#endif

        printf("三线程耗时: %fs\n", timeuse);
        sprintf (timeChars, "%lf, ", timeuse);
        write (fd, timeChars, strlen (timeChars));

        for (i = 0; i < size; I++)
        for (j = 0; j < size; j++){
            result[i * size + j] = 0;
            result2[i * size + j] = 0;
            result3[i * size + j] = 0;
            result4[i * size + j] = 0;
            /*
            result[i][j] = 0;
            result2[i][j] = 0;
            result3[i][j] = 0;
            result4[i][j] = 0;
            */
        }
        /* 三线程 end */

        /* 四线程 */
        gettimeofday (&t1, NULL);
        pthread_create (&tid4[0], NULL, (void *)fourThread1, NULL);
        pthread_join (tid4[0], NULL);
        pthread_create (&tid4[1], NULL, (void *)fourThread2, NULL);
        pthread_join (tid4[1], NULL);
        pthread_create (&tid4[2], NULL, (void *)fourThread3, NULL);
        pthread_join (tid4[2], NULL);
        pthread_create (&tid4[3], NULL, (void *)fourThread4, NULL);
        pthread_join (tid4[3], NULL);

        gettimeofday (&t2, NULL);
        timeuse = t2.tv_sec - t1.tv_sec + (t2.tv_usec - t1.tv_usec)/1000000.0;

#ifdef LOG
        printf ("四线程结果矩阵: \n");
        for (i = 0; i < size; i++){
            for (j = 0; j < size; j++){
                printf ("%d ", result4[i * size + j]);
            }
            printf ("\n");
        }
#endif

        printf("四线程耗时: %fs\n", timeuse);
        sprintf (timeChars, "%lf\n", timeuse);
        write (fd, timeChars, strlen (timeChars));

        for (i = 0; i < size; I++)
        for (j = 0; j < size; j++){
            result[i * size + j] = 0;
            result2[i * size + j] = 0;
            result3[i * size + j] = 0;
            result4[i * size + j] = 0;
            /*
            result[i][j] = 0;
            result2[i][j] = 0;
            result3[i][j] = 0;
            result4[i][j] = 0;
            */
        }
        /* 四线程 end */
    }

    // 释放空间
    free (A);
    free (B);
    free (result);
    free (result2);
    free (result3);
    free (result4);
    A = NULL; 
    B = NULL;
    result = NULL;
    result2 = NULL;
    result3 = NULL;
    result4 = NULL;

    // 关闭文件
    close (fd);

    return 0;
}

cuda部分的代码直接贴出来, 解析可以看之前的文章. 其实就是为每一次的乘法开一个线程, 这在CPU中是无法想象的, 如此多的线程, 开销太大. 但是在GPU中, 这里运行速度可以用快得无法想象来形容.

#include 
#include 
#include 
#include 

#define w 8000

struct Matrix
{
    int width;
    int height;
    float *elements;
};

__device__ float getElement(Matrix *A, int row, int col)
{
        return A->elements[row * A->width + col];
}

__device__ void setElement(Matrix *A, int row, int col, float value)
{
        A->elements[row * A->width + col] = value;
}

__global__ void matMulKernel(Matrix *A, Matrix *B, Matrix *C)
{
        float Cvalue = 0.0;
        int row = threadIdx.y + blockIdx.y * blockDim.y;
        int col = threadIdx.x + blockIdx.x * blockDim.x;
        
        for (int i = 0; i < A->width; ++i)
        {
                Cvalue += getElement(A, row, i) * getElement(B, i, col);
        }
        setElement(C, row, col, Cvalue);
}

int main()
{
    int width = w;
    int height = w;

    Matrix *A, *B, *C;

    cudaMallocManaged((void**)&A, sizeof(Matrix));
    cudaMallocManaged((void**)&B, sizeof(Matrix));
    cudaMallocManaged((void**)&C, sizeof(Matrix));

    int nBytes = width * height * sizeof(float);

    cudaMallocManaged((void**)&A->elements, nBytes);
    cudaMallocManaged((void**)&B->elements, nBytes);
    cudaMallocManaged((void**)&C->elements, nBytes);

    A->height = height;
    A->width = width;
    B->height = height;
    B->width = width;
    C->height = height;
    C->width = width;

    for (int i = 0; i < width * height; ++i)
    {
        A->elements[i] = 1.0;
        B->elements[i] = 2.0;
    }

    dim3 blockSize(32, 32);
    dim3 gridSize((width + blockSize.x - 1) / blockSize.x,
        (height + blockSize.y - 1) / blockSize.y);

    struct timeval t1,t2;
    gettimeofday(&t1,NULL);
    double timeuse;

    matMulKernel << < gridSize, blockSize >> >(A, B, C);

    cudaDeviceSynchronize();

    gettimeofday(&t2,NULL);
    timeuse = t2.tv_sec - t1.tv_sec + (t2.tv_usec - t1.tv_usec)/1000000.0;
    printf("Use Time:%fs\n", timeuse);

    return 0;
}

来看下结果图, 首先是CPU的. 8000阶数的算了一两个小时, 这里可以看到, 多线程在阶数增长起来之后, 双线程有时比单线程耗时要少, 而四线程也有时耗时要少于三线程.

CUDA编程(四): CPU与GPU的矩阵乘法对比_第1张图片
CPU多线程

然后是GPU的, 一般情况下45s左右, 快的时候可以10s就算完, 真可谓是如斯恐怖:

CUDA编程(四): CPU与GPU的矩阵乘法对比_第2张图片
GPU多线程

gpu是gt750m, cpu是i7-4700mq. 其实cpu是比gpu好很多的, 但是并行计算上gpu的优势依旧明显.


最后

喜欢记得点赞哦, 有意见或者建议评论区见~


你可能感兴趣的:(CUDA编程(四): CPU与GPU的矩阵乘法对比)