【CUDA Runtime】第一个“Hello World“程序

文章目录

  • 前言
    • 前提须知
    • 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 交互的复杂性。

核心功能

  1. 设备管理

    • CUDA Runtime 提供了用于查询和选择 GPU 设备的函数,例如 cudaGetDeviceCountcudaSetDevice。这些函数允许程序在多 GPU 环境中灵活地选择和管理计算资源。
  2. 内存管理

    • CUDA Runtime 提供了内存分配和释放函数,例如 cudaMalloccudaFree,以及用于在主机(CPU)和设备(GPU)之间传输数据的函数,如 cudaMemcpy。这些函数使得开发者可以方便地管理 GPU 内存。
  3. 核函数执行

    • CUDA Runtime 允许开发者定义和启动核函数(kernel functions),即在 GPU 上执行的并行计算任务。核函数通常使用 <<<>>> 语法进行配置和调用,例如 myKernel<<<numBlocks, numThreads>>>(args)
  4. 错误处理

    • CUDA Runtime 提供了丰富的错误处理机制,例如 cudaGetLastErrorcudaPeekAtLastError,帮助开发者在程序执行过程中检测和调试错误。
  5. 流和事件

    • 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 配合使用,可以在程序执行完毕后释放分配的内存,避免内存泄漏。

示例:

  1. 声明设备指针: 在主机(CPU)代码中声明一个指向设备内存的指针。
  2. 调用 cudaMalloc: 使用 cudaMalloc 在设备上分配内存,并将分配的内存地址赋值给设备指针。
  3. 检查返回值: 检查 cudaMalloc 的返回值,确保内存分配成功。
  4. 使用设备内存: 使用分配的设备内存执行计算。
  5. 释放设备内存: 在不再需要设备内存时,调用 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 上进行计算前,需要将数据从主机传输到设备。
  • 将结果从设备内存复制到主机内存: 计算完成后,将结果从设备传输回主机,以便进一步处理或输出。
  • 设备内存之间的数据传输: 在不同的设备内存之间传输数据,以便更灵活地进行数据处理。

示例

  1. 将数据从主机内存复制到设备内存:

    cudaMemcpy(dev_a, a, arraySize * sizeof(int), cudaMemcpyHostToDevice);
    

    这里 dev_a 是设备内存指针,a 是主机内存数组。cudaMemcpyHostToDevice 指定拷贝方向为从主机到设备。

  2. 将结果从设备内存复制回主机内存:

    cudaMemcpy(c, dev_c, arraySize * sizeof(int), cudaMemcpyDeviceToHost);
    

    这里 dev_c 是设备内存指针,c 是主机内存数组。cudaMemcpyDeviceToHost 指定拷贝方向为从设备到主机。

注意事项

  • 确保 dstsrc 指针指向有效的内存地址,并且有足够的空间容纳 count 字节的数据。
  • 拷贝方向(由 kind 参数指定)必须正确,否则可能会导致运行时错误。
  • 对于大数据量传输,拷贝操作可能会较慢,可以考虑使用异步拷贝函数(如 cudaMemcpyAsync)来提高性能。

通过使用 cudaMemcpy,开发者可以在主机和设备之间高效地传输数据,从而充分利用 GPU 的计算能力,提升 CUDA 程序的性能。

cudaMalloc 确实是在 GPU 上分配内存。然后,我们需要使用 cudaMemcpy 将数据从主机(CPU)内存复制到设备(GPU)内存。这是因为 cudaMalloc 仅仅是在设备上分配了内存空间,但它不会自动将主机上的数据传输到设备上。为了在 GPU 上进行计算,我们需要显式地将数据从主机内存复制到设备内存。

主机内存 vs 设备内存

在 CUDA 编程中,我们有两种内存:

  1. 主机内存(Host Memory): 这是系统 RAM,由 CPU 访问和管理。
  2. 设备内存(Device Memory): 这是 GPU 的内存,由 GPU 访问和管理。

