首页 > 编程语言 >一文学会CUDA编程:深入了解CUDA编程与架构(一)

一文学会CUDA编程:深入了解CUDA编程与架构(一)

时间:2024-08-04 18:24:49浏览次数:8  
标签:index 编程 架构 int float 线程 CUDA block

前言:

CUDA(Compute Unified Device Architecture,统一计算设备架构)是由NVIDIA公司开发的一种并行计算平台和编程模型。CUDA于2006年发布,旨在通过图形处理器(GPU)解决复杂的计算问题。在早期,GPU主要用于图像处理和游戏渲染,但随着技术的发展,其并行计算能力被广泛应用于科学计算、工程仿真、深度学习等领域。

CUDA的工作原理

CUDA的核心思想是将计算任务分配给GPU上的大量线程,这些线程可以并行地执行任务,从而实现高性能计算。CUDA将GPU划分为多个独立的计算单元,称为“流处理器”(Streaming Processor),这些流处理器可以独立地执行指令,互相加不干扰。

硬件层面

1、CUDA核心 (CUDA Core)

CUDA核心是执行线程计算的基本硬件单元。每个CUDA核心可以执行一个线程的计算任务。

图片

2、SM (Streaming Multiprocessor)

流多处理器 (SM) 是由多个CUDA核心组成的集成单元。每个SM负责管理和执行一个或多个线程块。SM内部有共享内存和缓存,用于加速数据访问和计算。

3、设备 (Device)

设备指的是整个GPU硬件。一个设备包含多个SM,能够处理大量并行计算任务。设备通过高带宽的内存和数据传输机制与主机(如CPU)进行数据交换。

图片

软件层面

1、线程 (Thread)

在CUDA编程中,线程是执行基本计算任务的最小单位。每个线程执行相同的程序代码,但可以处理不同的数据。

图片

2、线程块 (Thread Block)

线程块是由多个线程组成的集合。线程块中的线程可以共享数据,并且可以通过同步机制来协调彼此的工作。线程块的大小在程序执行时是固定的。

图片

3、网格 (Grid)

网格是由多个线程块组成的更大集合。网格中的所有线程块并行执行任务,网格的大小也在程序执行时固定。

图片

示例

实现两个向量相加 arr_c[] = arr_a[] +arr_b[]

#include <cuda.h>
#include <cuda_runtime_api.h>

#include <cmath>
#include <iostream>

#define CUDA_CHECK(call)                                           \
    {                                                              \
        const cudaError_t error = call;                            \
        if (error != cudaSuccess) {                                \
            fprintf(stderr, "Error: %s:%d, ", __FILE__, __LINE__); \
            fprintf(stderr, "code: %d, reason: %s\n", error,       \
                    cudaGetErrorString(error));                    \
            exit(1);                                               \
        }                                                          \
    }

__global__ void addKernel(float *pA, float *pB, float *pC, int size)
{
    int index = blockIdx.x * blockDim.x + threadIdx.x; // 计算当前数组中的索引
    if (index >= size)
        return;

    pC[index] = pA[index] + pB[index];
}

int main()
{
    float a[16] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
    float b[16] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};

    int arr_len = 16;

    float *dev_a, *dev_b, *dev_c;
    CUDA_CHECK(cudaMalloc(&dev_a, sizeof(float) * arr_len));
    CUDA_CHECK(cudaMalloc(&dev_b, sizeof(float) * arr_len));
    CUDA_CHECK(cudaMalloc(&dev_c, sizeof(float) * arr_len));

    CUDA_CHECK(cudaMemcpy(dev_a, a, sizeof(float) * arr_len, cudaMemcpyHostToDevice));
    CUDA_CHECK(cudaMemcpy(dev_b, b, sizeof(float) * arr_len, cudaMemcpyHostToDevice));

    int *count;
    CUDA_CHECK(cudaMalloc(&count, sizeof(int)));
    CUDA_CHECK(cudaMemset(count, 0, sizeof(int)));

    addKernel<<<arr_len + 512 - 1, 512>>>(dev_a, dev_b, dev_c, arr_len);

    float *output = (float *)malloc(arr_len * sizeof(float));

    CUDA_CHECK(cudaMemcpy(output, dev_c, sizeof(float) * arr_len, cudaMemcpyDeviceToHost));

    std::cout << " output add" << std::endl;
    for (int i = 0; i < arr_len; i++) {
        std::cout << " " << output[i];
    }
    std::cout << std::endl;

    return 0;
}

