高性能计算-GPU单进程多卡(多流)并行计算编程模型示例(25)

news/2025/2/21 10:17:47/文章来源:https://www.cnblogs.com/anluo8/p/18665438

1. 简介

(1) 使用CPU对向量点乘进行串行计算
(2) 对数据进行分块,使用单进程多卡(多流)并行计算
(3) 使用不同数据规模,比较加速比的变化

2. 代码

#include <stdio.h>
#include <sys/time.h>
#include <stdlib.h>#define CUDA_ERROR_CHECKint nGpus = 1;                  //gpu数量
int blockSize = 256;            //线程块大小
int leftBit = 10;               //数据规模左移位数
unsigned long nSize = 1LL << leftBit;    //方阵维度
float *hostA = NULL;            //向量 A
float *hostB = NULL;            //向量 B
float *hostResult = NULL;       //串行计算结果
float *deviceResult = NULL;      //gpu计算结果//宏定义检查API调用是否出错
#define CudaCall(err) __cudaSafeCall(err,__FILE__,__LINE__)
inline void __cudaSafeCall(cudaError_t err,const char* file,const int line)
{#ifdef CUDA_ERROR_CHECKif(err!=cudaSuccess){fprintf(stderr,"cudaSafeCall failed at %s:%d :(%d) %s\n",file,line,err,cudaGetErrorString(err));exit(-1);}#endif
}//宏定义检查获取流中的执行错误,主要是对核函数
#define CudaCheck() _cudaCheckError(__FILE__,__LINE__)
inline void _cudaCheckError(const char * file,const int line)
{#ifdef CUDA_ERROR_CHECKcudaError_t err = cudaGetLastError();if(err != cudaSuccess){fprintf(stderr,"cudaCheckError failed at %s:%d :(%d) %s\n",file,line,err,cudaGetErrorString(err));exit(-1);}#endif
}//ms
long getTime()
{struct timeval cur;gettimeofday(&cur, NULL);// printf("sec %ld usec %ld,toal ms %ld\n",cur.tv_sec,cur.tv_usec,cur.tv_sec*1e3 + cur.tv_usec / 1e3);return cur.tv_sec*1e3 + cur.tv_usec / 1e3;
}void initData(float *A,float *B,unsigned long len)
{//设置随机数种子srand(0);// len = 10;for(unsigned long i=0;i<len;i++){A[i] = (float)rand()/RAND_MAX;B[i] = (float)rand()/RAND_MAX;// printf("%f %f\n",A[i],B[i]);}
}//cpu 串行计算
long serial(unsigned long len)
{long start = getTime();for(unsigned long i=0;i<len;i++)hostResult[i] = hostA[i] * hostB[i];long end = getTime();// printf("cpu time %d\n",end-start);return end-start;
}__global__ void kernel(float *A,float *B,float *result,unsigned long len)
{unsigned long id = blockIdx.x * blockDim.x + threadIdx.x;if(id<len)result[id] = A[id] * B[id];
}//gpu多卡并行
float gpu_multi(float *result,unsigned long len,int ngpus)
{float gpuTime = 0.0;//对数据分块,每个gpu上开辟内存空间存储数据,并创建一个流,每个GPU计算自己的数据//每个流GPU处理的数据个数unsigned long nPerGpu = len/ngpus;float **deviceA,**deviceB,**deviceResult;deviceA = (float**)calloc(ngpus,sizeof(float*));deviceB = (float**)calloc(ngpus,sizeof(float*));deviceResult = (float**)calloc(ngpus,sizeof(float*));cudaStream_t *streams = (cudaStream_t*)calloc(ngpus,sizeof(cudaStream_t));//在gpu上分配内存空间for(int i=0;i<ngpus;i++){CudaCall(cudaSetDevice(i));CudaCall(cudaMalloc((void**)&deviceA[i],nPerGpu*sizeof(float)));CudaCall(cudaMalloc((void**)&deviceB[i],nPerGpu*sizeof(float)));CudaCall(cudaMalloc((void**)&deviceResult[i],nPerGpu*sizeof(float)));CudaCall(cudaStreamCreate(streams+i));}//事件记录在默认流cudaEvent_t start,end;CudaCall(cudaSetDevice(0));CudaCall(cudaEventCreate(&start));CudaCall(cudaEventCreate(&end));CudaCall(cudaEventRecord(start,streams[0]));for(int i=0;i<ngpus;i++){CudaCall(cudaSetDevice(i));//异步数据拷贝CudaCall(cudaMemcpyAsync(deviceA[i],hostA+i*nPerGpu,nPerGpu*sizeof(float),cudaMemcpyHostToDevice,streams[i]));CudaCall(cudaMemcpyAsync(deviceB[i],hostB+i*nPerGpu,nPerGpu*sizeof(float),cudaMemcpyHostToDevice,streams[i]));//计算int gridDim = (nPerGpu-1)/blockSize + 1;kernel<<<gridDim,blockSize,0,streams[i]>>>(deviceA[i],deviceB[i],deviceResult[i],nPerGpu);CudaCheck();//异步拷贝数据CudaCall(cudaMemcpyAsync(result+i*nPerGpu,deviceResult[i],nPerGpu*sizeof(float),cudaMemcpyDeviceToHost,streams[i]));}CudaCall(cudaSetDevice(0));CudaCall(cudaEventRecord(end,streams[0]));//流同步for(int i=0;i<ngpus;i++){CudaCall(cudaSetDevice(i));CudaCall(cudaStreamSynchronize(streams[i]));}// CudaCall(cudaEventSynchronize(end));CudaCall(cudaEventElapsedTime(&gpuTime,start,end));//freeCudaCall(cudaEventDestroy(start));CudaCall(cudaEventDestroy(end));for(int i=0;i<ngpus;i++){CudaCall(cudaSetDevice(i));CudaCall(cudaFree(deviceA[i]));CudaCall(cudaFree(deviceB[i]));CudaCall(cudaFree(deviceResult[i]));CudaCall(cudaStreamDestroy(streams[i]));}cudaFree(deviceA);cudaFree(deviceB);cudaFree(deviceResult);free(streams);// printf("gpu time %f\n",gpuTime);return gpuTime;
}int main(int argc, char* argv[])
{cudaDeviceProp prop;int globalMemSize = 0;int memSize = 0;    //对单卡显存需求大小CudaCall(cudaGetDeviceProperties(&prop ,0));globalMemSize = (float)prop.totalGlobalMem/1024/1024;// printf("compute capability %d.%d\n", prop.major,prop.minor);//k80 3.7// printf("Memory clock rate: %d\n",prop.memoryClockRate);// printf("global memory:%dMB\n",globalMemSize);//获得 device 数量CudaCall(cudaGetDeviceCount(&nGpus));//限制参数设置的最大gpu数量if(argc==3){leftBit = atoi(argv[2]);nSize = 1LL << leftBit;int n = atoi(argv[1]);//当gpu数量设置为3时,nSize%n !=0,使用最大gpu数量计算nGpus = ((n > nGpus || nSize%n !=0)?nGpus:n);memSize = nSize*sizeof(float)*3/nGpus/1024/1024;//判断显存是否够用,k80 单卡可用显存为 11441MBif(memSize > globalMemSize){printf("one gpu memory not enough gater %dMB\n",globalMemSize);exit(-1);}}else{printf("parameter 1:ngpus 2:matrix dim 2^(_)\n");exit(-1);}unsigned long nBytes = nSize * sizeof(float);   //单个向量字节数//数据初始化,开辟主机锁页内存// hostA = (float*)calloc(nSize,sizeof(float));// hostB = (float*)calloc(nSize,sizeof(float));// hostResult = (float*)calloc(nSize,sizeof(float));CudaCall(cudaMallocHost((void**)&hostA,nBytes));CudaCall(cudaMallocHost((void**)&hostB,nBytes));CudaCall(cudaMallocHost((void**)&hostResult,nBytes));CudaCall(cudaMallocHost((void**)&deviceResult,nBytes));initData(hostA,hostB,nSize);//串行计算long cpuTime = serial(nSize);//多GPU计算float gpuTime = gpu_multi(deviceResult,nSize,nGpus);printf("单个向量长度 2^%ld,单个显卡三个数组需要显存 %dMB,使用 %d个GPU,cpu串行耗时 %ldms,GPU并行数据传输和计算耗时 %fms,加速比: %f\n",\leftBit,memSize,nGpus,cpuTime,gpuTime,cpuTime/gpuTime);cudaFreeHost(hostA);cudaFreeHost(hostB);cudaFreeHost(hostResult);cudaFreeHost(deviceResult);return 0;
}

