CUDA从入门到精通(三)——CUDA编程示例

CUDA 编程简介

CUDACompute Unified Device Architecture)是由 NVIDIA 提供的一种并行计算平台和编程模型。它允许开发者利用 NVIDIA GPU 的并行计算能力,编写可以在 GPU 上高效运行的代码,从而加速计算密集型任务。

CUDA 通过扩展标准的 C/C++ 语言,提供了编译工具链和库,使程序员可以轻松编写并行代码。


1. CUDA 的核心概念

GPU 和 CPU 的协同计算
  • CPU:负责运行主程序(主机代码,Host Code),将任务分配给 GPU。
  • GPU:负责运行数据并行的任务(设备代码,Device Code),执行大量并行计算。

CUDA 核函数 (Kernel)
  • Kernel 是 CUDA 程序中的核心函数,它运行在 GPU 上的所有线程中。
  • 通过 __global__ 关键字定义:
    __global__ void myKernel(int *a) {
        int i = threadIdx.x; // 当前线程索引
        a[i] = a[i] * 2;     // 并行计算
    }
    

线程、线程块和网格 (Grid)

CUDA 提供了一种分层的 线程模型,用于在 GPU 上组织并行计算:

  • 线程 (Thread):执行计算的基本单元。
  • 线程块 (Thread Block):由一组线程组成,可以共享数据。
  • 网格 (Grid):由多个线程块组成。

线程的组织结构可以用一维、二维或三维方式定义:

dim3 blockSize(256);       // 每个线程块 256 个线程
dim3 gridSize((N + 255)/256); // 网格大小 (任务总数 / 线程数)
myKernel<<<gridSize, blockSize>>>(d_data);

内存模型

CUDA 提供多种内存空间,分为 主机内存(CPU)设备内存(GPU)

  1. 全局内存(Global Memory):GPU 所有线程都可以访问,访问速度慢但容量大。
  2. 共享内存(Shared Memory):线程块内的线程共享的数据,速度快但容量小。
  3. 寄存器(Registers):线程私有的内存,速度最快。
  4. 常量内存(Constant Memory):只读内存,所有线程共享。
  5. 纹理内存 / 表面内存:用于特殊优化的数据访问场景。

2. CUDA 程序结构

一个典型的 CUDA 程序分为以下几个步骤:

  1. 主机代码初始化(CPU)

    • 分配和初始化内存。
  2. 从CPU内存中拷贝数据到GPU内存

    • 将数据传输到 GPU(cudaMemcpy)。
  3. 内核函数执行(GPU)

    • 调用核函数(<<<gridSize, blockSize>>>),GPU 执行并行任务。
  4. 结果传回主机

    • 将 GPU 上的数据传回 CPU。
  5. 释放资源

    • 释放主机和设备内存。

3. 简单 CUDA 示例

3.1 helloworld示例
#include<stdio.h>
__global__ void helloWorldFromGPU(){
    printf("hello world from GPU!\n\n");
}

int main(int argc, char const *argv[])
{
    helloWorldFromGPU<<<1,10>>>();
    return 0;
}

  • 执行:
nvcc helloworld.cu -o main
main

在这里插入图片描述

cudaDeviceReset函数

cudaDeviceReset 是一个用于清理 CUDA 设备状态的函数,它会释放与当前 CUDA 上下文(Context)相关联的所有资源,并将设备状态重置为初始状态。如果程序不调用它,CUDA 运行时可能会保留一些状态或资源,直到程序完全结束。

  1. 调用 cudaDeviceReset 的情况:
  • 释放 GPU 上的资源:cudaDeviceReset 会确保设备上的内存和上下文被完全释放,设备状态恢复为初始状态。
  • 干净退出:CUDA 上下文释放后,程序会正常退出,不会留有残余资源。
  1. 移除 cudaDeviceReset 的情况:
  • 资源未完全释放:在程序结束时,GPU 上的资源会保留,直到操作系统自动回收。
  • 影响多次执行的程序:如果后续还有其他 CUDA 程序,设备资源状态可能会受到前一个程序的影响。
  • 调试环境行为:在调试工具(如 cuda-memcheck)中,未调用 cudaDeviceReset 可能会产生警告,指出设备资源未被显式释放。

