CUDA C编程:第一个程序 向量相加点积

2024-05-07 15:20

本文主要是介绍CUDA C编程:第一个程序 向量相加点积,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

我的电脑没有装CUDA,所以使用租了带GPU的云服务器,然后使用vscode SSH远程连接云服务器。云GPU使用的是智星云,0.8元/h。

智星云

可以使用nvcc --version查看系统中安装的CUDA版本。

然后写第一个CUDA程序,两个向量相加结果给到第三个向量

#include <cuda_runtime.h>
#include <iostream>#define CHECK(call) \
{ \const cudaError_t error = call; \if (error != cudaSuccess) { \std::cerr << "Error: " << __FILE__ << ", line " << __LINE__ << ": " \<< cudaGetErrorString(error) << std::endl; \exit(1); \} \
}__global__ void addArrays(const int *A, const int *B, int *C, int N) {int idx = blockIdx.x * blockDim.x + threadIdx.x;if (idx < N)C[idx] = A[idx] + B[idx];
}int main() {const int N = 100; // 数组大小int A[N], B[N], C[N];// 初始化数组A和Bfor(int i = 0; i < N; ++i) {A[i] = i;B[i] = i * 2;}int *d_A, *d_B, *d_C;// 分配GPU内存CHECK(cudaMalloc((void**)&d_A, N * sizeof(int)));CHECK(cudaMalloc((void**)&d_B, N * sizeof(int)));CHECK(cudaMalloc((void**)&d_C, N * sizeof(int)));// 将数据从主机复制到设备CHECK(cudaMemcpy(d_A, A, N * sizeof(int), cudaMemcpyHostToDevice));CHECK(cudaMemcpy(d_B, B, N * sizeof(int), cudaMemcpyHostToDevice));// 调用核函数addArrays<<<10, 10>>>(d_A, d_B, d_C, N);// 同步以确保核函数执行完成cudaDeviceSynchronize();// 将结果从设备复制回主机CHECK(cudaMemcpy(C, d_C, N * sizeof(int), cudaMemcpyDeviceToHost));// 释放GPU内存CHECK(cudaFree(d_A));CHECK(cudaFree(d_B));CHECK(cudaFree(d_C));// 输出结果for(int i = 0; i < N; ++i)std::cout << C[i] << " "; // 应该输出 i + i*2return 0;
}

nvcc -o add add.cu编译程序

./add运行程序 

程序说明

#include <cuda_runtime.h>

引入cuda运行时环境

#define CHECK(call) \
{ \const cudaError_t error = call; \if (error != cudaSuccess) { \std::cerr << "Error: " << __FILE__ << ", line " << __LINE__ << ": " \<< cudaGetErrorString(error) << std::endl; \exit(1); \} \
}

用来提供CUDA报错信息的宏,用CHECK宏嵌套每一个将要调用的函数,便于调试。

#define CHECK(call) 定义了一个名为CHECK的宏,它接受一个参数call,这个参数是想检查的CUDA API调用。接下来的花括号 { ... } 包围了宏展开后将要执行的代码块。

const cudaError_t error=call;执行传入的CUDA API调用(即call),并将其返回的错误状态保存在变量error中。

if(error!=cudaSuccess){...}:检查error是否等于cudaSuccess,这是CUDA中表示操作成功的常量。如果不等于(即操作失败),则执行大括号内的错误处理代码。

std::cerr<< "Error: " <<__FILE__<<", line "<<__LINE__<<": "<<cudaGetErrorString(error)<< std::endl; 这行代码打印错误信息到标准错误输出。包括了出错的文件名(由__FILE__宏提供)、行号(由__LINE__宏提供),以及通过cudaGetErrorString(error)获取的错误描述字符串。exit(1); 如果确实发生了错误,程序会调用exit(1)立即终止,返回码1通常表示异常终止。