代码理解

addKernel<<<arr_len + 512 - 1, 512>>>

函数类型如下

addKernel<<<dim3 grid, dim3 block>>>

前面的表达等价于

addKernel<<<(dim3 grid(arr_len + 512 - 1), 1, 1), dim3 block(512, 1, 1)>>>

grid 与block 理解

假设只使用16个元素, arr_len =16

1、使用调整block的参数:

1.1只有x:

dim3 grid(1, 1, 1), block(arr_len, 1, 1); // 一个block里面有16个线程                   // 设置参数

图片

此时遍历的代码如下:

__global__ void addKernel(float *pA, float *pB, float *pC, int size){// block是一维的    int index = threadIdx.x; // 计算当前数组中的索引    if (index >= size)        return;    pC[index] = pA[index] + pB[index];}

1.2 含有x, y

dim3 grid(1, 1, 1), block(8, 2, 1); //每个block x方向有8个线程,总共2组。 

图片

__global__ void addKernel(float *pA, float *pB, float *pC, int size){  // block是二维的    int index = threadIdx.x + blockDim.x* threadIdx.y; // 计算当前数组中的索引    if (index >= size)        return;    pC[index] = pA[index] + pB[index];}

2、更改grid 参数

2.1 只更改x方向的参数

dim3 grid(16, 1, 1), block(1, 1, 1);   //还有16个block, 每个block就一个线程                 // 设置参数

图片

__global__ void addKernel(float *pA, float *pB, float *pC, int size){  // grid.x是一维的    int index = blockIdx.x; // 计算当前数组中的索引    if (index >= size)        return;    pC[index] = pA[index] + pB[index];}

3、grid, block参数都改

3.1 grid block各改一个

dim3 grid(4, 1, 1), block(4, 1, 1) // 代码还有4个x方向block, 每个block x方向有4个线程

图片

__global__ void addKernel(float *pA, float *pB, float *pC, int size){  // grid.x是一维的    int index = blockIdx.x*gridDim.x + threadIdx.x; // 计算当前数组中的索引    if (index >= size)        return;    pC[index] = pA[index] + pB[index];}

3.2 grid block更改两个

dim3 grid(2, 2, 1), block(2, 2, 1) // 代码还有2个X方向block,Y方向上有两组, 每个block x方向有2个线程, y方向上有两组

图片

__global__ void addKernel(float *pA, float *pB, float *pC, int size){      // 在第几个块中 * 块的大小 + 块中的x, y维度(几行几列)    int index = (blockIdx.y * gridDim.x + blockIdx.x) * (blockDim.x * blockDim.y) + threadIdx.y * blockDim.y + threadIdx.x;    if (index >= size)        return;    pC[index] = pA[index] + pB[index];}

总结

CUDA作为一种强大的并行计算平台和编程模型,极大地推动了高性能计算、深度学习等领域的快速发展。通过掌握CUDA,开发者可以充分利用GPU的并行计算能力,显著提升程序的运行效率和性能。无论是科学研究还是商业应用,CUDA都提供了广阔的可能性和机遇。

关注我的公众号auto_driver_ai(Ai fighting), 第一时间获取更新内容。

标签:index,编程,架构,int,float,线程,CUDA,block
From: https://blog.csdn.net/laukal/article/details/140833238

相关文章

  • Shell编程 --基础语法(2)
    文章目录Shell基础语法运算符算术运算符关系运算符bool运算符逻辑运算符字符串运算符文件测试运算符read命令printf命令总结ShellShell编程Shell是一种程序设计语言。作为命令语言,它交互式解释和执行用户输入的命令或者自动地解释和执行预先设定好的一连串的命令;......
  • 2024“钉耙编程”中国大学生算法设计超级联赛(4)
    题面:https://files.cnblogs.com/files/clrs97/%E7%AC%AC%E5%9B%9B%E5%9C%BA%E9%A2%98%E9%9D%A2.pdf题解:https://files.cnblogs.com/files/clrs97/%E7%AC%AC%E5%9B%9B%E5%9C%BA%E9%A2%98%E8%A7%A3.pdf Code:A.超维攻坚#include<cstdio>constintN=15,inf=~0U>>......
  • Continue-AI编程助手本地部署llama3.1+deepseek-coder-v2
    领先的开源人工智能代码助手。您可以连接任何模型和任何上下文,以在IDE内构建自定义自动完成和聊天体验推荐以下开源模型:聊天:llama3.1-8B推理代码:deepseek-coder-v2:16b嵌入模型nomic-embed-text模型默认存储路径:C:\Users\你的用户名\.ollama\models\blobs模型离线下......
  • ARM 架构硬件新趋势:嵌入式领域的未来
    目录目录一、ARM架构概述二、新趋势一:AI加速器集成三、新趋势二:更高效的电源管理四、新趋势三:安全性增强五、结语随着物联网(IoT)和边缘计算的发展,ARM架构在嵌入式系统中的应用越来越广泛。从智能手机到智能家居设备,ARM处理器因其低功耗、高性能的特点而备受青睐......
  • 架构知识点(二)
    轮询调度(RoundRobinScheduling)是一种时间片轮转调度算法,主要用于多任务系统中。其基本思想是将所有任务排成一个队列,每次调度时,系统会从队列中取出下一个任务执行,直到任务完成或达到其时间片限制。当任务的时间片用完后,该任务会被放回队列的末尾,等待下一次调度。轮询调度的特点......
  • 【Linux】网络架构探秘:网络层功能、IP协议详解及路由过程指南
    文章目录前言:1.网络层是干什么的?2.IP协议2.1理论铺垫2.2IP协议的头格式2.3网段划分(重点)2.3.1分类划分法:2.3.2子网掩码:2.3.3为什么要经行子网划分?2.4特殊的IP地址2.5IP地址的数量限制2.6私有IP地址和公网IP地址3.路由过程总结:前言:在当今数字化时代......
  • Python | 函数式编程
    文章目录1函数式编程2lamda表达式(匿名函数)3偏函数4闭包和自由变量5内置函数5.1map()函数5.2reduce()函数5.3filter()函数5.4sorted函数1函数式编程函数式编程(functionalprogramming)其实是个很古老的概念,诞生距今快60年啦!最古老的函数式编程语言Lisp......
  • 【C++核心篇】—— C++面向对象编程:封装相关语法使用和注意事项详解(全网最详细!!!)
    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档文章目录前言一、封装(类)1.封装的使用(类和对象)2.对象的初始化和清理2.1构造函数2.2析构函数2.3构造函数的分类及调用3.深拷贝与浅拷贝4.C++对象模型和this指针5.友元6.运算符重载前言在本篇......
  • Shell编程 --基础语法(1)
    文章目录Shell编程基础语法变量定义变量使用变量命令的使用只读变量删除变量传递参数字符串获取字符串长度字符串截取数组定义方式关联数组获取数组的长度总结Shell编程Shell是一种程序设计语言。作为命令语言,它交互式解释和执行用户输入的命令或者自动地解释和......
  • 【C++基础篇】—— 面向对象编程前的准备(内存分区,引用、函数重载)
    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档文章目录前言一、内存分区模型1.C++内存分区2.new操作符二、引用三、函数重载1.函数基本使用2.函数重载前言在本篇文章中,主要是对C++的基础语法进行回顾学习,回顾学习C++的基本语法规则、数据类型......