对于 小型程序,两种情况下的运行结果几乎没有差异,程序可以正常执行。但在以下情况下,差异会更加明显:

  • 长时间运行的程序:不调用 cudaDeviceReset 可能导致设备资源持续占用。
  • 反复调用 CUDA 程序:如果 GPU 设备状态未重置,后续程序可能会遇到资源分配失败或初始化冲突。
  • 工具检测:使用 CUDA 调试工具(如 cuda-memcheck)时,不调用 cudaDeviceReset 可能会报告内存泄漏。
#pragma execution_character_set("utf-8")
#include <cuda_runtime.h>
#include <iostream>

__global__ void vectorAdd(int *a, int *b, int *c, int n) {
    int idx = threadIdx.x + blockIdx.x * blockDim.x;
    if (idx < n) {
        c[idx] = a[idx] + b[idx];
    }
}

int main() {
    const int N = 1000000; // 数据规模
    size_t size = N * sizeof(int);

    // 分配主机内存
    int *h_a = (int *)malloc(size);
    int *h_b = (int *)malloc(size);
    int *h_c = (int *)malloc(size);

    // 初始化数据
    for (int i = 0; i < N; i++) {
        h_a[i] = i;
        h_b[i] = 2 * i;
    }

    // 分配设备内存
    int *d_a, *d_b, *d_c;
    cudaMalloc(&d_a, size);
    cudaMalloc(&d_b, size);
    cudaMalloc(&d_c, size);

    // 将数据从主机传输到设备
    cudaMemcpy(d_a, h_a, size, cudaMemcpyHostToDevice);
    cudaMemcpy(d_b, h_b, size, cudaMemcpyHostToDevice);

    // 启动核函数
    int blockSize = 256;
    int gridSize = (N + blockSize - 1) / blockSize;
    vectorAdd<<<gridSize, blockSize>>>(d_a, d_b, d_c, N);

    // 将结果传回主机
    cudaMemcpy(h_c, d_c, size, cudaMemcpyDeviceToHost);

    // 验证结果
    for (int i = 0; i < 10; i++) {
        std::cout << h_c[i] << " ";
    }
    std::cout << std::endl;

    // 释放资源
    cudaFree(d_a);
    cudaFree(d_b);
    cudaFree(d_c);
    free(h_a);
    free(h_b);
    free(h_c);

    // cudaDeviceReset 调用 (可以移除)
    cudaDeviceReset();

    return 0;
}

在这里插入图片描述


cudaDeviceSynchronize函数
cudaDeviceSynchronizecudaDeviceReset 的区别
  1. cudaDeviceSynchronize

    • 作用:确保所有之前提交到 GPU 的任务(如核函数、数据传输)都执行完成,起到 同步 的作用。
    • 执行后:程序继续执行后续的代码,但不会清理 GPU 上的资源。
  2. cudaDeviceReset

    • 作用:除了同步任务外,还会将 GPU 设备状态重置并释放所有资源(如设备内存、上下文等)。
    • 执行后:GPU 状态重置为初始状态,所有分配的资源会被释放。

为什么不能直接用 cudaDeviceSynchronize 替换 cudaDeviceReset
  • cudaDeviceSynchronize 仅起到同步作用,它不会释放 GPU 资源。
  • 如果程序在多次运行 CUDA 任务时,没有调用 cudaDeviceReset,GPU 上的资源可能无法及时释放,导致 资源泄漏初始化冲突

替换示例

以下是代码中用 cudaDeviceSynchronize 替换 cudaDeviceReset 的示例:

#pragma execution_character_set("utf-8")
#include <cuda_runtime.h>
#include <iostream>

__global__ void vectorAdd(int *a, int *b, int *c, int n) {
    int idx = threadIdx.x + blockIdx.x * blockDim.x;
    if (idx < n) {
        c[idx] = a[idx] + b[idx];
    }
}

