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

cuda编程笔记(10)--memory access 优化

全局内存访问优化(Coalesced Access)

什么是 Coalesced Access?

定义:一个 warp(32 个线程)在同一指令中访问全局内存时,如果这些访问请求可以合并成尽可能少的内存事务(通常是 32、64 或 128 字节对齐的块),就叫 coalesced

条件:一个 warp 的线程访问 连续且对齐 的地址。

int tid = threadIdx.x + blockIdx.x * blockDim.x;
float val = d_array[tid];  // ✅ 连续访问 → Coalesced

优化技巧

以结构体对齐内存:使用 __align__(16)float4

为什么需要对齐?

  • GPU 内存总线要求访问按 32/64/128 字节对齐,这样才能合并成一次事务。

  • 如果内存对齐不好,warp 访问会拆分成多个事务,带宽利用率降低。

技巧

  • 使用 float4(4 个 float 一起)保证 16 字节对齐。

  • 或者使用 CUDA 对齐修饰符:

float4 vs float

float

  • 单个 4 字节(32 bit)的浮点数。

  • 每个线程访问 1 个 float 时,如果 warp 中 32 个线程访问地址连续(0,1,2,3...),CUDA 会把它们合并成1~2 个内存事务,性能好。

float4

  • CUDA 提供的 矢量类型,表示 4 个连续的 float(总共 16 字节)。

  • 优点:

    • 天然 16 字节对齐(满足 GPU 内存事务对齐要求)。

    • 每个线程一次加载 4 个浮点数,减少指令数,提高带宽利用率。

//设置float4
float4 f;
f.x = 1.1, f.y = 2.2, f.z = 3.3, f.w = 4.4;
float4 v = data[idx];  // 读取 4 个 float
result = v.x + v.y + v.z + v.w;

如果要读取大数组,使用 float4 可以让 每个线程批量读取,提高 coalesced 访问效率。

对比:

  • 32 个线程一次访问 float → 128 字节(32*4)

  • 32 个线程一次访问 float4 → 512 字节(32*16),如果对齐良好,GPU 可以用更少的事务完成。

__align__(n) 关键字

作用

  • 强制结构体或变量的起始地址对齐到 n 字节边界

  • 为什么?因为 GPU(和 CPU)要求数据按一定字节对齐访问,否则:

    • 拆分访问 → 多次内存事务 → 性能差

    • 未对齐访问 → 有的设备直接报错

struct __align__(16) MyStruct {float x, y, z, w;
}; // 占 16 字节,起始地址必须是 16 的倍数
  • 如果不加 __align__(16),可能被编译器按 4 字节对齐排布,不符合 GPU 要求。

为什么 CUDA 推荐使用 float4 + 对齐?

  • 全局内存的访问规则:按 32/64/128 字节事务合并。

  • 如果 warp 32 线程访问 float(每个 4 字节),正好 128 字节,可以合并。

  • 如果 warp 32 线程访问 float4(每个 16 字节),正好 512 字节,GPU 需要 4 个事务,但每个事务更大,吞吐率更高。

  • 重要:必须保证起始地址按 float4 对齐,否则性能下降。

行优先存储,避免跨行访问

  • CUDA 全局内存是按一维线性存储的,如果访问跨行,会破坏 coalesced。

  • 例如,二维矩阵 A[M][N],默认按行优先(row-major)存储:

内存布局: A[0][0], A[0][1], ..., A[0][N-1], A[1][0], ...

错误访问模式(列遍历):

val = A[col][row]; // 每个线程跨 stride 访问

每个线程 stride 大,warp 访问不连续,性能差。

优化

  • 保证 threadIdx.x 对应 最快变化维度(行访问),这样 warp 线程连续访问。

调换索引顺序,确保 threadIdx.x 是最快变化维度

  • 原则:warp 线程访问地址必须连续

  • 如果你的算法天然是列操作,可以调整线程分布:

如果是行优先,那么变化最快的其实是列下标,threadIdx.x对应的也应该是列。如果算法要求列优先,可以对row和col进行调换

以下是行优先情况下col和row的写法

int col = blockIdx.x * blockDim.x + threadIdx.x;  // x 对应列
int row = blockIdx.y * blockDim.y + threadIdx.y;  // y 对应行

使用 Shared Memory 缓存 tile