__global__ void addArrays(const int *A, const int *B, int *C, int N) {int idx = blockIdx.x * blockDim.x + threadIdx.x;if (idx < N)C[idx] = A[idx] + B[idx];
}

__global__ 是一个关键字,用于声明一个在GPU上执行的函数,也称为全局函数或内核函数。这些函数由主机(CPU)调用,但在设备(GPU)上的多个线程并行执行。

void addArrays(const int *A,const int *B,int *C, int N)定义了内核函数addArrays。

const int *A 和 const int *B指向输入数组A和B的指针,在内核中只读。

int *C输出数组C的指针,存放A和B对应元素的和。

N:需要相加的元素个数。

int idx = blockIdx.x * blockDim.x + threadIdx.x; 计算当前线程的全局索引 idx。

这里是CUDA线程组织方式的一个体现:

blockIdx.x 是当前线程所在的块(block)在网格(grid)中的x轴索引。

blockDim.x 是每个块中线程的数量(块的尺寸)在x轴方向。

threadIdx.x 是当前线程在块内的x轴索引。 通过这样的计算,每个线程都能知道自己在整个计算任务中的唯一位置,从而决定应该处理哪个数组元素。

if (idx < N) 是一个边界检查,确保线程不会访问超过数组界限。因为CUDA会为整个网格启动比实际需要更多的线程以充分利用硬件资源,所以这种检查是必要的。

C[idx] = A[idx] + B[idx]; 如果索引idx在有效范围内,这个语句就执行数组A和B中相应位置的元素相加,并将结果存储到数组C的相同位置。

(这个地方还是没怎么看懂)。

cudaMalloc((void**)&d_A, N * sizeof(int))

给设备分配N个int类型的内存,使用指针变量d_A指示。

cudaMemcpy(d_A, A, N * sizeof(int), cudaMemcpyHostToDevice)

内存拷贝,从Host拷贝到Device。A数组赋值给d_A数组。

计算两个数组的点积

理解CUDA内核调用中的<<< >>>语法。

向量点积计算伪代码

function dotProduct(A, B, N):// 初始化点积结果为0dotProductResult := 0// 遍历两个向量的每个元素并相乘累加for i from 0 to N-1 dodotProductResult := dotProductResult + (A[i] * B[i])

假设有一个简单的CUDA内核函数,用于计算两个数组的点积,并将结果存储在一个变量中。

__global__ void dotProductKernel(const float* A, const float* B, 
float* result, int N) {extern __shared__ float partialSums[];unsigned int tid = threadIdx.x;unsigned int i = blockIdx.x * blockDim.x + threadIdx.x;float sum = 0.0f;if (i < N) {for (unsigned int j = 0; j < N; j++) {sum += A[j * blockDim.x + tid] * B[j * blockDim.x + tid];}}partialSums[tid] = sum;__syncthreads(); // 确保所有线程完成上面的计算// 如果是块内的第一个线程,则累加所有部分和if (tid == 0) {for (unsigned int i = 0; i < blockDim.x; i++) {*result += partialSums[i];}}
}

在这个内核中,我们想要计算两个长度为N的一维数组A和B的点积。为了简化说明,我们忽略了一些优化(如减少共享内存的银行冲突),专注于展示如何调用这个内核。
现在,让我们看看如何使用<<< >>>来调用这个内核函数,并配置执行环境:
 

int main() {const int N = 1024; // 假设数组长度为1024const int blockSize = 256; // 每个块包含256个线程const int gridSize = (N + blockSize - 1) / blockSize; // 计算所需块的数量float *d_A, *d_B, *d_result;float h_result = 0.0f;float h_A[N], h_B[N]; // 主机端的数组// 初始化h_A和h_B数组,略...// 分配和复制数据到GPU,略...// 调用内核函数,注意 <<<gridSize, blockSize, sizeof(float)*blockSize>>> 的用法dotProductKernel<<<gridSize, blockSize, blockSize * sizeof(float)>>>(d_A, d_B, &h_result, N);// 将结果从GPU复制回CPU,略...// 释放GPU资源,略...return 0;
}