int main() {
    const int N = 1000000; // 数据规模
    size_t size = N * sizeof(int);

    // 分配主机内存
    int *h_a = (int *)malloc(size);
    int *h_b = (int *)malloc(size);
    int *h_c = (int *)malloc(size);

    // 初始化数据
    for (int i = 0; i < N; i++) {
        h_a[i] = i;
        h_b[i] = 2 * i;
    }

    // 分配设备内存
    int *d_a, *d_b, *d_c;
    cudaMalloc(&d_a, size);
    cudaMalloc(&d_b, size);
    cudaMalloc(&d_c, size);

    // 将数据从主机传输到设备
    cudaMemcpy(d_a, h_a, size, cudaMemcpyHostToDevice);
    cudaMemcpy(d_b, h_b, size, cudaMemcpyHostToDevice);

    // 启动核函数
    int blockSize = 256;
    int gridSize = (N + blockSize - 1) / blockSize;
    vectorAdd<<<gridSize, blockSize>>>(d_a, d_b, d_c, N);

    // 将结果传回主机
    cudaMemcpy(h_c, d_c, size, cudaMemcpyDeviceToHost);

    // 验证结果
    for (int i = 0; i < 10; i++) {
        std::cout << h_c[i] << " ";
    }
    std::cout << std::endl;

    // 释放资源
    cudaFree(d_a);
    cudaFree(d_b);
    cudaFree(d_c);
    free(h_a);
    free(h_b);
    free(h_c);

    // cudaDeviceReset 调用 (可以移除)
    // cudaDeviceReset();
    cudaDeviceSynchronize();

    return 0;
}


在这里插入图片描述

打印线程索引
#include<stdio.h>
__global__ void helloWorldFromGPU(){
    //  需要添加线程索引
    int threadIndex = threadIdx.x;
    printf("hello world from GPU! threadIndex: %d\n\n", threadIndex);
}

int main(int argc, char const *argv[])
{
    helloWorldFromGPU<<<1,10>>>();  // 在GPU上执行helloWorldFromGPU函数
    return 0;
}


在这里插入图片描述


  1. 调用 cudaDeviceReset

    • GPU 资源会被完全释放。
    • 在多次运行程序时,GPU 设备状态重新初始化。
  2. 调用 cudaDeviceSynchronize

    • 程序会等待所有任务完成,但不会释放 GPU 资源。
    • 如果不手动释放内存(如 cudaFree),资源会在程序结束时才被操作系统回收。

  • 如果程序结束时需要 释放 GPU 资源,请使用 cudaDeviceReset
  • 如果只需要确保 任务完成 而不重置设备状态,使用 cudaDeviceSynchronize

  • cudaDeviceSynchronize:同步 GPU 任务,确保执行完成,但不释放资源。
  • cudaDeviceReset:同步任务并释放 GPU 资源,重置设备状态。

两者不能完全互换,具体使用取决于程序对资源管理的需求。

4. CUDA 的应用场景

  • 科学计算:例如矩阵乘法、数值模拟等。
  • 深度学习:使用 CUDA 加速框架如 TensorFlowPyTorch
  • 图像处理:高性能图像滤波、边缘检测等。
  • 金融计算:蒙特卡洛模拟、风险分析等。
  • 物理仿真:粒子模拟、流体动力学等。

5. CUDA 生态系统

CUDA 提供了丰富的库和工具,方便开发者进行高性能编程:

  • cuBLAS:GPU 加速的线性代数库。
  • cuDNN:用于深度学习的加速库。
  • Thrust:类似于 C++ STL 的并行模板库。
  • Nsight:性能分析和调试工具。
  • NCCL:用于多 GPU 通信的库。

总结

CUDA 是一种强大的并行编程工具,通过利用 NVIDIA GPU 的计算能力,使开发者能够高效地加速各种计算任务。它通过扩展 C/C++ 语言,提供了易于上手的编程模型,广泛应用于科学计算、深度学习和高性能计算领域。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【十进制整数转换为其他进制数——短除形式的贪心算法】

之前写过一篇用贪心算法计算十进制转换二进制的方法&#xff0c;详见&#xff1a;用贪心算法计算十进制数转二进制数&#xff08;整数部分&#xff09;_短除法求二进制-CSDN博客 经过一段时间的研究&#xff0c;本人又发现两个规律&#xff1a; 1、不仅仅十进制整数转二进制可…

