持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第18天,点击查看活动详情
内存优化
很多算法都是内存受限的。大多数是对内存带宽敏感。
前置知识:SM和warp
SM是GPU中的流多处理器,每个GPU有多个SM, 一个SM拥有多个指令调度单元,warps被划分给这些指令调度单元,每个单元追踪它们自己的warp,是否可以发送指令给warp执行。
数据从内存到SM的路径
首先,所有的数据都是保存在DRAM显存上的,包含全局数据,局部数据,纹理,常量。
DRAM其实是通过L2访问的
当数据传输到SM中之后,数据进入3个cache/buffer之一:L1 cache, Read only cache和Const Buffer。 L1是默认的,而Read-Only和Cost需要代码显式指定。
- L1路径:全局内存,映射的CPU内存;本地内存(编译器管理)
- Read-only/TEX路径:纹理对象,CUDA数组
- Constant path:全局作用域的constant数组
warp寻址模式
地址对齐
硬件支持的word size: 1B,2B, 4B, 8B, 16B。地址必须对齐到word-size边界。
内存访问是逐个warp处理的
- warp中的32个线程提供了32个地址(当然如果某些线程是inactive的,地址就会少一些)
- 硬件将地址转换成内存事务(memory transcations)
- 寻址模式可能需要一个指令使用多个事务
- 如果需要N个事务,就会有N-1次的指令重新执行(replays)
global memory写入
- GMEM的写入是没有Cache的,写入会使得L1失效,会直接写入到L2
- 访问是按32B大小的段(segment)的粒度
- Transcation到内存:1或2或4个段,且只有需要的段会被发送
- 如果warp中的多个线程写入了相同的地址,只有一个线程会赢,但哪一个是未定义的
一些写入的例子
一次写入4个段的事务
3个写入1个段的事务
1个写入2个段的事务
2个写入1个段的事务
GMEM读取
- 根据程序的设置以及计算的兼容性会尝试命中L1
- Caching的读取指令:如果L1 miss就读取L2, L2 miss就读取DRAM,事务: 128B line
- Non-Caching的读取指令:直接读取L2 (令L1失效),L2 miss则读取DRAM, 事务:1,2,4 段, 段=32B(和写入一样)
一些读取的例子
- caching load
- 场景:warp需要32个对齐的,连续的4字节words
- 地址落入到一个cache-line中
- 指令不会replay
- BUS利用率100% (warp需要128字节,如果miss则128字节通过bus读取)
- Non-Caching Load
- 场景:warp需要32个对齐的,连续的4字节words
- 地址落入到4个segments之内
- BUS利用率100%(warp需要128字节,如果miss则128字节通过bus读取)
- caching load
- 场景:warp需要32个对齐的,重新排列的4字节words
- 地址落入到一个cache-line中
- 指令不会replay
- BUS利用率100% (warp需要128字节,如果miss则128字节通过bus读取)
- Non-Caching Load
- 场景:warp需要32个对齐的,重新排列的4字节words
- 地址落入到4个segments之内
- 指令不会replay
- BUS利用率100%(warp需要128字节,如果miss则128字节通过bus读取)
- caching load
- 场景:warp需要32个连续的4字节words,从完美的对齐中偏移
- 地址落入到2个cache-line中
- 1个replay(2个事务)
- BUS利用率50%
- warp需要128字节,如果miss则256个字节通过bus读取
- Non-Caching Load
- 场景:warp需要32个连续的4字节words,从完美的对齐中偏移
- 地址落入到最多5个segments之内
- 1个replay(2个事务)
- BUS利用率至少80%
- warp需要128字节,如果miss则160字节通过bus读取
- 某些不对齐的模式下会落入到4个segmetns,所以100%利用
- caching load
- 场景:warp中的所有线程请求相同的4个byte的word
- 地址落入到一个cache line上
- 没有relay
- BUS利用率 3.125%
- warp需要4个字节,但是miss时需要通过bus传输128个字节
- Non-caching load
- 场景:warp中的所有线程请求相同的4个byte的word
- 地址落入到一个cache line上
- 没有relay
- BUS利用率 12.5%
- warp需要4个字节,但是miss时需要通过bus传输32个字节
- caching load
- 场景:warp请求32个离散的4字节words
- 地址落入到N个cache line上
- N-1个relay(N个事务)
- BUS利用率 324B/(N128B)
- warp需要128个字节,但是miss时需要通过bus传输N*128个字节
- Non-caching load
- 场景:warp请求32个离散的4字节words
- 地址落入到N个Segments上
- N-1个relay(N个事务)
- BUS利用率 128/(N*32)
- warp需要128个字节,但是miss时需要通过bus传输N*32个字节
小结
如果可以使用L1
- hit时,Caching Load更好
- 如果warp寻找是离散的或核心使用了很多local MEM,则Non-Caching load更好
如果不能使用L1,所有的loads和Non-caching一样
总之,不要像使用CPU时那样太依靠GPU的cache了
Read-Only Cache
- 离散访问的性能高于L1
- 比L1延迟大,且会增加寄存器使用
- 所有的texture访问使用Read-Only Cache
- 使用时要总试图命中
- 事务大小:32B
- warp地址转换为一次请求4个线程(因此每个warp最少是8个请求)
- 如果数据在一个32B的段中,且被多个线程需要,segment最多Miss一次
- texture object的额外功能:插值,clamping, 类型转换
Constant Cache
- 限制最大:64KB total, 4B per clock per SM
- 当所有的线程使用相同的小数据集有用
内存优化的目标:最大化带宽使用率
最大化的使用总线上传输的字节。
- 内存是按warp访问的
- 内存访问时是按离散的块访问
- 尽可能的并行访问内存。