dotProductKernel<<<gridSize, blockSize, blockSize * sizeof(float)>>>(d_A, d_B, &h_result, N);是内核调用的实例。

gridSize和blockSize分别定义了执行该内核的网格和块的大小。

gridSize = 4因为1024个元素,每个块处理256个,共需要4个块和blockSize = 256。

第三个参数blockSize * sizeof(float)指定每个块需要的共享内存大小,这里每个线程计算一个部分和,然后存入共享内存,所以我们需要为每个块分配足够大的共享内存来存储这些部分和。

这篇关于CUDA C编程:第一个程序 向量相加点积的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/967725

相关文章

基于Python开发PDF转Doc格式小程序

《基于Python开发PDF转Doc格式小程序》这篇文章主要为大家详细介绍了如何基于Python开发PDF转Doc格式小程序,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 用python实现PDF转Doc格式小程序以下是一个使用Python实现PDF转DOC格式的GUI程序,采用T

将java程序打包成可执行文件的实现方式

《将java程序打包成可执行文件的实现方式》本文介绍了将Java程序打包成可执行文件的三种方法:手动打包(将编译后的代码及JRE运行环境一起打包),使用第三方打包工具(如Launch4j)和JDK自带... 目录1.问题提出2.如何将Java程序打包成可执行文件2.1将编译后的代码及jre运行环境一起打包2

在不同系统间迁移Python程序的方法与教程

《在不同系统间迁移Python程序的方法与教程》本文介绍了几种将Windows上编写的Python程序迁移到Linux服务器上的方法,包括使用虚拟环境和依赖冻结、容器化技术(如Docker)、使用An... 目录使用虚拟环境和依赖冻结1. 创建虚拟环境2. 冻结依赖使用容器化技术(如 docker)1. 创

C#多线程编程中导致死锁的常见陷阱和避免方法

《C#多线程编程中导致死锁的常见陷阱和避免方法》在C#多线程编程中,死锁(Deadlock)是一种常见的、令人头疼的错误,死锁通常发生在多个线程试图获取多个资源的锁时,导致相互等待对方释放资源,最终形... 目录引言1. 什么是死锁?死锁的典型条件:2. 导致死锁的常见原因2.1 锁的顺序问题错误示例:不同

PyCharm接入DeepSeek实现AI编程的操作流程

《PyCharm接入DeepSeek实现AI编程的操作流程》DeepSeek是一家专注于人工智能技术研发的公司,致力于开发高性能、低成本的AI模型,接下来,我们把DeepSeek接入到PyCharm中... 目录引言效果演示创建API key在PyCharm中下载Continue插件配置Continue引言

C#反射编程之GetConstructor()方法解读

《C#反射编程之GetConstructor()方法解读》C#中Type类的GetConstructor()方法用于获取指定类型的构造函数,该方法有多个重载版本,可以根据不同的参数获取不同特性的构造函... 目录C# GetConstructor()方法有4个重载以GetConstructor(Type[]

好题——hdu2522(小数问题:求1/n的第一个循环节)

好喜欢这题,第一次做小数问题,一开始真心没思路,然后参考了网上的一些资料。 知识点***********************************无限不循环小数即无理数,不能写作两整数之比*****************************(一开始没想到,小学没学好) 此题1/n肯定是一个有限循环小数,了解这些后就能做此题了。 按照除法的机制,用一个函数表示出来就可以了,代码如下

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi

Linux 网络编程 --- 应用层

一、自定义协议和序列化反序列化 代码: 序列化反序列化实现网络版本计算器 二、HTTP协议 1、谈两个简单的预备知识 https://www.baidu.com/ --- 域名 --- 域名解析 --- IP地址 http的端口号为80端口,https的端口号为443 url为统一资源定位符。CSDNhttps://mp.csdn.net/mp_blog/creation/editor