舵机SG90详解

舵机&#xff0c;也叫伺服电机&#xff0c;在嵌入式开发中&#xff0c;舵机作为一种常见的运动控制组件&#xff0c;具有广泛的应用。其中&#xff0c;SG90 舵机以其高效、稳定的性能特点&#xff0c;成为了许多工程师和爱好者的首选&#xff0c;无论是航模、云台、机器人、智能…

如何为IntelliJ IDEA配置JVM参数

在使用IntelliJ IDEA进行Java开发时&#xff0c;合理配置JVM参数对于优化项目性能和资源管理至关重要。IntelliJ IDEA提供了两种方便的方式来设置JVM参数&#xff0c;以确保你的应用程序能够在最佳状态下运行。本文将详细介绍这两种方法&#xff1a;通过工具栏编辑配置和通过服…

跌倒数据集,5345张图片, 使用yolo,coco json,voc xml格式进行标注,平均识别率99.5%以上

跌倒数据集&#xff0c;5345张图片&#xff0c; 使用yolo&#xff0c;coco json&#xff0c;voc xml格式进行标注&#xff0c;平均识别率99.5%以上 &#xff0c;可用于某些场景下识别人是否跌倒或摔倒并进行告警。 数据集分割 训练组99&#xff05; 5313图片 有效集0&am…

nods.js之nrm安装及使用

nods.js之nrm安装及使用 一、简介二、安装 nrm与使用三、报错解决 一、简介 nrm 是 Node.js 的一个工具&#xff0c;用于管理和切换 npm 源&#xff08;Registry&#xff09;。它使得在不同的 npm 镜像源之间切换变得非常容易&#xff0c;尤其对于那些经常因为网络问题或速度原…

selenium自动化测试基础知识

目录 一、概念知识 (一)三大核心组件 (二)Selenium 自动化测试的工作原理 (三)Selenium 支持的操作 (四)Selenium 自动化测试的优点 (五)Selenium 自动化测试的缺点 (六)Selenium 自动化测试的应用场景 总结 二、实操例子 使用前提--安装步骤 注意事项 (一)浏览器的…

Cisco Packet Tarcer配置计网实验笔记

文章目录 概要整体架构流程网络设备互连基础拓扑图拓扑说明配置步骤 RIP/OSPF混合路由拓扑图拓扑说明配置步骤 BGP协议拓扑图拓扑说明配置步骤 ACL访问控制拓扑图拓扑说明配置步骤 HSRP冗余网关拓扑图拓扑说明配置步骤 小结 概要 一些环境配置笔记 整体架构流程 网络设备互连…

RNN LSTM Seq2Seq Attention

非端到端&#xff1a; data -》 cleaning -》 feature Engining &#xff08;70%-80%工作 设计特征&#xff09;-》 分类器 -》预测 端到端 End-to-End&#xff1a; data -》 cleaning -》Deep learning&#xff08;表示学习&#xff0c;从数据中学习特征&#xff09; -》…

【AI日记】24.12.17 kaggle 比赛 2-6 | 把做饭看成一种游戏 | 咖喱牛肉

【AI论文解读】【AI知识点】【AI小项目】【AI战略思考】【AI日记】 工作 参加&#xff1a;kaggle 比赛 Regression with an Insurance Dataset时间&#xff1a;9 小时睡得好很重要 读书 书名&#xff1a;富兰克林自传时间&#xff1a;0.5 小时阅读原因&#xff1a;100 美元纸…

电脑为什么会提示“msvcr120.dll缺失”?“找不到msvcr120.dll文件”要怎么解决?

电脑故障排查指南&#xff1a;揭秘“msvcr120.dll缺失”的真相与解决方案 在软件开发与日常维护的广阔天地里&#xff0c;遇到系统报错或文件缺失的情况可谓家常便饭。今天&#xff0c;我将带领大家深入探讨一个常见的系统提示——“msvcr120.dll缺失”&#xff0c;并揭秘其背…

