第八章 CUDA内存应用与性能优化篇(上篇)

cuda教程目录

第一章 指针篇
第二章 CUDA原理篇
第三章 CUDA编译器环境配置篇
第四章 kernel函数基础篇
第五章 kernel索引(index)篇
第六章 kenel矩阵计算实战篇
第七章 kenel实战强化篇
第八章 CUDA内存应用与性能优化篇
第九章 CUDA原子(atomic)实战篇
第十章 CUDA流(stream)实战篇
第十一章 CUDA的NMS算子实战篇
第十二章 YOLO的部署实战篇
第十三章 基于CUDA的YOLO部署实战篇

cuda教程背景

随着人工智能的发展与人才的内卷,很多企业已将深度学习算法的C++部署能力作为基本技能之一。面对诸多arm相关且资源有限的设备,往往想更好的提速,满足更高时效性,必将更多类似矩阵相关运算交给CUDA处理。同时,面对市场诸多教程与诸多博客岑子不起的教程或高昂教程费用,使读者(特别是小白)容易迷糊,无法快速入手CUDA编程,实现工程化。
因此,我将结合我的工程实战经验,我将在本专栏实现CUDA系列教程,帮助读者(或小白)实现CUDA工程化,掌握CUDA编程能力。学习我的教程专栏,你将绝对能实现CUDA工程化,完全从环境安装到CUDA核函数编程,从核函数到使用相关内存优化,从内存优化到深度学习算子开发(如:nms),从算子优化到模型(以yolo系列为基准)部署。最重要的是,我的教程将简单明了直切主题,CUDA理论与实战实例应用,并附相关代码,可直接上手实战。我的想法是掌握必要CUDA相关理论,去除非必须繁杂理论,实现CUDA算法应用开发,待进一步提高,将进一步理解更高深理论。

cuda教程内容

第一章到第三章探索指针在cuda函数中的作用与cuda相关原理及环境配置;

第四章初步探索cuda相关函数编写(globaldevice、__host__等),实现简单入门;

第五章探索不同grid与block配置,如何计算kernel函数的index,以便后续通过index实现各种运算;

第六、七章由浅入深探索核函数矩阵计算,深入探索grid、block与thread索引对kernel函数编写作用与影响,并实战多个应用列子(如:kernel函数实现图像颜色空间转换);

第八章探索cuda内存纹理内存、常量内存、全局内存等分配机制与内存实战应用(附代码),通过不同内存的使用来优化cuda计算性能;

第九章探索cuda原子(atomic)相关操作,并实战应用(如:获得某些自加索引等);

第十章探索cuda流stream相关应用,并给出相关实战列子(如:多流操作等);

第十一到十三章探索基于tensorrt部署yolo算法,我们首先将给出通用tensorrt的yolo算法部署,该部署的前后处理基于C++语言的host端实现,然后给出基于cuda的前后处理的算子核函数编写,最后数据无需在gpu与host间复制操作,实现gpu处理,提升算法性能。

目前,以上为我们的cuda教学全部内容,若后续读者有想了解知识,可留言,我们将根据实际情况,更新相关教学内容。

大神忽略

源码链接地址点击这里