为什么?

  • 全局内存访问延迟大(400~600 cycles),共享内存延迟低(≈100x 更快)。

  • 如果每个线程直接从全局内存多次访问,会拖慢性能。

  • 解决:把要用的数据块(tile)加载到共享内存,所有线程复用,减少全局访问。

例子见上一篇文章:cuda编程笔记(9)--使用 Shared Memory 实现 tiled GEMM -CSDN博客

Bank Conflict

Bank Conflict(共享内存银行冲突) 是 CUDA 编程中的一个性能问题,发生在多个线程同时访问 共享内存(Shared Memory) 时。

共享内存的结构

  • CUDA 的 共享内存被划分成多个 Bank,类似一个并行访问的“多路存储器”。

  • 每个 Bank 可以在一个时钟周期内处理 1 个 32-bit 访问请求

  • Warp(32 个线程)同时访问共享内存时:

    • 如果 32 个线程访问 32 个不同的 Bank无冲突(完美并行)

    • 如果 多个线程访问同一个 Bank 的不同地址发生 Bank Conflict,访问会被 串行化,性能大幅下降。

具体原理

假设:

  • 共享内存被分为 32 个 Bank

  • 每个 Bank 宽度 = 4 字节(一个 float

  • 地址映射公式:

bank_id = (address_in_bytes / 4) % 32

例子

__shared__ float s[32][32];
按行优先存储(Row-major):

  • s[i][j] 的地址 = base + (i * 32 + j) * 4

情况 1:访问同一列

如果每个线程访问 s[threadIdx.x][k](同一列 k),

  • 地址 = base + (threadIdx.x * 32 + k) * 4

  • bank_id = (threadIdx.x * 32 + k) % 32 = k(因为 threadIdx.x * 32 是 32 的倍数)

  • 所有线程访问同一 Bank(k) → 严重冲突

情况 2:访问同一行

如果每个线程访问 s[k][threadIdx.x](同一行 k),

  • 地址 = base + (k * 32 + threadIdx.x) * 4

  • bank_id = (k * 32 + threadIdx.x) % 32 = threadIdx.x

  • 每个线程访问不同 Bank → 无冲突

避免 Bank Conflict 的方法

核心原则:让 warp 内的 32 个线程访问的地址尽量分布到不同的 bank。

  • 按行访问而非按列

    • 推荐:s[threadIdx.y][threadIdx.x](X 对应列,变化最快)

  • 增加 padding(填充列)

    • 如果二维数组导致 bank 冲突,可以在第二维加一个“dummy 列”,让 stride ≠ 32:

__shared__ float s[TILE_SIZE][TILE_SIZE + 1];

使用结构化数据(float4)或 align

  • 一次加载多个元素,减少 warp 的 bank 竞争。

 

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

相关文章:

  • 《P4568 [JLOI2011] 飞行路线》
  • Flutter开发实战之性能优化与调试
  • 自动标注软件X-AnyLabeling的使用教程
  • OpenLayers 综合案例-地图绘制
  • 深入理解Linux网络--读书笔记(二)
  • HDFS基础命令
  • 简易 BMI 身体质量指数计算器
  • 墨者:SQL注入漏洞测试(布尔盲注)
  • FastAPI入门:查询参数模型、多个请求体参数
  • (LeetCode 面试经典 150 题)71. 简化路径 (字符串)
  • 小白投资理财 - 从换手率和成交量分析股票趋势
  • Vue vuex模块化编码
  • 网络资源模板--基于Android Studio 实现的新闻App
  • 自由学习记录(74)
  • 基于混沌系统的图像加密学习日志——论文学习3
  • unity3dTextMeshPro 设置中文字体,解决中文显示为框或中文字后面带背景颜色的问题
  • Unity SMAA
  • 三、搭建springCloudAlibaba2021.1版本分布式微服务-springcloud loadbalancer负载均衡
  • 习题综合练习
  • 自然语言处理NLP (1)
  • 【笔记】系统
  • 上位机知识篇---AJAX
  • MongoDB分片集群横向扩展
  • 2.qt调试日志输出
  • 区块链共识机制与联邦学习
  • 【C++】数字cmath库常用函数
  • 基于深度学习的图像分类:使用ShuffleNet实现高效分类
  • LeetCode 1577.数的平方等于两数乘积的方法数
  • day061-全网监控
  • 【科研绘图系列】R语言绘制边际云雨图散点图