Kotlin复习

一、Kotlin类型 1.整数 2.浮点 显示转换&#xff1a; 所有数字类型都支持转换为其他类型&#xff0c;但是转换前会检测长度。 toByte(): Byte toShort(): Short toInt(): Int toLong(): Long toFloat(): Float toDouble(): Double 不同进制的数字表示方法&#xff08;为了提高…

【BUG】记一次context canceled的报错

文章目录 案例分析gorm源码解读gin context 生命周期context什么时候cancel的什么时候context会被动cancel掉呢&#xff1f; 野生协程如何处理 案例分析 报错信息 {"L":"ERROR","T":"2024-12-17T11:11:33.0050800","file"…

召回系统介绍

一、以Lucene为例介绍召回系统 1、倒排检索 Lucene的倒排索引由 Term Index -> TermDictionary -> Posting List 三层组成&#xff0c;倒排检索实际上就是通过分词Term查询到倒排拉链&#xff0c;然后对所有拉链进行合并。 Term-> Posting List&#xff0c;可以直接…

Ubuntu22.04系统下MVS运行海康威视工业相机

之前的开发环境是Ubuntu16.04&#xff0c;最近因项目需求换到了Ubuntu22.04系统&#xff0c;安装了ROS2-humble&#xff0c;重新记录下开发过程。 Ubuntu16.04系统可参考&#xff1a; VMware虚拟机中Ubuntu16.04系统下通过MVS运行海康威视工业相机 Linux环境中对海康威视工业相…

慧知开源充电桩平台 - OCPP充电桩协议越南充电平台:多语种支持、多元支付、本地化策略

越南充电新体验&#xff1a;多语种支持&#xff0c;便捷支付&#xff01; 助力充电桩运营本土化落地&#xff0c;为越南市场提供定制化解决方案 随着全球电动汽车市场的迅猛发展&#xff0c;越南作为东南亚新兴的汽车市场&#xff0c;对电动汽车充电基础设施的需求也在急剧增…

基于Clinical BERT的医疗知识图谱自动化构建方法,双层对比框架

基于Clinical BERT的医疗知识图谱自动化构建方法&#xff0c;双层对比框架 论文大纲理解1. 确认目标2. 目标-手段分析3. 实现步骤4. 金手指分析 全流程核心模式核心模式提取压缩后的系统描述核心创新点 数据分析第一步&#xff1a;数据收集第二步&#xff1a;规律挖掘第三步&am…

华为ensp--BGP路径选择-Preferred Value

学习新思想&#xff0c;争做新青年。今天学习的是BGP路径选择-Preferred Value 实验目的 理解BGP路由信息首选值&#xff08;Preferred Value&#xff09;的作用 掌握修改Preferred Value属性的方法 掌握通过修改Preferred Value属性来实现流量分担的方法 实验拓扑 实验要求…

如何在OpenCV中运行自定义OCR模型

我们首先介绍如何获取自定义OCR模型&#xff0c;然后介绍如何转换自己的OCR模型以便能够被opencv_dnn模块正确运行&#xff0c;最后我们将提供一些预先训练的模型。 训练你自己的 OCR 模型 此存储库是训练您自己的 OCR 模型的良好起点。在存储库中&#xff0c;MJSynthSynthTe…

“从零到一:揭秘操作系统的奇妙世界”【操作系统的发展】

1.手工操作阶段 此时没有OS&#xff0c;用户采用人工操作方式进行。 方式&#xff1a;程序员在纸带机上打孔---计算机读取---结果输出到纸袋机上---程序员取走结果 缺点&#xff1a;耗时长&#xff0c;难度大、用户独占全机、人机速度矛盾导致资源利用率低 2.单批道处理系统 引…

二叉树理论基础篇

这里写目录标题 二叉树的种类**满二叉树&#xff08;Full Binary Tree&#xff09;****完全二叉树&#xff08;Complete Binary Tree&#xff09;****二叉搜索树&#xff08;Binary Search Tree&#xff0c;BST&#xff09;**平衡二叉搜索树 二叉树的存储方式二叉树的遍历方式二…