文章目录

  • cuda教程目录
  • cuda教程背景
  • cuda教程内容
    • 源码链接地址[点击这里](https://github.com/tangjunjun966/cuda-tutorial-master)
  • 前言
  • 一、内存知识回顾
  • 二、GPU内存信息查询
  • 三、可分页内存与页锁定内存
  • 四、cudaMallocHost 和 cudaMalloc(可分页内存与页锁定内存)
    • 1、内存分配方式
      • Host端内存分配(Pageable Memory)
    • 2、分配的内存类型
    • 3、内存的使用方式
    • 4、内存的传输方式
    • 5、性能
    • 6、总结
  • 八、总结


前言

以上章节中,我们已经比较熟练掌握如何使用cuda编写自己想要的计算逻辑,已能成功编写cuda代码了。 那么,另外一个重要问题值得我们关注,如何优化其性能,使其工程部署能加速运行了。而这种性能优化与cuda内存密切相关。为此,我们在本节中介绍cuda内存相关内容,并附其源码。


一、内存知识回顾

我再次简单回顾下相关内存概念,详细内容可看我第二章内容(我个人局的还是重要)链接点击这里。

Registers:寄存器是GPU中最快的memory,kernel中没有什么特殊声明的自动变量都是放在寄存器中的。当数组的索引是constant类型且在编译期能被确定的话,就是内置类型,数组也是放在寄存器中。 寄存器变量是每个线程私有的,一旦thread执行结束,寄存器变量就会失效。

Shared Memory:用__shared__修饰符修饰的变量存放在shared memory中。Shared Memory位于GPU芯片上,访问延迟仅次于寄存器。所有Thread来进行访问的,可以实现Block内的线程间的低开销通信。 要使用__syncthread()同步。

Local Memory:本身在硬件中没有特定的存储单元,而是从Global Memory虚拟出来的地址空间。是为寄存器无法满足存储需求的情况而设计的,主要是用于存放单线程的大型数组和变量。Local Memory是线程私有的,线程之间是不可见的。它的访问是比较慢的,跟Global Memory的访问速度是接近的。使用情景,无法确定其索引是否为常量的数组;会消耗太多寄存器空间的大型结构或数组;如果内核使用了多于寄存器的任何变量(这也称为寄存器溢出);

Constant Memory:固定内存空间驻留在设备内存中,并缓存在固定缓存中(constant cache),范围是全局的,针对所有kernel; kernel只能从constant Memory中读取数据,因此其初始化必须在host端使用下面的function调用:cudaError_t cudaMemcpyToSymbol(const void* symbol,const void* src,size_t count); 当一个warp中所有线程都从同一个Memory地址读取数据时,constant Memory表现会非常好,会触发广播机制。

Global Memory:Global Memory在某种意义上等同于GPU显存,kernel函数通过Global Memory来读写显存。Global Memory是kernel函数输入数据和写入结果的唯一来源。

Texture Memory:是GPU的重要特性之一,也是GPU编程优化的关键。Texture Memory实际上也是Global Memory的一部分,但是它有自己专用的只读cache。这个cache在浮点运算很有用,Texture Memory是针对2D空间局部性的优化策略,所以thread要获取2D数据就可以使用texture Memory来达到很高的性能。从读取性能的角度跟Constant Memory类似。

Host Memory:主机端存储器主要是内存可以分为两类:可分页内存(Pageable)和页面 (Page-Locked 或 Pinned)内存。可分页内存通过操作系统 API(malloc/free) 分配存储器空间,该内存是可以换页的,即内存页可以被置换到磁盘中。可分页内存是不可用使用DMA(Direct Memory Acess)来进行访问的,普通的C程序使用的内存就是这个内存。

二、GPU内存信息查询

代码如下:

int inquire_GPU_info() {
    int deviceCount;
    cudaGetDeviceCount(&deviceCount);

    int dev;
    for (dev = 0; dev < deviceCount; dev++)
    {
        int driver_version(0), runtime_version(0);
        cudaDeviceProp deviceProp;
        cudaGetDeviceProperties(&deviceProp, dev);
        if (dev == 0)
            if (deviceProp.minor = 9999 && deviceProp.major == 9999)
                printf("\n");
        printf("\nDevice%d:\"%s\"\n", dev, deviceProp.name);
        cudaDriverGetVersion(&driver_version);
        printf("CUDA驱动版本:                                         %d.%d\n", driver_version / 1000, (driver_version % 1000) / 10);
        cudaRuntimeGetVersion(&runtime_version);
        printf("CUDA运行时版本:                                       %d.%d\n", runtime_version / 1000, (runtime_version % 1000) / 10);
        printf("设备计算能力:                                         %d.%d\n", deviceProp.major, deviceProp.minor);
        printf("设备全局内存总量 Global Memory:                       %u M\n", deviceProp.totalGlobalMem/(1024*1024));
        printf("Number of SMs:                                        %d\n", deviceProp.multiProcessorCount);
        printf("常量内存 Constant Memory:                             %u K\n", deviceProp.totalConstMem/1024);
        printf("每个block的共享内存 Shared Memory:                    %u K\n", deviceProp.sharedMemPerBlock/1024);
        printf("每个block的寄存器 registers :                         %d\n", deviceProp.regsPerBlock);
        printf("线程束Warp size:                                      %d\n", deviceProp.warpSize);
        printf("每个SM的最大线程数 threads per SM:                    %d\n", deviceProp.maxThreadsPerMultiProcessor);
        printf("每个block的最大线程数 threads per block:              %d\n", deviceProp.maxThreadsPerBlock);
        printf("每个block的最大维度 each dimension of a block:        %d x %d x %d\n", deviceProp.maxThreadsDim[0],     deviceProp.maxThreadsDim[1],  deviceProp.maxThreadsDim[2]);
        printf("每个grid的最大维度 dimension of a grid:               %d x %d x %d\n", deviceProp.maxGridSize[0], deviceProp.maxGridSize[1], deviceProp.maxGridSize[2]);
        printf("Maximum memory pitch:                                 %u bytes\n", deviceProp.memPitch);
        printf("Texture alignmemt:                                    %u bytes\n", deviceProp.texturePitchAlignment);
        printf("Clock rate:                                           %.2f GHz\n", deviceProp.clockRate * 1e-6f);
        printf("Memory Clock rate:                                    %.0f MHz\n", deviceProp.memoryClockRate * 1e-3f);
        printf("Memory Bus Width:                                     %d-bit\n", deviceProp.memoryBusWidth);
    }

    return 0;
}
}

