【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,一经查实,立即删除!

相关文章

scrapy出现OSError: could not get source code错误解决

出现问题如下&#xff1a; Traceback (most recent call last):File "C:\Users\admin\Desktop\crawler_scrapy_us\venv\lib\site-packages\scrapy\utils\defer.py", line 73, in mustbe_deferredresult f(*args, **kw)File "C:\Users\admin\Desktop\crawler_sc…

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

在实际的开发中&#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的版…

Linux Centos防火墙相关操作命令

防火墙基础操作 #开启防火墙 systemctl start firewalld#关闭防火墙 systemctl stop firewalld重新加载防火墙规则(改了规则后均需执行) firewall-cmd --reload防火墙开放某端口 firewall-cmd --permanent --add-port8080/tcp防火墙禁用某ip访问 firewall-cmd --permanent …

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…

查看、指定使用的 GPU 数量和编号

在使用 PyTorch 框架时&#xff0c;可以通过以下步骤查看可用的 GPU 数量&#xff0c;指定使用的 GPU 编号&#xff0c;并在代码中体现这一点。下面以2个GPU为例&#xff1a; 目录 一、脚本代码块实现1. 查看可用的 GPU2. 指定使用 GPU 的数量和编号使用 CUDA_VISIBLE_DEVICES…

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

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

第12章 Express的RESTful API开发(二)

3. 路由与中间件 在Express中&#xff0c;路由用于定义应用的各个端点&#xff08;URI&#xff09;及其处理程序。中间件是一个可以访问请求对象&#xff08;req&#xff09;、响应对象&#xff08;res&#xff09;和下一个中间件函数的函数。中间件用于处理请求之前执行一些操…

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&…

Elasticsearch面试三道题

针对Elasticsearch的面试题&#xff0c;从简单到困难&#xff0c;我可以给出以下三道题目&#xff1a; 1. Elasticsearch的基本概念与优势 问题&#xff1a;请简要介绍Elasticsearch是什么&#xff0c;并说明它相比传统数据库的优势有哪些&#xff1f; 答案&#xff1a; El…

数学建模--整数规划和非线性规划

目录 整数规划 非线性规划 总结 整数规划中分支定界法的具体步骤和实现细节是什么&#xff1f; 初始化&#xff1a; 分支&#xff1a; 定界&#xff1a; 剪枝&#xff1a; 终止条件&#xff1a; 非线性规划中的梯度法、牛顿法和拟牛顿法的比较分析有哪些&#xff1f;…

php判断某个目录下是否存在文件

/*** 判断字符串是否以什么结尾* param String $haystack 字符串* param String $needle 结尾* return Boolean*/ function endWith($haystack, $needle) {$length strlen($needle);if ($length 0) {return true;}return (substr($haystack, -$length) $needle); } /***…

Prometheus 监控 RabbitMQ

1. 安装 RabbitMQ Exporter RabbitMQ Exporter 是连接 RabbitMQ 和 Prometheus 的桥梁,它从 RabbitMQ 收集指标并以 Prometheus 可以理解的格式暴露这些指标。 1.1 下载 RabbitMQ Exporter wget https://github.com/kbudde/rabbitmq_exporter/releases/download/v0.20.0/ra…

前端面试题每日一学_1

今日一问&#xff1a; 下面的JS代码中&#xff0c;执行结果为object的选项是 () A、typeof []; B、typeof {}; C、typeof null; D、typeof undefined;​ 答案和解析可在文章底部查看。 今日面试题&#xff1a; 1、前端SEO优化的方法有哪些&#xff1f; ① 设置合理准确的t…

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

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