当前位置: 首页 > news >正文

cuda编程笔记(6)--流

cudaHostAlloc

cudaHostAlloc 是 CUDA 提供的用于在 主机端申请页锁定内存(pinned memory) 的函数

cudaHostAlloc()malloc() 的核心区别

特性/方面malloc()(标准 C)cudaHostAlloc()(CUDA)
内存类型普通主机内存(分页内存)页锁定内存(pinned memory)
是否支持 DMA 传输❌ 需要复制缓冲区✅ 可直接使用 DMA 提升带宽
是否支持异步传输❌ 不支持✅ 支持与 CUDA 流异步 cudaMemcpyAsync
是否支持映射给 GPU❌ 不支持✅ 可映射到设备端地址空间(取决于 flag)
释放函数free()cudaFreeHost()
是否影响主机调度是(锁页后主机无法交换该页)

cudaHostAlloc() 函数原型与参数

cudaError_t cudaHostAlloc(void** ptr, size_t size, unsigned int flags);

参数说明:

  • void** ptr:返回页锁定主机内存的地址;

  • size:申请的字节数;

  • flags:控制内存行为,常见值如下:

Flag含义
cudaHostAllocDefault默认锁页内存,适用于异步传输
cudaHostAllocMapped允许内存映射到设备(即 GPU 可以直接访问)
cudaHostAllocWriteCombined禁止读优化,仅用于主机写后设备读,提高性能
cudaHostAllocPortable多 GPU 上可见(跨 context 使用)

 什么是页锁定的内存

页锁定内存(pinned memory)是指不会被操作系统虚拟内存管理机制移到磁盘的主机内存
它是物理内存中被“锁住”的一段区域,可用于GPU 的直接访问(DMA)和异步数据传输

在操作系统中,主机内存通常是分页(pageable)管理的:

  • 普通内存(如 malloc() 得到的):是可分页的

    • 如果内存长时间未访问,操作系统可能把它换出到磁盘(swap),释放物理内存。

  • 页锁定内存(如 cudaHostAlloc() 得到的):是不可分页的

    • 它始终驻留在主机物理内存中,不能被 swap 掉。

为什么这很重要?
因为 GPU 想要高效读取主机内存时,必须使用 DMA(直接内存访问),而 DMA 不能访问“随时可能被换出”的普通内存。

优势说明
✅ 支持 异步内存传输允许 cudaMemcpyAsync 与 kernel 并发
✅ 支持 更高带宽的内存传输通过 DMA 直连传输,避免临时缓存
✅ 可被 GPU 映射访问如使用 cudaHostAllocMapped
✅ 节省系统临时复制开销避免内部“拷贝到临时 pinned 缓冲区”的中间过程
问题原因与建议
❗ pinned memory 会限制系统物理内存不应大规模分配,避免影响主机性能
🔄 必须用 cudaFreeHost() 释放不要用 free()
❗ 必须配合流和 cudaMemcpyAsync 才能异步传输否则性能不会提升

 速度测试


