文章目录
- 前言
- 前提须知
- CUDA Runtime 简介
- 核心功能
- 优势和应用
- 使用CudaRuntime进行第一个"Hello world"程序
- 创建CudaRuntime工程
- 选择GPU
- 函数原型
- 参数
- 返回值
- 作用
- 获取支持Cuda的GPU信息
- 获取支持Cuda的GPU数量
- 获取设备属性
- 运行展示
- 在GPU上分配内存
- 把需要运行的主机内存复制到CPU
- cudaMemcpy函数
- 主机内存 vs 设备内存
- `cudaMalloc` 和 `cudaMemcpy` 的作用
- 为什么需要 `cudaMemcpy`
- 图示
- 启动核函数
- 示例
- 整体代码
- 总结
前言
CUDA(Compute Unified Device Architecture)是 NVIDIA 提供的一个并行计算平台和编程模型,旨在通过利用 GPU 的强大计算能力来加速计算密集型应用。对于许多开发者而言,开始学习 CUDA 编程的第一步通常是编写一个简单的“Hello World”程序。尽管 CUDA 的实际应用远远超出一个简单的例子,但这个初步的示例能帮助开发者快速入门,理解基本的 CUDA 编程流程。
在这篇文章中,我们将介绍如何使用 C++ 和 CUDA 编写一个基础的“Hello World”程序。我们将演示如何设置开发环境,编写简单的 CUDA 程序,并编译和运行它。这个过程不仅会帮助你熟悉 CUDA 的基本语法,还会为进一步的 CUDA 编程奠定基础。
前提须知
使用Cuda之前,你先需要安装个VS,Cuda支持的版本有VS2017,2019与2022,我使用的为2022
VS2022的安装在这里VS2022
安装Cuda的博客在这里Cuda 安装
!!!!!再次提醒,使用Cuda你必须是英伟达的显卡!!!!!!!!
CUDA Runtime 简介
CUDA Runtime(CUDA 运行时)是 NVIDIA 提供的一个用于简化 CUDA 编程的高级 API。它建立在 CUDA Driver API 之上,提供了一组更高层次的接口,旨在使开发者更容易使用 CUDA 进行并行编程。CUDA Runtime 主要负责管理 GPU 设备、内存和核函数的执行,简化了与 GPU 交互的复杂性。
核心功能
-
设备管理:
- CUDA Runtime 提供了用于查询和选择 GPU 设备的函数,例如
cudaGetDeviceCount
和cudaSetDevice
。这些函数允许程序在多 GPU 环境中灵活地选择和管理计算资源。
- CUDA Runtime 提供了用于查询和选择 GPU 设备的函数,例如
-
内存管理:
- CUDA Runtime 提供了内存分配和释放函数,例如
cudaMalloc
和cudaFree
,以及用于在主机(CPU)和设备(GPU)之间传输数据的函数,如cudaMemcpy
。这些函数使得开发者可以方便地管理 GPU 内存。
- CUDA Runtime 提供了内存分配和释放函数,例如
-
核函数执行:
- CUDA Runtime 允许开发者定义和启动核函数(kernel functions),即在 GPU 上执行的并行计算任务。核函数通常使用
<<<>>>
语法进行配置和调用,例如myKernel<<<numBlocks, numThreads>>>(args)
。
- CUDA Runtime 允许开发者定义和启动核函数(kernel functions),即在 GPU 上执行的并行计算任务。核函数通常使用
-
错误处理:
- CUDA Runtime 提供了丰富的错误处理机制,例如
cudaGetLastError
和cudaPeekAtLastError
,帮助开发者在程序执行过程中检测和调试错误。
- CUDA Runtime 提供了丰富的错误处理机制,例如
-
流和事件:
- CUDA Runtime 支持流(streams)和事件(events)的概念,使得开发者可以管理并发计算和同步操作。这些特性有助于优化程序性能,充分利用 GPU 的并行计算能力。
优势和应用
使用 CUDA Runtime 的主要优势在于其简化了 CUDA 编程的复杂性,使得开发者无需直接处理底层的驱动 API,从而更专注于并行算法的实现和优化。通过 CUDA Runtime,开发者可以快速上手 CUDA 编程,并逐步深入了解和应用更多高级特性。
CUDA Runtime 被广泛应用于各类计算密集型应用领域,包括科学计算、机器学习、图像处理和计算机视觉等。它的高效性和灵活性使得 CUDA 成为高性能计算(HPC)和数据密集型任务的首选工具之一。
使用CudaRuntime进行第一个"Hello world"程序
创建CudaRuntime工程
在安装完Cuda之后,在vs新建工程应该会出现这个:
我们使用这个来创建就行了,他创建好的文件拓展名为cu
选择GPU
cudaSetDevice
是 CUDA Runtime API 中的一个函数,用于选择要用于后续 CUDA 操作的 GPU 设备。在多 GPU 系统中,使用该函数可以指定哪个 GPU 设备将用于执行 CUDA 代码。这个函数在程序开始时调用,确保之后的 CUDA 操作都在指定的 GPU 上执行。
函数原型
cudaError_t cudaSetDevice(int device);
参数
device
: 要选择的 GPU 设备的编号。编号从 0 开始,表示第一个 GPU 设备。
返回值
cudaSuccess
: 如果设备设置成功,返回此值。- 其他错误码: 如果设备设置失败,返回相应的 CUDA 错误码,例如:
cudaErrorInvalidDevice
: 如果指定的设备编号无效(超出设备范围),返回此错误。cudaErrorDeviceAlreadyInUse
: 如果设备已被另一个 CUDA 上下文使用并且当前上下文不能切换到该设备,返回此错误。
作用
cudaSetDevice
的主要作用是指定要用于后续 CUDA 操作的 GPU 设备。在多 GPU 系统中,可以使用此函数来选择不同的 GPU 设备执行不同的 CUDA 任务,从而提高程序的并行性能和资源利用率。
获取支持Cuda的GPU信息
要获取系统中可用的 GPU 数量,你可以使用 cudaGetDeviceCount
函数。这个函数会返回系统中 CUDA 兼容的 GPU 设备的数量。
获取支持Cuda的GPU数量
cudaError_t cudaGetDeviceCount(int *count);
参数
count
: 指向一个整数的指针,用于存储系统中可用的 GPU 设备数量。
返回值
cudaSuccess
: 如果成功获取设备数量,返回此值。- 其他错误码: 如果操作失败,返回相应的 CUDA 错误码。
获取设备属性
cudaGetDeviceProperties
是 CUDA Runtime API 中的一个函数,用于获取指定 GPU 设备的属性。通过该函数,开发者可以获取 GPU 设备的各种硬件特性和能力,从而更好地优化 CUDA 程序。
函数原型:
cudaError_t cudaGetDeviceProperties(cudaDeviceProp *prop, int device);
参数:
prop
: 指向cudaDeviceProp
结构体的指针,用于存储所获取的设备属性信息。device
: 要查询的 GPU 设备编号。编号从 0 开始,表示第一个 GPU 设备。
返回值:
cudaSuccess
: 如果成功获取设备属性,返回此值。- 其他错误码: 如果操作失败,返回相应的 CUDA 错误码,例如:
cudaErrorInvalidDevice
: 如果指定的设备编号无效(超出设备范围),返回此错误。
cudaDeviceProp
结构体:
cudaDeviceProp
结构体包含了设备的各种属性信息,包括硬件和配置参数。以下是 cudaDeviceProp
结构体中常用的一些字段:
char name[256]
: 设备名称。size_t totalGlobalMem
: 全局内存的总字节数。size_t sharedMemPerBlock
: 每个块的共享内存字节数。int regsPerBlock
: 每个块的寄存器数量。int warpSize
: Warp 的大小(通常为 32)。size_t memPitch
: 最大内存分配宽度(字节)。int maxThreadsPerBlock
: 每个块的最大线程数。int maxThreadsDim[3]
: 每个块的最大维度(x、y、z)。int maxGridSize[3]
: 网格的最大尺寸(x、y、z)。int clockRate
: 时钟频率(千赫兹)。size_t totalConstMem
: 常量内存的总字节数。int major
: 计算能力的主要版本号。int minor
: 计算能力的次要版本号。size_t textureAlignment
: 纹理对齐要求。int multiProcessorCount
: 多处理器数量。int kernelExecTimeoutEnabled
: 核函数执行超时使能标志。int integrated
: 设备是否为集成设备(非离散 GPU)。int canMapHostMemory
: 设备是否支持映射主机内存。int computeMode
: 计算模式(例如,默认、限制访问、禁止访问等)。int concurrentKernels
: 设备是否支持并发核函数执行。int ECCEnabled
: 设备是否启用 ECC(错误纠正码)。int pciBusID
: PCI 总线 ID。int pciDeviceID
: PCI 设备 ID。int pciDomainID
: PCI 域 ID。
作用:
cudaGetDeviceProperties
的主要作用是获取指定 GPU 设备的详细属性信息。这些属性信息可以帮助开发者了解设备的硬件特性和限制,从而更好地优化 CUDA 应用。例如,开发者可以根据设备的最大线程数、共享内存大小、寄存器数量等信息来调整核函数的配置和资源分配,以提高程序性能。
通过使用 cudaGetDeviceProperties
函数,开发者可以对 CUDA 兼容设备进行详细分析和评估,为 CUDA 程序的开发和优化提供重要依据。
运行展示
在GPU上分配内存
cudaMalloc
函数介绍
cudaMalloc
是 CUDA Runtime API 中的一个函数,用于在 GPU 设备上分配内存。这个函数类似于 C 语言中的 malloc
函数,但它是在 GPU 设备的全局内存上进行内存分配。通过使用 cudaMalloc
,开发者可以为 CUDA 程序在设备上分配所需的内存,以存储数据和执行计算。
函数原型
cudaError_t cudaMalloc(void **devPtr, size_t size);
参数
devPtr
: 指向设备内存指针的指针。cudaMalloc
会在设备上分配指定大小的内存,并将分配的内存地址赋值给devPtr
指向的指针。size
: 要分配的内存大小(字节数)。
返回值
cudaSuccess
: 如果内存分配成功,返回此值。- 其他错误码: 如果操作失败,返回相应的 CUDA 错误码,例如:
cudaErrorMemoryAllocation
: 如果内存分配失败,返回此错误。
作用
cudaMalloc
的主要作用是在 GPU 设备的全局内存上分配指定大小的内存块。这些内存块可以用于存储从主机(CPU)复制过来的数据,或用于在 GPU 上进行计算和存储中间结果。与 cudaFree
配合使用,可以在程序执行完毕后释放分配的内存,避免内存泄漏。
示例:
- 声明设备指针: 在主机(CPU)代码中声明一个指向设备内存的指针。
- 调用
cudaMalloc
: 使用cudaMalloc
在设备上分配内存,并将分配的内存地址赋值给设备指针。 - 检查返回值: 检查
cudaMalloc
的返回值,确保内存分配成功。 - 使用设备内存: 使用分配的设备内存执行计算。
- 释放设备内存: 在不再需要设备内存时,调用
cudaFree
释放内存。
注意事项:
- 内存分配大小应根据需要计算,并确保不会超过 GPU 的可用内存。
- 分配的内存应在使用完毕后及时释放,以避免内存泄漏。
cudaMalloc
分配的内存只能在设备代码(核函数)中使用,不能直接在主机代码中访问。
通过使用 cudaMalloc
,开发者可以灵活地在 GPU 设备上分配和管理内存,为 CUDA 程序的高效执行提供必要的资源保障。
把需要运行的主机内存复制到CPU
cudaMemcpy函数
cudaMemcpy
是 CUDA Runtime API 中的一个函数,用于在主机(CPU)内存和设备(GPU)内存之间复制数据。它是实现主机与设备之间数据传输的核心函数之一。
函数原型
cudaError_t cudaMemcpy(void *dst, const void *src, size_t count, cudaMemcpyKind kind);
参数
dst
: 目标内存地址。可以是主机内存或设备内存,具体取决于kind
参数。src
: 源内存地址。可以是主机内存或设备内存,具体取决于kind
参数。count
: 要复制的字节数。kind
: 指定内存拷贝的方向。可以是以下值之一:cudaMemcpyHostToHost
: 主机内存到主机内存的拷贝。cudaMemcpyHostToDevice
: 主机内存到设备内存的拷贝。cudaMemcpyDeviceToHost
: 设备内存到主机内存的拷贝。cudaMemcpyDeviceToDevice
: 设备内存到设备内存的拷贝。
返回值
cudaSuccess
: 如果内存拷贝成功,返回此值。- 其他错误码: 如果操作失败,返回相应的 CUDA 错误码,例如:
cudaErrorInvalidValue
: 如果count
为 0 或kind
无效,返回此错误。cudaErrorMemoryAllocation
: 如果无法分配内存,返回此错误。cudaErrorInvalidMemcpyDirection
: 如果kind
参数无效,返回此错误。
作用
cudaMemcpy
的主要作用是在主机内存和设备内存之间传输数据。这在 CUDA 编程中是非常常见的操作,因为在 GPU 上进行计算之前,需要将数据从主机传输到设备,并在计算完成后将结果传回主机。
使用场景
- 将数据从主机内存复制到设备内存: 在 GPU 上进行计算前,需要将数据从主机传输到设备。
- 将结果从设备内存复制到主机内存: 计算完成后,将结果从设备传输回主机,以便进一步处理或输出。
- 设备内存之间的数据传输: 在不同的设备内存之间传输数据,以便更灵活地进行数据处理。
示例
-
将数据从主机内存复制到设备内存:
cudaMemcpy(dev_a, a, arraySize * sizeof(int), cudaMemcpyHostToDevice);
这里
dev_a
是设备内存指针,a
是主机内存数组。cudaMemcpyHostToDevice
指定拷贝方向为从主机到设备。 -
将结果从设备内存复制回主机内存:
cudaMemcpy(c, dev_c, arraySize * sizeof(int), cudaMemcpyDeviceToHost);
这里
dev_c
是设备内存指针,c
是主机内存数组。cudaMemcpyDeviceToHost
指定拷贝方向为从设备到主机。
注意事项
- 确保
dst
和src
指针指向有效的内存地址,并且有足够的空间容纳count
字节的数据。 - 拷贝方向(由
kind
参数指定)必须正确,否则可能会导致运行时错误。 - 对于大数据量传输,拷贝操作可能会较慢,可以考虑使用异步拷贝函数(如
cudaMemcpyAsync
)来提高性能。
通过使用 cudaMemcpy
,开发者可以在主机和设备之间高效地传输数据,从而充分利用 GPU 的计算能力,提升 CUDA 程序的性能。
cudaMalloc
确实是在 GPU 上分配内存。然后,我们需要使用 cudaMemcpy
将数据从主机(CPU)内存复制到设备(GPU)内存。这是因为 cudaMalloc
仅仅是在设备上分配了内存空间,但它不会自动将主机上的数据传输到设备上。为了在 GPU 上进行计算,我们需要显式地将数据从主机内存复制到设备内存。
主机内存 vs 设备内存
在 CUDA 编程中,我们有两种内存:
- 主机内存(Host Memory): 这是系统 RAM,由 CPU 访问和管理。
- 设备内存(Device Memory): 这是 GPU 的内存,由 GPU 访问和管理。
当我们在主机代码中声明变量时,它们默认是在主机内存中分配的。而 CUDA 核函数只能访问设备内存中的数据。
cudaMalloc
和 cudaMemcpy
的作用
cudaMalloc
: 在 GPU 的设备内存中分配一块内存。cudaMemcpy
: 在主机内存和设备内存之间复制数据。
为什么需要 cudaMemcpy
当你在主机代码中声明数组 a
和 b
时,它们是在主机内存中分配的。如果要在 GPU 上进行计算,我们需要将这些数组的数据从主机内存复制到设备内存中。否则,GPU 无法访问这些数据。
以下是一个简化的流程:
-
声明主机内存中的数组:
const int a[arraySize] = { 1, 2, 3, 4, 5 }; const int b[arraySize] = { 10, 20, 30, 40, 50 };
-
在设备内存中分配内存:
int *dev_a = 0; int *dev_b = 0; cudaMalloc((void**)&dev_a, arraySize * sizeof(int)); cudaMalloc((void**)&dev_b, arraySize * sizeof(int));
-
将主机内存中的数据复制到设备内存:
cudaMemcpy(dev_a, a, arraySize * sizeof(int), cudaMemcpyHostToDevice); cudaMemcpy(dev_b, b, arraySize * sizeof(int), cudaMemcpyHostToDevice);
-
在设备上进行计算:
addKernel<<<1, arraySize>>>(dev_c, dev_a, dev_b);
-
将结果从设备内存复制回主机内存:
cudaMemcpy(c, dev_c, arraySize * sizeof(int), cudaMemcpyDeviceToHost);
图示
主机内存(CPU) 设备内存(GPU)
+----------------------+ +----------------------+
| a = {1, 2, 3, 4, 5} | | |
| b = {10, 20, 30, 40, 50} | | |
+----------------------+ +----------------------+| || cudaMemcpy (Host to Device)| v v
+----------------------+ +----------------------+
| | | dev_a = {1, 2, 3, 4, 5} |
| | | dev_b = {10, 20, 30, 40, 50} |
+----------------------+ +----------------------+
通过这种方式,我们将主机内存中的数据传输到设备内存,以便在 GPU 上进行并行计算。计算完成后,我们将结果从设备内存复制回主机内存,以便在 CPU 上进一步处理或输出结果。
启动核函数
启动核函数(Launching a Kernel)是指在 CUDA 编程中,将在 GPU 上执行的并行计算任务提交给 GPU 进行处理。核函数(Kernel)是一个在 GPU 上运行的函数,它可以在成千上万的线程上并行执行,用于加速大量数据的处理。
-
核函数是什么:
核函数就是一个普通的 C++ 函数,只不过它是在 GPU 上运行的。核函数可以在多个线程上并行执行,每个线程执行相同的代码,但处理不同的数据。 -
为什么需要核函数:
核函数允许我们利用 GPU 的强大并行计算能力。GPU 拥有大量的处理核心,可以同时运行许多线程。通过启动核函数,我们可以让成百上千的线程同时工作,从而大大加速数据处理任务。 -
启动核函数的过程:
- 准备数据: 首先,我们需要准备好要处理的数据,并将它们从主机(CPU)内存复制到设备(GPU)内存。
- 编写核函数: 编写一个核函数,它描述了每个线程应该执行的操作。
- 启动核函数: 使用 CUDA 提供的语法,将核函数提交给 GPU 执行。
示例
假设我们有一个简单的任务:将两个数组相加。我们可以编写一个核函数来完成这个任务,并启动它在 GPU 上执行。
-
编写核函数:
__global__ void addKernel(int *c, const int *a, const int *b) {int i = threadIdx.x; // 获取当前线程的索引c[i] = a[i] + b[i]; // 将两个数组对应位置的元素相加 }
-
启动核函数:
核函数不能像我们普通函数这样调用
它的规则如下
kernel<<<number_of_blocks, threads_per_block>>>(...);
-
number_of_blocks: 这是你希望启动的线程块的数量。线程块是 GPU 上的基本并行单元,每个线程块由多个线程组成。你可以根据需要指定多个线程块,这样可以将任务划分给多个线程块处理。
-
threads_per_block: 这是每个线程块中线程的数量。每个线程块中的线程会并行执行核函数中的代码。你需要根据计算任务的规模和 GPU 的能力来决定每个线程块的线程数量。
int main() {const int arraySize = 5;int a[arraySize] = {1, 2, 3, 4, 5};int b[arraySize] = {10, 20, 30, 40, 50};int c[arraySize] = {0};// 在设备内存中分配数组int *dev_a, *dev_b, *dev_c;cudaMalloc((void**)&dev_a, arraySize * sizeof(int));cudaMalloc((void**)&dev_b, arraySize * sizeof(int));cudaMalloc((void**)&dev_c, arraySize * sizeof(int));// 将数据从主机内存复制到设备内存cudaMemcpy(dev_a, a, arraySize * sizeof(int), cudaMemcpyHostToDevice);cudaMemcpy(dev_b, b, arraySize * sizeof(int), cudaMemcpyHostToDevice);// 启动核函数,执行并行计算addKernel<<<1, arraySize>>>(dev_c, dev_a, dev_b);// 将结果从设备内存复制回主机内存cudaMemcpy(c, dev_c, arraySize * sizeof(int), cudaMemcpyDeviceToHost);// 输出结果for (int i = 0; i < arraySize; i++) {printf("%d ", c[i]);}// 释放设备内存cudaFree(dev_a);cudaFree(dev_b);cudaFree(dev_c);return 0; }
在上面的例子中,addKernel
是一个核函数,每个线程负责将数组 a
和 b
中对应位置的元素相加,并将结果存储在数组 c
中。
通过它的输出可以得知:总共使用了5个线程
整体代码
#include "cuda_runtime.h"
#include "device_launch_parameters.h"#include <stdio.h>
#include <iostream>
using namespace std;// 声明一个函数,用于使用 CUDA 在 GPU 上进行向量相加。
cudaError_t addWithCuda(int* c, const int* a, const int* b, unsigned int size);// CUDA 核函数,在 GPU 上并行运行,用于执行向量相加操作。
__global__ void addKernel(int* c, const int* a, const int* b)
{int i = threadIdx.x; // 获取当前线程的索引c[i] = a[i] + b[i]; // 执行向量相加
}int main()
{int count;cudaGetDeviceCount(&count);printf("can use :%d\n", count);cudaDeviceProp prop;cudaGetDeviceProperties(&prop, 0);printf("can use name:%s\n", prop.name);const int arraySize = 5; // 定义数组大小const int a[arraySize] = { 1, 2, 3, 4, 5 }; // 初始化输入向量 aconst int b[arraySize] = { 10, 20, 30, 40, 50 }; // 初始化输入向量 bint c[arraySize] = { 0 }; // 初始化输出向量 c// 在 GPU 上并行相加向量。cudaError_t cudaStatus = addWithCuda(c, a, b, arraySize);if (cudaStatus != cudaSuccess) {fprintf(stderr, "addWithCuda failed!"); // 如果调用失败,输出错误信息return 1;}// 打印输出结果printf("{1,2,3,4,5} + {10,20,30,40,50} = {%d,%d,%d,%d,%d}\n",c[0], c[1], c[2], c[3], c[4]);// 调用 cudaDeviceReset,确保在退出前完成所有 CUDA 任务,便于使用 Nsight 和 Visual Profiler 等工具进行分析。cudaStatus = cudaDeviceReset();if (cudaStatus != cudaSuccess) {fprintf(stderr, "cudaDeviceReset failed!"); // 如果调用失败,输出错误信息return 1;}return 0;
}// 使用 CUDA 在 GPU 上并行相加向量的辅助函数。
cudaError_t addWithCuda(int* c, const int* a, const int* b, unsigned int size)
{int* dev_a = 0; // 指向 GPU 上的输入向量 a 的指针int* dev_b = 0; // 指向 GPU 上的输入向量 b 的指针int* dev_c = 0; // 指向 GPU 上的输出向量 c 的指针cudaError_t cudaStatus;// 选择运行的 GPU,在多 GPU 系统中可以更改此设置。cudaStatus = cudaSetDevice(0);if (cudaStatus != cudaSuccess) {fprintf(stderr, "cudaSetDevice failed! Do you have a CUDA-capable GPU installed?"); // 如果调用失败,输出错误信息goto Error;}// 为三个向量(两个输入,一个输出)在 GPU 上分配内存。cudaStatus = cudaMalloc((void**)&dev_c, size * sizeof(int));if (cudaStatus != cudaSuccess) {fprintf(stderr, "cudaMalloc failed!"); // 如果调用失败,输出错误信息goto Error;}cudaStatus = cudaMalloc((void**)&dev_a, size * sizeof(int));if (cudaStatus != cudaSuccess) {fprintf(stderr, "cudaMalloc failed!"); // 如果调用失败,输出错误信息goto Error;}cudaStatus = cudaMalloc((void**)&dev_b, size * sizeof(int));if (cudaStatus != cudaSuccess) {fprintf(stderr, "cudaMalloc failed!"); // 如果调用失败,输出错误信息goto Error;}// 将输入向量从主机内存复制到 GPU 内存。cudaStatus = cudaMemcpy(dev_a, a, size * sizeof(int), cudaMemcpyHostToDevice);if (cudaStatus != cudaSuccess) {fprintf(stderr, "cudaMemcpy failed!"); // 如果调用失败,输出错误信息goto Error;}cudaStatus = cudaMemcpy(dev_b, b, size * sizeof(int), cudaMemcpyHostToDevice);if (cudaStatus != cudaSuccess) {fprintf(stderr, "cudaMemcpy failed!"); // 如果调用失败,输出错误信息goto Error;}// 使用每个元素一个线程,在 GPU 上启动一个核函数。addKernel<<<1, size >>>(dev_c, dev_a, dev_b);// 检查启动核函数时是否有任何错误。cudaStatus = cudaGetLastError();if (cudaStatus != cudaSuccess) {fprintf(stderr, "addKernel launch failed: %s\n", cudaGetErrorString(cudaStatus)); // 如果调用失败,输出错误信息goto Error;}// 等待核函数执行完毕,并返回执行过程中遇到的任何错误。cudaStatus = cudaDeviceSynchronize();if (cudaStatus != cudaSuccess) {fprintf(stderr, "cudaDeviceSynchronize returned error code %d after launching addKernel!\n", cudaStatus); // 如果调用失败,输出错误信息goto Error;}// 将输出向量从 GPU 内存复制到主机内存。cudaStatus = cudaMemcpy(c, dev_c, size * sizeof(int), cudaMemcpyDeviceToHost);if (cudaStatus != cudaSuccess) {fprintf(stderr, "cudaMemcpy failed!"); // 如果调用失败,输出错误信息goto Error;}Error:cudaFree(dev_c); // 释放 GPU 内存cudaFree(dev_a); // 释放 GPU 内存cudaFree(dev_b); // 释放 GPU 内存return cudaStatus; // 返回 CUDA 调用状态
}
总结
在本文中,我们通过编写一个简单的 CUDA “Hello World”程序,演示了 CUDA 编程的基本步骤。这包括了环境设置、代码编写、编译和运行。尽管这个示例程序非常简单,它展示了如何在 C++ 中集成 CUDA,并通过 GPU 执行一个基本的计算任务。掌握了这些基础后,你可以进一步探索更复杂的 CUDA 编程技术和应用,包括内存管理、核函数优化和并行计算等方面。
学习 CUDA 编程虽然一开始可能会觉得有些复杂,但通过不断实践和探索,你会发现它强大的并行计算能力能够显著提升计算密集型应用的性能。希望本文的示例能为你的 CUDA 编程之旅提供一个良好的起点,帮助你更好地理解和运用这一强大的工具。