CUDA-矩阵乘法实现(含提速对比,适合上手)

作者:翟天保Steven
版权声明:著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处

GPU一定比CPU快吗?

       不一定。CUDA是一种用于并行计算的平台和编程模型,可以在NVIDIA的GPU上进行高效的并行计算。在某些特定的计算任务上,使用CUDA进行GPU计算可能比使用CPU更快,特别是对于涉及大量数据并需要并行处理的任务。GPU在处理大规模数据集和并行计算方面通常具有优势。

       然而,并不是所有的任务都适合在GPU上执行。有些任务可能更适合在CPU上执行,特别是涉及到频繁的控制流程、内存访问模式不规则或数据量较小的情况下。此外,GPU的性能还受到算法的优化程度和硬件特性的影响。

       下文将通过数组加法和矩阵乘法这两种基本算法来进行对比。

配置与代码

1)配置CUDA,教程如下:

CUDA下载安装与配置-CSDN博客

2)创建一个空项目(假设为Project1),创建一个main.cpp,右键项目->生成依赖项->生成自定义,选择CUDA(自己的版本)。

3)打开项目属性,CUDA C/C++就出来了,选平台选项,我是64位。这样就CUDA配置完成了。

4)头文件创建Add.cuh和Mul.cuh,源文件创建Add.cu和Mul.cu,将如下代码分别输入。__global__的意思就是用设备(GPU)运行。然后把这四个文件右键->属性->项类型,改为CUDA C/C++。

Add.cuh

#ifndef ADD_H
#define ADD_H

void vectorAddCUDA(const float* a, const float* b, float* c, int size);

#endif // ADD_H

Add.cu

#include <cuda_runtime.h>
#include "Add.cuh"

__global__ void vectorAddGPU(const float* a, const float* b, float* c, int size) 
{
	int idx = blockIdx.x * blockDim.x + threadIdx.x;
	if (idx < size) 
	{
		c[idx] = a[idx] + b[idx];
	}
}

void vectorAddCUDA(const float* a, const float* b, float* c, int size) 
{
	float *d_a, *d_b, *d_c;
	cudaMalloc((void**)&d_a, size * sizeof(float));
	cudaMalloc((void**)&d_b, size * sizeof(float));
	cudaMalloc((void**)&d_c, size * sizeof(float));

	cudaMemcpy(d_a, a, size * sizeof(float), cudaMemcpyHostToDevice);
	cudaMemcpy(d_b, b, size * sizeof(float), cudaMemcpyHostToDevice);

	dim3 threadsPerBlock(256);
	dim3 blocksPerGrid((size + threadsPerBlock.x - 1) / threadsPerBlock.x);
	vectorAddGPU << <blocksPerGrid, threadsPerBlock >> > (d_a, d_b, d_c, size);

	cudaMemcpy(c, d_c, size * sizeof(float), cudaMemcpyDeviceToHost);

	cudaFree(d_a);
	cudaFree(d_b);
	cudaFree(d_c);
}

Mul.cuh

#ifndef MUL_H
#define MUL_H

void vectorMulCUDA(const float* a, const float* b, float* c, int N);

#endif // MUL_H

Mul.cu

#include <vector>
#include <cuda_runtime.h>
#include "Mul.cuh"

__global__ void vectorMulGPU(const float* a, const float* b, float* c, int size) 
{
	int row = blockIdx.y * blockDim.y + threadIdx.y;
	int col = blockIdx.x * blockDim.x + threadIdx.x;

	if (row < size && col < size) 
	{
		float sum = 0.0f;
		for (int k = 0; k < size; ++k) 
		{
			sum += a[row * size + k] * b[k * size + col];
		}
		c[row * size + col] = sum;
	}
}

void vectorMulCUDA(const float* a, const float* b, float* c, int size) 
{
	float *d_a, *d_b, *d_c;
	cudaMalloc((void**)&d_a, size * size * sizeof(float));
	cudaMalloc((void**)&d_b, size * size * sizeof(float));
	cudaMalloc((void**)&d_c, size * size * sizeof(float));

	cudaMemcpy(d_a, a, size * size * sizeof(float), cudaMemcpyHostToDevice);
	cudaMemcpy(d_b, b, size * size * sizeof(float), cudaMemcpyHostToDevice);

	dim3 threadsPerBlock(16, 16);
	dim3 blocksPerGrid((size + threadsPerBlock.x - 1) / threadsPerBlock.x, (size + threadsPerBlock.y - 1) / threadsPerBlock.y);
	vectorMulGPU << <blocksPerGrid, threadsPerBlock >> > (d_a, d_b, d_c, size);

	cudaMemcpy(c, d_c, size * size * sizeof(float), cudaMemcpyDeviceToHost);

	cudaFree(d_a);
	cudaFree(d_b);
	cudaFree(d_c);
}