当我们在主机代码中声明变量时,它们默认是在主机内存中分配的。而 CUDA 核函数只能访问设备内存中的数据。

cudaMalloccudaMemcpy 的作用
  1. cudaMalloc: 在 GPU 的设备内存中分配一块内存。
  2. cudaMemcpy: 在主机内存和设备内存之间复制数据。
为什么需要 cudaMemcpy

当你在主机代码中声明数组 ab 时,它们是在主机内存中分配的。如果要在 GPU 上进行计算,我们需要将这些数组的数据从主机内存复制到设备内存中。否则,GPU 无法访问这些数据。

以下是一个简化的流程:

  1. 声明主机内存中的数组:

    const int a[arraySize] = { 1, 2, 3, 4, 5 };
    const int b[arraySize] = { 10, 20, 30, 40, 50 };
    
  2. 在设备内存中分配内存:

    int *dev_a = 0;
    int *dev_b = 0;
    cudaMalloc((void**)&dev_a, arraySize * sizeof(int));
    cudaMalloc((void**)&dev_b, arraySize * sizeof(int));
    
  3. 将主机内存中的数据复制到设备内存:

    cudaMemcpy(dev_a, a, arraySize * sizeof(int), cudaMemcpyHostToDevice);
    cudaMemcpy(dev_b, b, arraySize * sizeof(int), cudaMemcpyHostToDevice);
    
  4. 在设备上进行计算:

    addKernel<<<1, arraySize>>>(dev_c, dev_a, dev_b);
    
  5. 将结果从设备内存复制回主机内存:

    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 上运行的函数,它可以在成千上万的线程上并行执行,用于加速大量数据的处理。

  1. 核函数是什么:
    核函数就是一个普通的 C++ 函数,只不过它是在 GPU 上运行的。核函数可以在多个线程上并行执行,每个线程执行相同的代码,但处理不同的数据。

  2. 为什么需要核函数:
    核函数允许我们利用 GPU 的强大并行计算能力。GPU 拥有大量的处理核心,可以同时运行许多线程。通过启动核函数,我们可以让成百上千的线程同时工作,从而大大加速数据处理任务。

  3. 启动核函数的过程:

    • 准备数据: 首先,我们需要准备好要处理的数据,并将它们从主机(CPU)内存复制到设备(GPU)内存。
    • 编写核函数: 编写一个核函数,它描述了每个线程应该执行的操作。
    • 启动核函数: 使用 CUDA 提供的语法,将核函数提交给 GPU 执行。

示例

假设我们有一个简单的任务:将两个数组相加。我们可以编写一个核函数来完成这个任务,并启动它在 GPU 上执行。

  1. 编写核函数:

    __global__ void addKernel(int *c, const int *a, const int *b) {int i = threadIdx.x;  // 获取当前线程的索引c[i] = a[i] + b[i];   // 将两个数组对应位置的元素相加
    }
    
  2. 启动核函数:
    核函数不能像我们普通函数这样调用

它的规则如下

kernel<<<number_of_blocks, threads_per_block>>>(...);
  1. number_of_blocks: 这是你希望启动的线程块的数量。线程块是 GPU 上的基本并行单元,每个线程块由多个线程组成。你可以根据需要指定多个线程块,这样可以将任务划分给多个线程块处理。

  2. 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 是一个核函数,每个线程负责将数组 ab 中对应位置的元素相加,并将结果存储在数组 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 编程之旅提供一个良好的起点,帮助你更好地理解和运用这一强大的工具。

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

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

相关文章

数据库密码实现加盐加密处理

在实际的开发中&#xff0c;我们的数据库密码一般都是明文的方式存储在数据库中&#xff0c;但是&#xff0c;这种操作非常不安全&#xff0c;容易被黑&#xff01; 那么&#xff0c;此时我们就需要对其进行加密处理&#xff0c;市面上比较常见的就是MD5加密了&#xff0c;但是…

【Linux】syscall sys_write流程摸索