#include <cuda_runtime.h>
#include <device_launch_parameters.h>#include <iostream>
#include<cstdio>
#define SIZE 10*1024*1024
void error_handling(cudaError_t res) {if (res !=cudaSuccess) {std::cout << "error!" << std::endl;}
}
//up是内存分配的方向
float cuda_malloc_test(int size, bool up) {cudaEvent_t start, stop;int* a, * dev_a;float elapsedTime;error_handling(cudaEventCreate(&start));error_handling(cudaEventCreate(&stop));a = new int[size];error_handling(cudaMalloc((void**)&dev_a,size*sizeof(int)));error_handling(cudaEventRecord(start,0));//执行100复制操作for (int i = 0; i < 100; i++) {if (up) {//主机到设备error_handling(cudaMemcpy(dev_a, a, size * sizeof(int), cudaMemcpyHostToDevice));}else {//设备到主机error_handling(cudaMemcpy(a, dev_a, size * sizeof(int), cudaMemcpyDeviceToHost));}}error_handling(cudaEventRecord(stop, 0));error_handling(cudaEventSynchronize(stop));error_handling(cudaEventElapsedTime(&elapsedTime, start, stop));delete[]a;error_handling(cudaFree(dev_a));error_handling(cudaEventDestroy(start));error_handling(cudaEventDestroy(stop));return elapsedTime;
}
float cuda_host_malloc_test(int size, bool up) {cudaEvent_t start, stop;int* a, * dev_a;float elapsedTime;error_handling(cudaEventCreate(&start));error_handling(cudaEventCreate(&stop));//error_handling(cudaHostAlloc((void**)&a, size * sizeof(int),cudaHostAllocDefault));//error_handling(cudaMalloc((void**)&dev_a, size * sizeof(int)));error_handling(cudaEventRecord(start, 0));//执行100复制操作for (int i = 0; i < 100; i++) {if (up) {//主机到设备error_handling(cudaMemcpy(dev_a, a, size * sizeof(int), cudaMemcpyHostToDevice));}else {//设备到主机error_handling(cudaMemcpy(a, dev_a, size * sizeof(int), cudaMemcpyDeviceToHost));}}error_handling(cudaEventRecord(stop, 0));error_handling(cudaEventSynchronize(stop));error_handling(cudaEventElapsedTime(&elapsedTime, start, stop));error_handling(cudaFreeHost(a));error_handling(cudaFree(dev_a));error_handling(cudaEventDestroy(start));error_handling(cudaEventDestroy(stop));return elapsedTime;
}
int main() {float elapsedTime;float MB = (float)100 * SIZE * sizeof(int) / 1024/1024;//可以自行修改调用的函数,进行速度测试elapsedTime = cuda_malloc_test(SIZE, true);printf("Time using cudaMalloc: %3.1f ms\n", elapsedTime);printf("\tMB/s during copy up:  %3.1f\n", MB / (elapsedTime / 1000));
}

CUDA 中采用 流(stream) 能带来性能提升的核心原因是:

流允许数据传输(拷贝)和 GPU 计算(kernel) 之间 并行 / 重叠执行,最大化利用 CPU、PCIe 带宽、GPU 核心 等多个资源。

默认情况下(不使用流),操作是串行执行的,比如:

cudaMemcpy(...);               // 主机到设备拷贝 (同步,阻塞)
my_kernel<<<...>>>(...);      // kernel 启动
cudaMemcpy(...);               // 设备到主机拷贝 (同步)

这些操作会:

  •  一个结束后才开始下一个(传输和计算不能并发);

  • 阻塞 CPU,无法做其他事情;

  • GPU 计算资源闲置(等数据);

  •  PCIe 传输通道利用率低。

流可以带来哪些 并发重叠

传输和计算重叠

通过 流 + pinned memory + async copy,可以做到:

  • stream1 中异步拷贝 A 到 GPU;

  • 同时 stream2 中 GPU 正在处理 B 数据;

  • 然后把 B 结果拷回主机的同时拷入 C 数据……

这称为 pipeline 式数据处理

时间线Stream 1Stream 2Stream 3
T1HtoD A
T2Kernel AHtoD B
T3DtoH AKernel BHtoD C
T4DtoH BKernel C
T5DtoH C

这样:数据传输 和 GPU 计算可以重叠,整体吞吐量大幅提升。

多核函数并发调度

多个独立 kernel(如多个图像处理任务),如果:

  • 都用默认流:会串行排队;

  • 用不同的流:调度器会并行启动它们!

前提是设备支持并发执行(Concurrent Kernel Execution)

什么条件下流才能提升效果?

条件是否必须说明
✔ 使用 cudaMemcpyAsync✅ 必须否则不会异步执行
✔ 主机内存是 pinned memory✅ 必须普通 malloc 内存不支持异步
✔ 操作放在非默认流上✅ 必须默认流(stream 0)会阻塞其他流
✔ 设备支持并发(多数支持)✅ 强烈建议多流多 kernel 才能并行调度
❌ 没有内存访问依赖关系✅ 最佳否则执行顺序会被限制

 代码示例