3. 测试脚本

#!/bin/bash
# 编译
nvcc pointMul.cu -o pointMul
dir=out
# 清空文件夹
> "$dir"echo "start $(date)" >> out# 串行计算
# for((i=0;i<4;i++)); do
#     yhrun -N1 -n1 -pTH_GPU ./matrix_add2D 0 | tee -a "$dir"
# done# 显卡数量
nGpus=(1 2 3 4)   
# 数据规模 2^(S)
S=(24 28 30 31)# gpu
for n in "${nGpus[@]}"; dofor s in "${S[@]}"; dofor((i=0;i<3;i++)); doyhrun -N1 -n1 -pTH_GPU ./pointMul "$n" "$s" | tee -a "$dir"donedone
doneecho "end $(date)" >> out

4. 测试数据

由于测试脚本的限制,CPU串行计算在GPU单卡(K80 12G显存)、双卡、四卡测试中分别跑了一轮,数据如下:

数据长度 gpu单卡(ms) gpu2个卡(ms) gpu4个卡(ms)
2^24 44.3 44.7 44
2^28 719.7 717.3 703.3
2^30 - 2988.7 2951.7
2^31 - - 5906.7

GPU测试耗时及加速比数据:

数据长度 gpu单卡(ms) gpu2个卡(ms) gpu4个卡(ms)
2^24(耗时/加速比) 27.9/1.6 17.7/2.5 17.1/2.6
2^28(耗时/加速比) 399.8/1.8 273.6/2.6 273.9/2.6
2^30(耗时/加速比) 显存不足 985/3.0 1056.3/2.8
2^31(耗时/加速比) 显存不足 显存不足 1942.4/3.0