这是通过tty进行摸索sys_write的流程。 在前面的博客里&#xff0c;我们可以看到基于内核C语言源代码日志打印&#xff0c;在打印的日志里边包含&#xff1a;日期&#xff0c;时间&#xff0c;当前文件所在代码目录&#xff0c;当前执行函数名&#xff0c;当前文件执行行号&am…

运维团队如何借助分布式部署提升监控效率与可靠性

随着企业IT基础设施的日益复杂和分布式架构的广泛应用&#xff0c;传统的监控解决方案已经难以满足现代运维团队的需求。在这样的背景下&#xff0c;分布式部署作为一种新型的监控架构&#xff0c;以其灵活性、可扩展性和高可用性&#xff0c;成为了运维团队提升监控效率与可靠…

C++模版基础知识与STL基本介绍

目录 一. 泛型编程 二. 函数模板 1. 概念 2. 函数模版格式 3. 函数模版的原理 4. 模版函数的实例化 (1). 隐式实例化 (2.) 显式实例化 5. 模版参数的匹配原则 三. 类模板 1. 类模板的定义格式 2. 类模板的实例化 四. STL的介绍 1. 什么是STL&#xff1f; 2. STL的版…

3.5-RNN文本生成

1语言模型生成文本的顺序 前面我们已经能够实现使用下图的LSTM网络进行语言建模&#xff1b; 对于一个已经在语料库上学习好的LSTM模型&#xff1b;如果语料库就只是you say goobye and i say hello&#xff1b;那么当把单词i输入到模型中&#xff0c;Time xxx层的第一个LSTM…

苍穹外卖01