查询结果显示如下:
在这里插入图片描述

三、可分页内存与页锁定内存

CPU内存,称之为Host Memory,逻辑上可分为Pageable Memory(可分页内存)、Page Lock Memory(页锁定内存),Page Lock Memory又称为Pinned Memory,从字面意思上而言Page Lock Memory是锁定的内存,一旦申请后就专供申请者使用,Pageable Memory则没有锁定特性,申请后可能会被交换。

总结如下:
①、pinned memory具有锁定特性,是稳定不会被交换的;
pageable memory没有锁定特性,对于第三方设备(比如GPU),去访问时,因为无法感知内存是否被交换,可能得不到正确的数据;
②、pageable memory的性能比pinned memory差,很可能降低你程序的优先级然后把内存交换给别人用;
pageable memory策略使用内存假象,实际8GB但是可以使用15GB,可以提高程序运行数量,但运行速度会降低;
pinned memory太多,会导致操作系统整体性能降低,因为程序运行数量减少了;
③、GPU可以直接访问pinned memory而不能访问pageable memory(因为第二条)。
说明:当将pageable host Memory数据送到device时,CUDA驱动会首先分配一个临时的page-locked或者pinned host Memory,并将host的数据放到这个临时空间里。然后GPU从这个所谓的pinned Memory中获取数据,如下图所示:

在这里插入图片描述

四、cudaMallocHost 和 cudaMalloc(可分页内存与页锁定内存)

之前章节一直以实例介绍cuda代码编写,也对host与device端的变量进行了内存分配,并未重点说明cudaMallocHost 和 cudaMalloc的使用方法,我个人觉得很重要,对于不同设备的数据传输(如host与GPU间)均需要使用复制方法,而针对GPU内存分配与CPU数据间关系,需要我们有更深入了解,在此,我介绍重点介绍一下。

1、内存分配方式

以上介绍gpu如何访问cpu的内存方式。对于给GPU访问而言,距离计算单元越近,内存访问效率越高。为此,由低到高访问速度为:Pinned Memory < Global Memory < Shared Memory
重点说明,GPU可以直接访问Pinned Memory,称之为DMA Direct Memory Access

接下来,我将介绍实际内存分配的几种方式:

Host端内存分配(Pageable Memory)