#include <cuda_runtime.h>
#include <device_launch_parameters.h>#include <iostream>
#include<cstdio>
const int N = 1024 * 1024;
#define FULL_DATA_SIZE N*20
void error_handling(cudaError_t res) {if (res !=cudaSuccess) {std::cout << "error!" << std::endl;}
}
//核函数不关键,可以不看
__global__ void kernel(int* a, int* b, int* c) {int idx = threadIdx.x + blockIdx.x * blockDim.x;if (idx < N) {int idx1 = (idx + 1) % 256;int idx2 = (idx + 2) % 256;float as = (a[idx] + a[idx1] + a[idx2]) / 3.0f;float bs = (b[idx] + b[idx1] + b[idx2]) / 3.0f;c[idx] = (as + bs) / 2;}
}
int main() {cudaDeviceProp prop;int whichDevice;error_handling(cudaGetDevice(&whichDevice));error_handling(cudaGetDeviceProperties_v2(&prop, whichDevice));if (!prop.deviceOverlap) {//选择一个支持设备重叠功能的设备printf("Device will not handle overlappps,so no spped up from streams\n");return 0;}cudaEvent_t start, stop;float elappsedTime;//启动计时器error_handling(cudaEventCreate(&start));error_handling(cudaEventCreate(&stop));error_handling(cudaEventRecord(start, 0));//初始化流cudaStream_t stream;error_handling(cudaStreamCreate(&stream));int* host_a, * host_b, * host_c;int* dev_a, * dev_b, * dev_c;//分配设备内存error_handling(cudaMalloc((void**)&dev_a,N*sizeof(int)));error_handling(cudaMalloc((void**)&dev_b, N * sizeof(int)));error_handling(cudaMalloc((void**)&dev_c, N * sizeof(int)));//分配由流使用的页锁定内存error_handling(cudaHostAlloc((void**)&host_a, FULL_DATA_SIZE * sizeof(int), cudaHostAllocDefault));error_handling(cudaHostAlloc((void**)&host_b, FULL_DATA_SIZE * sizeof(int), cudaHostAllocDefault));error_handling(cudaHostAlloc((void**)&host_c, FULL_DATA_SIZE * sizeof(int), cudaHostAllocDefault));for (int i = 0; i < FULL_DATA_SIZE; i++) {host_a[i] = rand();host_b[i] = rand();}//每一批做三件事,都通过同一个 stream 异步调度:for (int i = 0; i < FULL_DATA_SIZE; i += N) {error_handling(cudaMemcpyAsync(dev_a, host_a + i, N * sizeof(int), cudaMemcpyHostToDevice, stream));error_handling(cudaMemcpyAsync(dev_b, host_b + i, N * sizeof(int), cudaMemcpyHostToDevice, stream));kernel << <N / 256, 256, 0, stream >> > (dev_a, dev_b, dev_c);error_handling(cudaMemcpyAsync(host_c + i, dev_c, N * sizeof(int), cudaMemcpyHostToDevice, stream));}error_handling(cudaStreamSynchronize(stream));error_handling(cudaEventRecord(stop, 0));error_handling(cudaEventSynchronize(stop));error_handling(cudaEventElapsedTime(&elappsedTime, start, stop));printf("Time taken : %31f ms\n", elappsedTime);error_handling(cudaFreeHost(host_a));error_handling(cudaFreeHost(host_b));error_handling(cudaFreeHost(host_c));error_handling(cudaFree(dev_a));error_handling(cudaFree(dev_b));error_handling(cudaFree(dev_c));error_handling(cudaStreamDestroy(stream));error_handling(cudaEventDestroy(start));error_handling(cudaEventDestroy(stop));
}

多流 pipelining(真正 overlap)

目前所有操作都用一个 stream,所以 一个 batch 的三个操作仍然串行排队
可以使用多个 stream 轮流调度每个 batch

cudaStream_t streams[4];
for (int i = 0; i < 4; ++i)cudaStreamCreate(&streams[i]);for (int i = 0; i < FULL_DATA_SIZE; i += N) {int s = (i / N) % 4;cudaMemcpyAsync(..., streams[s]);kernel<<<..., ..., 0, streams[s]>>>(...);cudaMemcpyAsync(..., streams[s]);
}

这样可实现拷贝和 kernel 的真正 overlap。

流+事件计时

例子:两个 kernel 用两个 stream 并发执行,各自测时间

cudaStream_t stream1, stream2;
cudaEvent_t start1, stop1, start2, stop2;
float time1 = 0.0f, time2 = 0.0f;cudaStreamCreate(&stream1);
cudaStreamCreate(&stream2);cudaEventCreate(&start1); cudaEventCreate(&stop1);
cudaEventCreate(&start2); cudaEventCreate(&stop2);// kernel1 时间测量(绑定到 stream1)
cudaEventRecord(start1, stream1);
my_kernel<<<gridDim, blockDim, 0, stream1>>>(...);
cudaEventRecord(stop1, stream1);// kernel2 时间测量(绑定到 stream2)
cudaEventRecord(start2, stream2);
my_kernel2<<<gridDim, blockDim, 0, stream2>>>(...);
cudaEventRecord(stop2, stream2);// 等待各自完成
cudaEventSynchronize(stop1);
cudaEventSynchronize(stop2);// 计算时间(单位 ms)
cudaEventElapsedTime(&time1, start1, stop1);
cudaEventElapsedTime(&time2, start2, stop2);printf("Kernel 1 time: %.2f ms\n", time1);
printf("Kernel 2 time: %.2f ms\n", time2);// 清理
cudaEventDestroy(start1); cudaEventDestroy(stop1);
cudaEventDestroy(start2); cudaEventDestroy(stop2);
cudaStreamDestroy(stream1); cudaStreamDestroy(stream2);

http://www.dtcms.com/a/269035.html

相关文章:

  • PowerQuery逆透视之二维表转一维表
  • 50天50个小项目 (Vue3 + Tailwindcss V4) ✨ | ContentPlaceholder(背景占位)
  • 电动汽车的传导发射仿真
  • navicate如何设置数据库引擎
  • RabbitMQ在SpringBoot中的使用详解
  • 2025光学成像与机器视觉国际会议 (OIMV 2025)
  • 用Python制作华夫图:从零开始
  • ShortGPT: Layers in Large Language Models are More Redundant Than You Expect
  • delphi,c++程序 阻止Win11 用户更改系统时间
  • 电子防抖(EIS)技术概述
  • Springboot 如何加密数据库连接相关配置信息
  • 特伦斯T1节拍器,突出综合优势与用户体验
  • AI建站工具对决:Wegic、腾讯云、Hocoos、Typedream深度测评,谁是国内用户的首选?
  • MySQL Galera Cluster企业级部署
  • 【Python】VSCode:解决模块导入与调试
  • 【音视频】HLS简介与服务器搭建
  • 【LLIE专题】通过预训练模型先验提升暗光增强模型复原效果
  • 安卓10.0系统修改定制化____如何修改固件 去除开机向导 实现开机直接进入桌面
  • C++笔记之开关控制的仿真与实际数据处理优雅设计
  • 基于物联网的城市低洼地段水深报警系统设计
  • 【人工智能学习路线(一)】以SCI为目标学习笔记——Python 编程基础入门
  • 面试总结46-50天
  • Python爬虫图片验证码和滑块验证码识别总结
  • 前端技术博客汇总文档
  • 思考5-10分钟,输出高质量的学术科研报告,谷歌的deepsearch模型太惊艳了!
  • 【最新版】Spring Boot 项目打包部署到服务器
  • 【配置+图解Android各种版本配置】
  • V8 主要版本与对应 ECMAScript 支持
  • 2025 API 开发管理工具 Apipost 与 Apifox 全维度对比
  • CentOS-7-x86_64解决:使用NAT模式无法ping通www.baidu.com或无法ping 8.8.8.8问题。