0. 配置maven (仅一次的操作 1.项目导入idea 2. 保证nginx服务器运行 &#xff08;nginx.exe要在非中文的目录下&#xff09; 开启服务&#xff1a; start nginx 查看任务进程是否存在&#xff1a; tasklist /fi "imagename eq nginx.exe" 关闭ngi…

中文之美,美在辞藻富丽,也美在情感含蓄内敛。

文章目录 引言句句不提幸福,句句都是幸福句句不提释怀,句句都是释怀句句不提爱意,句句都是爱意句句不提安慰,句句都是安慰句句不提遗憾,句句都是遗憾句句不提思念,句句都是思念引言 许多句子没有将主题直抒胸臆,却通过字词间的呼应、碰撞,让人感受到“言未表而意无穷”…

java高级——Exception异常类基本解读

java高级——Exception异常类基本解读 前情提要文章介绍继承结构异常详解1. 异常的定义2. 异常的分类3.3 异常的处理机制3.3.1 try catch finally语句3.3.2 throw关键字3.3.3 throws关键字 4. 浅谈如何有效的避免异常的发生5. 自定义异常6. 常见的RuntimeException 总结 前情提…

JDBC(Java访问数据库)

Java Database Connectivity&#xff1a;Java访问数据库的解决方案 JDBC定义了一套标准接口&#xff0c;即访问数据库的通用API&#xff0c; 不同的数据库厂商根据各自数据库的特点去实现这些接口。 JDBC希望用相同的方式访问不同的数据库&#xff0c;让具体的数据库操作与数…

HDU1056——HangOver,HDU1057——A New Growth Industry,HDU1058——Humble Numbers

目录 HDU1056——HangOver 题目描述 运行代码 代码思路 HDU1057——A New Growth Industry 题目描述 运行代码 代码思路 HDU1058——Humble Numbers 题目描述 运行代码 代码思路 HDU1056——HangOver 题目描述 Problem - 1056 运行代码 #include <iostream&…

拉提查合创5步玩转git工具协作代码开发

1 工具使用场景 开发团队使用git版本管理工具&#xff0c;进行协作代码开发过程中&#xff0c;最常用的场景为&#xff1a; &#xff08;1&#xff09;拉取代码 将git远端仓库最新代码拉取到本地。 &#xff08;2&#xff09;提交代码 将本地新增修改的代码提交至git远端仓库中…

【SpringBoot】2 项目搭建

创建项目 1&#xff09;确实本地 jdk 版本 打开命令行窗口&#xff1a;快捷键 Windows R&#xff0c;输入 CMD&#xff0c;敲回车 执行命令&#xff1a;java -version 2&#xff09;在项目 clone 的位置创建 Spring Boot 项目&#xff0c;使用 Maven 进行依赖管理&#xff…

大模型学习(1)

初学者&#xff0c;仅做自己学习记录&#xff0c;如果对你有什么帮助&#xff0c;那更好了。 下面是论文《Attention Is All You Need》的经典transformer架构&#xff0c;在学习的过程中&#xff0c;有很多疑惑。 embedding层在做什么 Transformer的embedding层在做的是将输…

35.【C语言】详解函数递归

目录&#xff1a; 定义 作用 例子1~3 拓展学习 趣味练习 1.定义&#xff1a;函数自己调用自己&#xff08;递推回归&#xff09; int main() {main()return 0; } 这样容易死循环&#xff0c;导致爆栈(Stack Overflow) 所以需要设立限制条件&#xff0c;使执行时越来越接近条…

02 Golang面向对象编程_20240727 课程笔记

视频课程 最近发现越来越多的公司在用Golang了&#xff0c;所以精心整理了一套视频教程给大家&#xff0c;这个是其中的第二部&#xff0c;后续还会有很多。 视频已经录制完成&#xff0c;完整目录截图如下&#xff1a; 课程目录 01 结构体的声明.mp402 使用var根据结构体…

iOS基础---多线程:GCD、NSThread、NSOperation

系列文章目录 iOS基础—多线程&#xff1a;GCD、NSThread、NSOperation 文章目录 系列文章目录一、GCD1.GCD的任务、函数、队列a.任务b.函数c.队列 2.GCD的使用a.同步函数并发队列b.异步函数并发队列c.同步函数串行队列d.异步函数串行队列e.同步函数主队列f.异步函数主队列 3.…

FastAPI(七十五)实战开发《在线课程学习系统》接口开发-- 创建课程

源码见&#xff1a;"fastapi_study_road-learning_system_online_courses: fastapi框架实战之--在线课程学习系统" 上次我们分享了&#xff0c;FastAPI&#xff08;七十四&#xff09;实战开发《在线课程学习系统》接口开发-- 删除留言 从本篇文章开始&#xff0c;…

如何学习Doris:糙快猛的大数据之路(从入门到专家)

引言:大数据世界的新玩家 还记得我第一次听说"Doris"这个名字时的情景吗?那是在一个炎热的夏日午后,我正在办公室里为接下来的大数据项目发愁。作为一个刚刚跨行到大数据领域的新手,我感觉自己就像是被丢进了深海的小鱼—周围全是陌生的概念和技术。 就在这时,我的…

嵌入式Python、ROS、SLAM、WebSocket和Node.js:智能巡逻监控安防机器人设计流程(代码示例)

项目概述 随着智能技术的发展&#xff0c;智能巡逻机器人在安防、监控和巡逻等领域的应用越来越广泛。本文将介绍一个结合嵌入式系统、机器人技术和后端开发的智能巡逻机器人。该机器人能够自主导航&#xff0c;实时检测异常情况&#xff08;如火灾或入侵者&#xff09;&#…

【北京迅为】《i.MX8MM嵌入式Linux开发指南》-第三篇 嵌入式Linux驱动开发篇-第五十七章 Linux中断实验

i.MX8MM处理器采用了先进的14LPCFinFET工艺&#xff0c;提供更快的速度和更高的电源效率;四核Cortex-A53&#xff0c;单核Cortex-M4&#xff0c;多达五个内核 &#xff0c;主频高达1.8GHz&#xff0c;2G DDR4内存、8G EMMC存储。千兆工业级以太网、MIPI-DSI、USB HOST、WIFI/BT…