之前代码使用cudaMallocHost对内存分配,但也可使用new或malloc分配,而该分配属于Pageable Memory可分页内存。
其分配内存代码如下:

 std::cout << "设置new(malloc)可分页内存" << std::endl;
    float* memory_device = nullptr;
    float* memory_host = new float[100]; // Pageable Memory
    for (int i = 0; i < 100; i++) { memory_host[i] = i * 100; }
    checkRuntime(cudaMemcpy(memory_device, memory_host, sizeof(float) * 100, cudaMemcpyHostToDevice)); // 返回的地址是开辟的device地址,存放在memory_device
    show_value << <dim3(1), dim3(100) >> > (memory_device);

以上直接使用CPU分配内存,然后使用cudaMemcpy复制给memory_device中,仍然可以实现,然这种效率较低。但切记,这种memory_host可以直接使用new赋值在核函数中使用。同时,这种速度较慢,不建议使用。
预测结果显示(仅显示前10个数)如下:

在这里插入图片描述

2、分配的内存类型

cudaMallocHost 分配的内存是页锁定内存,而 cudaMalloc 分配的内存是普通可分页内存

3、内存的使用方式

cudaMallocHost 分配的内存可以通过主机和设备访问,而 cudaMalloc 分配的内存只能通过设备访问。
cudaMalloc分配内存方式为GPU的全局内存,代码如下:

	std::cout << "设置全局内存"  << std::endl;
    float* memory_device = nullptr; // Global Memory
    checkRuntime(cudaMalloc(&memory_device, 100 * sizeof(float))); // pointer to device

以上代码实际在前面章节中已大量使用,实际作用为:使用cudaMalloc在gpu设备上分配一个全局内存空间,便于在kernel计算中存储数据。
cudaMallocHost分配内存方式,代码如下:

	std::cout << "设置页锁定内存" << std::endl;
    float* memory_device = nullptr;
    float* memory_page_locked = nullptr; // Pinned Memory
    checkRuntime(cudaMallocHost(&memory_page_locked, 100 * sizeof(float))); // 返回的地址是被开辟的pin memory的地址,存放在memory_page_locked
    checkRuntime(cudaMemcpy(memory_page_locked, memory_device, sizeof(float) * 100, cudaMemcpyDeviceToHost)); // 将其返回host内存

以上代码实际在前面章节中已大量使用,实际作用也就是上面解释,即使用cudaMallocHost在gpu设备上分配内存,可使主机host和设备device均可访问,并使用cudaMemcpy赋值gpu数据。

4、内存的传输方式

由于 cudaMallocHost 分配的内存可以通过主机和设备访问,因此可以通过零拷贝技术(Zero-Copy)将数据直接从主机内存传输到设备内存,而 cudaMalloc 分配的内存则需要使用显式的数据传输函数(如 cudaMemcpy)进行传输。

5、性能

由于 cudaMallocHost 分配的内存是页锁定内存,因此可以避免在主机和设备之间进行数据传输时产生额外的复制操作,从而提高数据传输的性能。

6、总结

尽量多用Pinned Memory储存host端数据,或者显式处理Host到Device时用PinnedMemory做缓存,都是提高性能的关键。因此,如果需要在主机和设备之间进行频繁的数据传输,建议使用 cudaMallocHost 分配内存。如果只需要在设备上进行计算,并且不需要频繁地与主机进行数据交换,则可以使用 cudaMalloc 分配内存。

八、总结

以上为cuda内存应用与性能优化篇关于内存的相关知识和简单代码说明,旨在掌握在host端与device端间内存传输与分配相关原理,后面篇章将介绍如何定义常量内存、共享内存、纹理内存等相关方法,并附上相应代码。

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

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

相关文章

0基础学C#笔记09:希尔排序法

文章目录 前言一、希尔排序的思想二、使用步骤总结 前言 希尔排序可以说是插入排序的一种变种。无论是插入排序还是冒泡排序&#xff0c;如果数组的最大值刚好是在第一位&#xff0c;要将它挪到正确的位置就需要 n - 1 次移动。也就是说&#xff0c;原数组的一个元素如果距离它…