5)在main.cpp中加上CPU测试代码,并调用GPU进行对比。

#include <iostream>
#include <vector>
#include <time.h>
#include "Add.cuh"
#include "Mul.cuh"

using namespace std;

void vectorAddCPU(const float* a, const float* b, float* c, int size)
{
	for (int i = 0; i < size; ++i)
	{
		c[i] = a[i] + b[i];
	}
}

void vectorMulCPU(const std::vector<float>& a, const std::vector<float>& b, std::vector<float>& c, int size)
{
#pragma omp parallel for
	for (int i = 0; i < size; ++i)
	{
		for (int j = 0; j < size; ++j)
		{
			float sum = 0.0f;
			for (int k = 0; k < size; ++k)
			{
				sum += a[i * size + k] * b[k * size + j];
			}
			c[i * size + j] = sum;
		}
	}
}

int main() 
{
	// 加法测试
	const int size = 1000000;
	std::vector<float> a(size, 1.0f);
	std::vector<float> b(size, 2.0f);
	std::vector<float> c(size);
	// CPU加法
	clock_t sc1, ec1;
	sc1 = clock();
	vectorAddCPU(a.data(), b.data(), c.data(), size);
	ec1 = clock();
	cout << "add CPU time:" << float(ec1 - sc1) / 1000 << endl;
	// GPU加法
	clock_t sg1, eg1;
	sg1 = clock();
	vectorAddCUDA(a.data(), b.data(), c.data(), size);
	eg1 = clock();
	cout << "add GPU time:" << float(eg1 - sg1) / 1000 << endl;
	// 检查错误
	for (int i = 0; i < size; ++i) 
	{
		if (c[i] != a[i] + b[i]) 
		{
			std::cerr << "Add Error: Incorrect result at index " << i << std::endl;
			return 1;
		}
	}
	std::cout << "Add successful!" << std::endl;

	// 乘法测试
	const int N = 1024; // 矩阵大小
	std::vector<float> a1(N * N, 1.0f);
	std::vector<float> b1(N * N, 2.0f);
	std::vector<float> c1(N * N, 0.0f);
	std::vector<float> a2(N * N, 1.0f);
	std::vector<float> b2(N * N, 2.0f);
	std::vector<float> c2(N * N, 0.0f);
	// CPU矩阵乘法
	clock_t sc2, ec2;
	sc2 = clock();
	vectorMulCPU(a1, b1, c1, N);
	ec2 = clock();
	cout << "mul CPU time:" << float(ec2 - sc2) / 1000 << endl;
	// GPU矩阵乘法
	clock_t sg2, eg2;
	sg2 = clock();
	vectorMulCUDA(a2.data(), b2.data(), c2.data(), N);
	eg2 = clock();
	cout << "mul GPU time:" << float(eg2 - sg2) / 1000 << endl;
	// 检查结果
	for (int i = 0; i < N * N; ++i) 
	{
		if (c1[i] != c2[i]) 
		{
			std::cerr << "Mul Error: Incorrect result at index " << i << std::endl;
			return 1;
		}
	}
	std::cout << "Mul successful!" << std::endl;
	return 0;
}

测试效果

       从结果中可以看出,针对一维数据的简单加法,如果用GPU反而速度慢了,而在矩阵乘法中可以很好地发挥CUDA并行策略,速度很快。针对加法速度慢的情况,我个人猜测是如下几个原因:

  1. 内存访问模式:CUDA的性能高度依赖于内存访问模式。一维数据的加法可能会导致不连续的内存访问,从而增加了访存延迟,降低了性能。

  2. 线程同步:在一维数据加法中,可能无法充分利用线程级并行性,因为每个线程可能需要等待其他线程完成其任务。

  3. 任务太小:在GPU上启动计算任务的开销可能会比实际的计算时间还要大,特别是对于一维数据的简单操作而言。

  4. 未充分利用GPU资源:一维数据加法可能无法充分利用GPU的计算资源,因为它不能很好地分解成适合GPU并行处理的任务。

       本文提供的矩阵乘法和数据加法这两个函数是非常基础的,很适合初学CUDA的同学用来上手,我也初学,有经验的朋友可以多多指点。

       如果文章帮助到你了,可以点个赞让我知道,我会很快乐~加油!

  • 17
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

翟天保Steven

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值