第八章 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相关函数编写(global、device、__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 Memoryfor (int i = 0; i < 100; i++) { memory_host[i] = i * 100; }checkRuntime(cudaMemcpy(memory_device, memory_host, sizeof(float) * 100, cudaMemcpyHostToDevice)); // 返回的地址是开辟的device地址,存放在memory_deviceshow_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 MemorycheckRuntime(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 MemorycheckRuntime(cudaMallocHost(&memory_page_locked, 100 * sizeof(float))); // 返回的地址是被开辟的pin memory的地址,存放在memory_page_lockedcheckRuntime(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端间内存传输与分配相关原理,后面篇章将介绍如何定义常量内存、共享内存、纹理内存等相关方法,并附上相应代码。
相关文章:

第八章 CUDA内存应用与性能优化篇(上篇)
cuda教程目录 第一章 指针篇 第二章 CUDA原理篇 第三章 CUDA编译器环境配置篇 第四章 kernel函数基础篇 第五章 kernel索引(index)篇 第六章 kenel矩阵计算实战篇 第七章 kenel实战强化篇 第八章 CUDA内存应用与性能优化篇 第九章 CUDA原子(atomic)实战篇 第十章 CUDA流(strea…...

chrome浏览器改为黑色背景
chrome浏览器改为黑色背景 https://blog.csdn.net/yuchen_123456/article/details/127487278 不一样的地方:...
【AI】《动手学-深度学习-PyTorch版》笔记(十七):卷积神经网络入门
AI学习目录汇总 1、从全链接层到卷积 1.1 卷积 我们在前面学习的多层感知机中,已经认识了全链接层,缺点很明显,在稍微大点的网络模型中,参数成指数级别增长。参数量很快就达到数十亿,这样的量级几乎无法计算。为此科学家们想出一个减少参数的方法:卷积。 从全链接层到…...
element-ui table表格,根据缩放自适应
安装依赖 npm install af-table-columnmain.js 中引入依赖, import Vue from vue import ElementUI from element-ui //需要按需引入,先引入vue并引入element-ui import AFTableColumn from af-table-column Vue.use(AFTableColumn)demo样式࿱…...

【electron】electron安装过慢和打包报错:Unable to load file:
文章目录 一、安装过慢问题:二、打包报错:Unable to load file: 一、安装过慢问题: 一直处于安装过程 【解决】 #修改npm的配置文件 npm config edit#添加配置 electron_mirrorhttps://cdn.npm.taobao.org/dist/electron/二、打包报错:Unable to load…...

微服务部署中的动态扩缩容和故障迁移实践经验!快来看看!
随着微服务架构的快速普及,越来越多的组织开始将传统的单体应用转变为分布式的微服务系统。在这种架构下,动态扩缩容和故障迁移变得尤为重要,因为它们能够帮助我们应对不断变化的负载和故障情况。本文将详细介绍动态扩缩容和故障迁移的概念&a…...
代码随想录第四十五天
代码随想录第四十五天 Leetcode 70. 爬楼梯Leetcode 322. 零钱兑换Leetcode 279. 完全平方数 Leetcode 70. 爬楼梯 题目链接: 爬楼梯 自己的思路:之前是用斐波那契做的,但是现在学了完全背包,可以将m2拓展的更大一点,我们可以将楼顶n设为背包…...

Vue Baidu Map--自定义点图标bm-marker
自定义点图标 将准备好的图标放到项目中 使用import引入, 并在data中进行声明 <script> import mapIconRed from ./vue-baidu-map/img/marker_red_sprite.png export default {data() {return {mapIconRed,}}, } </script>在<bm-marker>中加入参…...
ZooKeeper的基本概念
集群角色 通常在分布式系统中,构成一个集群的每一台机器都有自己的角色,最典型的集群模式就是Master/Slave模式(主备模式)。在这种模式中,我们把能够处理所有写操作的机器称为Master机器,把所有通过异步复制方式获取最新数据&…...

SpringBoot复习:(51)默认情况下DataSource是怎么创建出来的,是什么类型的?
DataSource是通过DataSourceAutoConfiguration创建的,这个类代码如下: 可以看到DataSourceAutoConfiguration有个静态内部类PooledDataSourceConfiguration,在这个类上有个Import注解,导入了DataSourceConfiguration.Hikari这个类࿰…...

Python+Selenium自动化测试环境搭建步骤(selenium环境搭建)
一、自动化简介 1.自动化测试概念: 是把以人为驱动的测试转化为机器执行的一种过程,它是一种以程序测试程序的过程 2.自动化测试分类: 一般IT上所说的自动化测试是指功能自动化测试,通过编码的方式用一段程序来测试一个软件的功…...

实现简单纯Canvas文本输入框,新手适用
文章目录 概要效果技术细节代码 概要 Canvas上面提供输入: 一、最简单可能是用dom渲染一个input,覆盖在图形上面进行文本编辑,编辑完再把内容更新到图形.这样简单,但是缺点也明显,就是它不是真正绘制在canvas上面,没…...

React构建的JS优化思路
背景 之前个人博客搭建时,发现页面加载要5s才能完成并显示 问题 React生成的JS有1.4M,对于个人博客服务器的带宽来说,压力较大,因此耗费了5S的时间 优化思路 解决React生成的JS大小,因为我用的是react-router-dom…...

vim键盘图
国外:http://www.viemu.com/a_vi_vim_graphical_cheat_sheet_tutorial.html,原创,有SVG图,有分步骤的图。 国内翻译:[https://blog.csdn.net/qq_41052753/article/details/101031847 有几个配色,很高清&…...

【实战】十一、看板页面及任务组页面开发(一) —— React17+React Hook+TS4 最佳实践,仿 Jira 企业级项目(二十三)
文章目录 一、项目起航:项目初始化与配置二、React 与 Hook 应用:实现项目列表三、TS 应用:JS神助攻 - 强类型四、JWT、用户认证与异步请求五、CSS 其实很简单 - 用 CSS-in-JS 添加样式六、用户体验优化 - 加载中和错误状态处理七、Hook&…...

深入源码分析kubernetes informer机制(三)Resync
[阅读指南] 这是该系列第三篇 基于kubernetes 1.27 stage版本 为了方便阅读,后续所有代码均省略了错误处理及与关注逻辑无关的部分。 文章目录 为什么需要resyncresync做了什么 为什么需要resync 如果看过上一篇,大概能了解,client数据主要通…...

FL Studio 21最新for Windows-21.1.0.3267中文解锁版安装激活教程及更新日志
FL Studio 21最新版本for Windows 21.1.0.3267中文解锁版是最新强大的音乐制作工具。它可以与所有类型的音乐一起创作出令人惊叹的音乐。它提供了一个非常简单且用户友好的集成开发环境(IDE)来工作。这个完整的音乐工作站是由比利时公司 Image-Line 开发…...

HTML详解连载(4)
HTML详解连载(4) 专栏链接 [link](http://t.csdn.cn/xF0H3)下面进行专栏介绍 开始喽CSS定义书写位置示例注意 CSS引入方式内部样式表:学习使用 外部演示表:开发使用代码示例行内样式代码示例 选择器作用基础选择器标签选择器举例特…...

STM32 LL库+STM32CubeMX--点亮板载LED
一、前期准备 硬件:STM32F103C8T6开发板调试工具:DAPLink(本次使用)或USB-TTL开发环境:STM32CubeMX、Keil、Vscode(可选)板载LED:PC13(低电平点亮) 二、STM32CubeMX配置 1.选择芯片型号: 2.配置外设时钟:…...
【HBZ分享】ES的评分score机制的原理
score类型 基础评分boost,默认2.2,逆向文档频率值(IDF):表示该词再文档中(ES中)出现的次数越多,表示越不重要,评分越低关键词在文档中出现的频率(TF):表示该词在文档中出现的频率,频率越高表示…...

Linux 文件类型,目录与路径,文件与目录管理
文件类型 后面的字符表示文件类型标志 普通文件:-(纯文本文件,二进制文件,数据格式文件) 如文本文件、图片、程序文件等。 目录文件:d(directory) 用来存放其他文件或子目录。 设备…...

vscode(仍待补充)
写于2025 6.9 主包将加入vscode这个更权威的圈子 vscode的基本使用 侧边栏 vscode还能连接ssh? debug时使用的launch文件 1.task.json {"tasks": [{"type": "cppbuild","label": "C/C: gcc.exe 生成活动文件"…...

YSYX学习记录(八)
C语言,练习0: 先创建一个文件夹,我用的是物理机: 安装build-essential 练习1: 我注释掉了 #include <stdio.h> 出现下面错误 在你的文本编辑器中打开ex1文件,随机修改或删除一部分,之后…...
pam_env.so模块配置解析
在PAM(Pluggable Authentication Modules)配置中, /etc/pam.d/su 文件相关配置含义如下: 配置解析 auth required pam_env.so1. 字段分解 字段值说明模块类型auth认证类模块,负责验证用户身份&am…...

2021-03-15 iview一些问题
1.iview 在使用tree组件时,发现没有set类的方法,只有get,那么要改变tree值,只能遍历treeData,递归修改treeData的checked,发现无法更改,原因在于check模式下,子元素的勾选状态跟父节…...

UR 协作机器人「三剑客」:精密轻量担当(UR7e)、全能协作主力(UR12e)、重型任务专家(UR15)
UR协作机器人正以其卓越性能在现代制造业自动化中扮演重要角色。UR7e、UR12e和UR15通过创新技术和精准设计满足了不同行业的多样化需求。其中,UR15以其速度、精度及人工智能准备能力成为自动化领域的重要突破。UR7e和UR12e则在负载规格和市场定位上不断优化…...

SpringTask-03.入门案例
一.入门案例 启动类: package com.sky;import lombok.extern.slf4j.Slf4j; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cache.annotation.EnableCach…...

蓝桥杯3498 01串的熵
问题描述 对于一个长度为 23333333的 01 串, 如果其信息熵为 11625907.5798, 且 0 出现次数比 1 少, 那么这个 01 串中 0 出现了多少次? #include<iostream> #include<cmath> using namespace std;int n 23333333;int main() {//枚举 0 出现的次数//因…...

优选算法第十二讲:队列 + 宽搜 优先级队列
优选算法第十二讲:队列 宽搜 && 优先级队列 1.N叉树的层序遍历2.二叉树的锯齿型层序遍历3.二叉树最大宽度4.在每个树行中找最大值5.优先级队列 -- 最后一块石头的重量6.数据流中的第K大元素7.前K个高频单词8.数据流的中位数 1.N叉树的层序遍历 2.二叉树的锯…...
稳定币的深度剖析与展望
一、引言 在当今数字化浪潮席卷全球的时代,加密货币作为一种新兴的金融现象,正以前所未有的速度改变着我们对传统货币和金融体系的认知。然而,加密货币市场的高度波动性却成为了其广泛应用和普及的一大障碍。在这样的背景下,稳定…...