优维低代码实践:自定义模板

优维低代码技术专栏&#xff0c;是一个全新的、技术为主的专栏&#xff0c;由优维技术委员会成员执笔&#xff0c;基于优维7年低代码技术研发及运维成果&#xff0c;主要介绍低代码相关的技术原理及架构逻辑&#xff0c;目的是给广大运维人提供一个技术交流与学习的平台。 优维…

此文详解,数据仓库管理建设的经验

目前由于数据分散在不同的存储环境或数据库中&#xff0c;对于新业务需求的开发需要人工先从不同的数据库中同步、集中、合并等处理&#xff0c;造成资源和人力的浪费。同时&#xff0c;目前的系统架构&#xff0c;无法为未来数据驱动业务创新的理念提供友好的支撑。需要建设新…

如何构建一个 NodeJS 影院微服务并使用 Docker 部署

文章目录 前言什么是微服务&#xff1f;构建电影目录微服务构建微服务从 NodeJS 连接到 MongoDB 数据库总结 前言 如何构建一个 NodeJS 影院微服务并使用 Docker 部署。在这个系列中&#xff0c;将构建一个 NodeJS 微服务&#xff0c;并使用 Docker Swarm 集群进行部署。 以下…

d3dcompiler43.dll缺失怎么修复?dll缺失解决方法分享

在使用电脑过程中&#xff0c;我们有时会遇到一些系统文件的问题&#xff0c;其中一个常见的问题是d3dcompiler43.dll文件的损坏或丢失。当这个文件出现问题时&#xff0c;可能会导致应用程序无法正常运行或图形渲染出现异常。最近我也遇到了这个问题&#xff0c;以下是我修复d…

安达发APS|生产计划排产软件助力加工制造业智能化转型

随着全球经济一体化的不断深入&#xff0c;市场竞争日益激烈&#xff0c;加工制造企业面临着巨大的生存压力。在这种情况下&#xff0c;企业对于生产计划的精细化管理需求日益迫切。为了适应这一市场需求&#xff0c;安达发推出了专门针对加工企业的APS生产计划排产软件&#x…

机器学习实战5-KMeans聚类算法

文章目录 概述KMeansKMeans参数&接口n_clusters质心inertia模型评估指标轮廓系数Calinski-Harabaz Index 重要参数init & random_state & n_init&#xff1a;初始质心怎么放好?重要参数max_iter & tol&#xff1a;让迭代停下来重要属性与重要接口 概述 聚类 …

Linux之awk判断和循环

echo zhaoy 70 72 74 76 74 72 >> score.txt echo wangl 70 81 84 82 90 88 >> score.txt echo qiane 60 62 64 66 65 62 >> score.txt echo sunw 80 83 84 85 84 85 >> score.txt echo lixi 96 80 90 95 89 87 >> score.txt把下边的内容写入到s…

大语言模型(LLM)与 Jupyter 连接起来了

现在&#xff0c;大语言模型&#xff08;LLM&#xff09;与 Jupyter 连接起来了&#xff01; 这主要归功于一个名叫 Jupyter AI 的项目&#xff0c;它是官方支持的 Project Jupyter 子项目。目前该项目已经完全开源&#xff0c;其连接的模型主要来自 AI21、Anthropic、AWS、Co…

优雅地处理RabbitMQ中的消息丢失

目录 一、异常处理 二、消息重试机制 三、错误日志记录 四、死信队列 五、监控与告警 优雅地处理RabbitMQ中的消息丢失对于构建可靠的消息系统至关重要。下面将介绍一些优雅处理消息丢失的方案&#xff0c;包括异常处理、重试机制、错误日志记录、死信队列和监控告警等。…

Go Gin 中使用 JWT

