全局内存访问优化(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.pswp.cn/diannao/93176.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/93176.shtml
英文地址,请注明出处:http://en.pswp.cn/diannao/93176.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

闲庭信步使用图像验证平台加速FPGA的开发:第三十一课——车牌识别的FPGA实现(3)车牌字符分割预处理

(本系列只需要modelsim即可完成数字图像的处理,每个工程都搭建了全自动化的仿真环境,只需要双击top_tb.bat文件就可以完成整个的仿真,大大降低了初学者的门槛!!!!如需要该系列的工程…

电子电气架构 --- 汽车软件全生命周期

我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 简单,单纯,喜欢独处,独来独往,不易合同频过着接地气的生活,除了生存温饱问题之外,没有什么过多的欲望,表面看起来很高冷,内心热情,如果你身…

力扣面试150(41/150)

7.25 56. 合并区间 以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] [starti, endi] 。请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间 。 我的思路: 左端点升序…

【隧道篇 / IPsec】(7.6) ❀ 01. 利用向导快速建立IPsec安全隧道 (点对点) ❀ FortiGate 防火墙

【简介】相信很多人已经习惯利用导向快速创建VPN了,而且已经有部分尝鲜者已经用上了FortiOS 7.6,但是会发现FortiOS 7.6下的VPN向导改变了很多,一时无法下手,下面我们来看看最常见的点对点是如何配置的。环境介绍在配置IPsec VPN之…

PLLIP核

。1 号红色框内的速度等级代表着设备的速度 等级,保存默认就好;2 号红色框内设置输入频率;3 号红色框选择 PLL 的工作模式。我们 开发板用的晶振是 50MHz 的,故在 2 号红色框内我们填写 50MHz;我们在 3 号红色框内选正…

1.1 Deep learning?pytorch ?深度学习训练出来的模型通常有效但无法解释合理性? 如何 解释?

DL 是什么,你如何理解DL模型? DL 对于我而言,就是人类试图想通过数学语言描述人类学习过程的一门技术,或者说学科。 因此 DL 模型 相当于 数学 的 一个 funciton ,有输入,通过function处理,得…

java实现在工具类中注入其他对象方式

方案1: Slf4j Component public class ChatdocApiClient {Value("${chatdoc.app-id}")private String appId;Value("${chatdoc.secret}")private String secret;Value("${chatdoc.domain}")private String domain;private final Rest…

electron中IPC 渲染进程与主进程通信方法解析

electron中ipcRenderer.invoke、ipcRenderer.on、ipcRenderer.send、ipcRenderer.sendSync作用与区别 IPC 渲染进程与主进程通信方法解析 ipcRenderer 的这几个方法作用不完全相同,它们适用于不同的通信场景,核心区别在于通信方向、是否需要响应以及同步…

epoll_event 事件类型详解

epoll_event 事件类型详解 epoll_event 是 Linux epoll I/O 多路复用机制的核心结构体&#xff0c;其中的事件类型决定了 epoll 监控的行为和触发条件。以下是各种事件类型的详细解析&#xff1a; epoll_event 结构体 #include <sys/epoll.h>typedef union epoll_data {v…

设计自己的小传输协议 导论与概念

设计自己的小传输协议 导论与概念 1&#xff1a;聊一聊协议头设计 ​ 早在《TCP/IP详解》中的第一句话中&#xff0c;我们就知道协议的含义是这样的&#xff1a;协议是通信双方共同遵守的一套规则&#xff0c;提供格式定义、语义解释等&#xff0c;使不同设备或软件能够正确交…

iOS —— 天气预报仿写总结

在iOS中&#xff0c;最常见的网络请求方式是NSURLSession&#xff0c;它是苹果推荐的现代API&#xff0c;简单安全且易于拓展。一次完整的网络请求流程&#xff1a;构造 NSURL 对象创建 NSURLSessionDataTask发起请求&#xff08;resume&#xff09;在回调中解析数据回到主线程…

MySQL 8.4 Windows 版安装记录与步骤参考

导语&#xff1a; MySQL 作为广泛使用的开源数据库管理系统&#xff0c;是许多开发者和学习者的必备工具。最近有朋友询问安装过程&#xff0c;正好整理了 MySQL 8.4 在 Windows 系统下的安装步骤和一些注意事项&#xff0c;分享给有需要的朋友做个参考。关于 MySQL&#xff1a…

七、搭建springCloudAlibaba2021.1版本分布式微服务-skywalking9.0链路追踪

前言链路追踪介绍 对于一个大型的几十个&#xff0c;几百个微服务构成的微服务架构系统&#xff0c;通常会遇到下面的一系列问题。 如何串联整个调用链路&#xff0c;快速定位问题&#xff1f;如何澄清各个微服务之间的依赖关系&#xff1f;如何进行各个微服务接口的性能分析&a…

深入理解大语言模型生成参数:temperature、top\_k、top\_p 等全解析

在使用大语言模型&#xff08;如 GPT-4、LLaMA、ChatGLM 等&#xff09;进行文本生成任务时&#xff0c;很多开发者会面对各种“生成参数”&#xff0c;如 temperature、top_k、top_p、repetition_penalty 等。这些参数虽然看起来抽象&#xff0c;但掌握它们的意义和配置技巧&a…

vulhub Web Machine(N7)靶场攻略

下载地址&#xff1a; https://download.vulnhub.com/webmachine/Web-Machine-N7.ova 使用方法&#xff1a; 靶场下载好以后不用解压&#xff0c;需要使用Oracle VirtualBox虚拟机打开&#xff0c;用VMware会报错。安装Oracle VirtualBox虚拟机时安装地址不能随便选择&#…

【机器学习深度学习】模型微调:多久才算微调完成?——如何判断微调收敛,何时终止训练

目录 前言 一、微调过程的目标&#xff1a;优化模型表现 二、微调需要多久&#xff1f; 微调时间无法确定 三、如何判断微调何时收敛&#xff1f; 3.1 观察Loss的下降趋势 3.2 损失值趋于平稳&#xff0c;意味着收敛 如何识别收敛&#xff1f; 3.3 验证Loss的波动&…

红队视角:实战渗透测试中漏洞利用的进阶技巧与防御

红队作为渗透测试的 “攻击方”&#xff0c;其核心价值不仅在于发现漏洞&#xff0c;更在于挖掘漏洞的深度利用方式 —— 通过绕过防护措施、组合低危漏洞形成攻击链&#xff0c;暴露企业真实安全风险。从红队视角解析漏洞利用的进阶技巧&#xff0c;既能帮助防御方理解攻击思路…

OpenHarmony BUILD.gn中执行脚本

在OpenHarmony编译构建中笔者经常遇到这样的场景——需要执行sh脚本完成某些操作。笔者将OpenHarmony BUILD.gn中执行脚本的方法分享如下&#xff1a; 前置知识点 1.能够把自定义的子系统加入OpenHarmony源码的编译构建&#xff0c;请参考&#xff1a;https://ost.51cto.com/…

QUIC协议如何在UDP基础上解决网络切换问题

一、UDP 四元组的本质局限UDP 本身无连接状态&#xff0c;其数据包仅通过四元组寻址。但 QUIC 在 UDP 之上构建了完整的连接语义。二、QUIC 的连接迁移核心机制1. 连接标识符&#xff08;Connection ID&#xff09;关键设计&#xff1a;每个 QUIC 连接拥有全局唯一 64-bit Conn…

力扣131:分割回文串

力扣131:分割回文串题目思路代码题目 给你一个字符串 s&#xff0c;请你将 s 分割成一些 子串&#xff0c;使每个子串都是 回文串 。返回 s 所有可能的分割方案。 思路 从题目中我们可以总结出这道题的三个需要解决的问题&#xff1a; 如何判断回文串如何找到一种方案里的所…