5. 结果分析

(1)GPU比CPU计算有明显的性能提升,根据数据规模,数据量越大提升越明显。
(2)GPU数量越多,计算效率提升越高,数据规模越大,提升越明显。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/886764.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

亲测可用,IDEA中使用满血版DeepSeek R1!支持深度思考!免费!免配置!

之前介绍过在IDEA中使用DeepSeek的方案,但是很多人表示还是用的不够爽,比如用CodeChat的方案,只支持V3版本,不支持带推理的R1。想要配置R1的话有特别的麻烦。作者:程序员 Hollis之前介绍过在IDEA中使用DeepSeek的方案,但是很多人表示还是用的不够爽,比如用CodeChat的方案…

Vscode中Eigen库的导入问题

Vscode中Eigen库的导入问题.Vscode中Eigen库的导入问题 在Vscode中,C++外部库(这里为Eigen)的导入问题主要有库的zip文件下载、在c_cpp_properties.json和tasks.json文件中进行路径配置。另外,如果运行代码使用Code Runner插件,还需要在Code Runner中进行路径配置(即使在…

最新更新!扣子(Coze)接入地表最强DeepSeek-R1大模型,超全攻略,手把手教学,完全免费教程

‍ 最新消息,国产地表最强大模型可以接入Coze平台了,今天斜杠君为大家带来了最细接入攻略,大家快学起来吧~ 备注:需要登录专业版火上引擎接入,开通专业版的同学需要开通一下。 接下来,话不多说,斜杠君用最简单的方式教给大家。 大家可以关注收藏,以免之后找不到,而且也…

1.如何在python中安装playwright

1.如何在python中安装playwright 打开pycharm,进入终端,输入如下的2个命令行代码即可自动完成playwright的安装 pip install playwright ——》在python中安装playwright第三方模块 playwright install ——》安装playwright所需的工具插件和所支持的浏览器 看到这里,是否想…

最新扣子(Coze)案例教程:DeepSeek 图像生成,用扣子应用打造超萌表情包生成器,手把手教学,完全免费教程

上一篇文章和大家分享了如何把DeepSeek-R1接入到扣子智能体中进行使用,这篇教程让我们来应用一下DeepSeek,使用DeepSeek结合工作流中的图像生成节点,打造一个表情包生成器的应用。 应用作用:输入一个人物或动物主题,生成一组表情包。 首先我们来看一下生成后的效果: 图像…

我悟了!原来本地图片预览还能这样搞

在网页开发中,经常会遇到需要让用户上传图片并在上传前进行预览的需求。这样做的好处显而易见:用户可以立即看到自己选择的图片是否正确,避免了不必要的上传和服务器资源浪费,提升了用户体验。Hey, 我是 Immerse 本文首发于 【沉浸式趣谈】,我的个人博客 https://yaolifen…

octave画高通滤波、超前,滞后补偿器的幅频响应图

octave代码非常简单:pkg load control s=tf(s); k=0.5; sysG1=k*(0.005*s)/(0.005*s+1); sysG2=k*(0.8*s+1)/(0.1*s+1); sysG3=k*(s+1)/(5*s+1); figure bode(sysG1) figure bode(sysG2) figure bode(sysG3)也可以借助循环,看如下代码:1 pkg load control2 s=tf(s);3 k=0.5;…

frame切换/窗口切换

frame切换/窗口切换 切换到frame点击这里,边看视频讲解,边学习以下内容 请大家点击这里,打开这个链接 如果我们要 选择 下图方框中 所有的 蔬菜,使用css选择,怎么写表达式? 当然,要先查看到它们的html元素特征大家可能会照旧写出如下代码:from selenium import webdriv…

选择元素的基本方法

选择元素的基本方法点击这里,边看视频讲解,边学习以下内容对于百度搜索页面,如果我们想自动化输入 白月黑羽 ,怎么做呢? 这就是在网页中,操控界面元素。 web界面自动化,要操控元素,首先需要 选择 界面元素 ,或者说 定位 界面元素 就是 先告诉浏览器,你要操作 哪个 …

操控元素的基本方法

操控元素的基本方法 点击这里,边看视频讲解,边学习以下内容选择到元素之后,我们的代码会返回元素对应的 WebElement对象,通过这个对象,我们就可以 操控 元素了。 操控元素通常包括 点击元素在元素中输入字符串,通常是对输入框这样的元素获取元素包含的信息,比如文本内容…

CClink IEF Basic设备数据 转EthernetIP项目案例

VFBOX协议转换网关支持PLC,modbus,EthernetIP,Profinet,CCLink,EtherCAT,IEC61850,IEC104,bacnet,DLT645,HJ212,opc ua,opc da,DNP3。目录 1 案例说明 1 2 VFBOX网关工作原理 1 3 准备工作 2 4 网关采集CCLINK IEF BASIC数据 2 5 使用ETHERNETIP转发数据 5 6 案例…

SciTech-EECS-BigDataAIML-NN(神经网络): Forward NN(前向传播算法)

SciTech-EECS-BigDataAIML-NN(神经网络): Forward NN(前向传播算法)