一、JWT JWT全称JSON Web Token是一种跨域认证解决方案&#xff0c;属于一个开放的标准&#xff0c;它规定了一种Token实现方式&#xff0c;目前多用于前后端分离项目和OAuth2.0业务场景下。 二、为什么要用在你的Gin中使用JWT 传统的Cookie-Sesson模式占用服务器内存, 拓展性…

QT 使用第三方库QtXlsx操作Excel表

1.简介 一直以来&#xff0c;都想学习一下C/C如何操作excel表&#xff0c;在网上调研了一下&#xff0c;觉得使用C/C去操作很麻烦&#xff0c;遂转向QT这边&#xff1b;QT有一个自带的类QAxObject&#xff0c;可以使用他去操作&#xff0c;但随着了解的深入&#xff0c;觉得他…

C++初阶之模板深化讲解

模板深化讲解 非类型模板模板的特化1.函数模板特化2.类模板特化 模板分离编译1.什么是分离编译2.模板的分离编译 模板总结 非类型模板 非类型模板&#xff08;Non-Type Template&#xff09;是 C 中的一种模板形式&#xff0c;它允许你在模板中传递除了类型以外的其他值&#x…

ESP 系列的产品 ULP 协处理器的应用

参考文档&#xff1a; 《ESP32-S2 技术参考手册》 中 “1. 超低功耗协处理器 (ULP)” 章节《ESP32-S3 技术参考手册》 中 “2 超低功耗协处理器 (ULPFSM, ULPRISCV)” 章节《ESP32-C6 技术参考手册》 中 “3 低功耗处理器” 章节ULP 协处理器编程ULP RISC-V 协处理器编程Progr…

Mac下⬇️Git如何下载/上传远程仓库

使用终端检查电脑是否安装Git git --version 通过此文章安装Git ➡️ ​​​​​​​传送门&#x1f310; 方式1⃣️使用终端操作 1.下载——克隆远程仓库到本地 git clone [远程地址] 例&#xff1a;git clone https://gitee.com/lcannal/movie.git​ 2.编…

Java课题笔记~ JSP开发模型

MVC 1.JSP演化历史 1. 早期只有servlet&#xff0c;只能使用response输出标签数据&#xff0c;非常麻烦 2. 后来有了jsp&#xff0c;简化了Servlet的开发&#xff0c;如果过度使用jsp&#xff0c;在jsp中即写大量的java代码&#xff0c;有写html表&#xff0c;造成难于维护&…

【校招VIP】前端JS语言考点之px rem等单位

考点介绍&#xff1a; rem vm等问题是前端面试里的高频题型。但是不少同学并不能很清楚的说明为什么在有px单位之后&#xff0c;还需要rem单位&#xff1f;往往会往不对的自适应方向回答。 作为基础性问题&#xff0c;只要回答不出来&#xff0c;面试就通过不了&#xff0c;需要…

compile_and_runtime_not_namespaced_r_class_jar\debug\R.jar: 另一个程序正在使用

问题情况&#xff1a; run App的时候&#xff0c;提示该文件被占用 想要clean Project&#xff0c;还是提示该文件被占用&#xff0c;这个文件和连带的文件夹都无法被删除。 方法1&#xff1a; AndroidStudio下方的terminal&#xff08;没有这个窗口的话&#xff0c;从上面的…

【JAVA基础】- 同步非阻塞模式NIO详解

【JAVA基础】- 同步非阻塞模式NIO详解 文章目录 【JAVA基础】- 同步非阻塞模式NIO详解一、概述二、常用概念三、NIO的实现原理四、NIO代码实现客户端实现服务端实现 五、同步非阻塞NIO总结 一、概述 NIO&#xff08;Non-Blocking IO&#xff09;是同步非阻塞方式来处理IO数据。…

【Spring Boot】构建RESTful服务 — RESTful简介

RESTful简介 本节将从基础的概念开始介绍什么是RESTful、RESTful的特点、RESTful中的资源、HTTP Method、HTTP Status&#xff0c;还将介绍RESTful和SOAP到底有哪些区别。 1.什么是RESTful RESTful是目前流行的互联网软件服务架构设计风格。REST&#xff08;Representationa…