从GPU架构看性能优化(2)内存优化

820 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第18天,点击查看活动详情

内存优化

很多算法都是内存受限的。大多数是对内存带宽敏感。

前置知识:SM和warp

SM是GPU中的流多处理器,每个GPU有多个SM, 一个SM拥有多个指令调度单元,warps被划分给这些指令调度单元,每个单元追踪它们自己的warp,是否可以发送指令给warp执行。

数据从内存到SM的路径

image.png

首先,所有的数据都是保存在DRAM显存上的,包含全局数据,局部数据,纹理,常量。

image.png

DRAM其实是通过L2访问的

image.png

当数据传输到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中的多个线程写入了相同的地址,只有一个线程会赢,但哪一个是未定义的

一些写入的例子

image.png

一次写入4个段的事务

image.png

3个写入1个段的事务

image.png

1个写入2个段的事务

image.png

2个写入1个段的事务

GMEM读取

  • 根据程序的设置以及计算的兼容性会尝试命中L1
  • Caching的读取指令:如果L1 miss就读取L2, L2 miss就读取DRAM,事务: 128B line
  • Non-Caching的读取指令:直接读取L2 (令L1失效),L2 miss则读取DRAM, 事务:1,2,4 段, 段=32B(和写入一样)

一些读取的例子

image.png

  • caching load
  • 场景:warp需要32个对齐的,连续的4字节words
  • 地址落入到一个cache-line中
  • 指令不会replay
  • BUS利用率100% (warp需要128字节,如果miss则128字节通过bus读取)

image.png

  • Non-Caching Load
  • 场景:warp需要32个对齐的,连续的4字节words
  • 地址落入到4个segments之内
  • BUS利用率100%(warp需要128字节,如果miss则128字节通过bus读取)

image.png

  • caching load
  • 场景:warp需要32个对齐的,重新排列的4字节words
  • 地址落入到一个cache-line中
  • 指令不会replay
  • BUS利用率100% (warp需要128字节,如果miss则128字节通过bus读取)

image.png

  • Non-Caching Load
  • 场景:warp需要32个对齐的,重新排列的4字节words
  • 地址落入到4个segments之内
  • 指令不会replay
  • BUS利用率100%(warp需要128字节,如果miss则128字节通过bus读取)

image.png

  • caching load
  • 场景:warp需要32个连续的4字节words,从完美的对齐中偏移
  • 地址落入到2个cache-line中
  • 1个replay(2个事务)
  • BUS利用率50%
  • warp需要128字节,如果miss则256个字节通过bus读取

image.png

  • Non-Caching Load
  • 场景:warp需要32个连续的4字节words,从完美的对齐中偏移
  • 地址落入到最多5个segments之内
  • 1个replay(2个事务)
  • BUS利用率至少80%
  • warp需要128字节,如果miss则160字节通过bus读取
  • 某些不对齐的模式下会落入到4个segmetns,所以100%利用

image.png

  • caching load
  • 场景:warp中的所有线程请求相同的4个byte的word
  • 地址落入到一个cache line上
  • 没有relay
  • BUS利用率 3.125%
  • warp需要4个字节,但是miss时需要通过bus传输128个字节

image.png

  • Non-caching load
  • 场景:warp中的所有线程请求相同的4个byte的word
  • 地址落入到一个cache line上
  • 没有relay
  • BUS利用率 12.5%
  • warp需要4个字节,但是miss时需要通过bus传输32个字节

image.png

  • caching load
  • 场景:warp请求32个离散的4字节words
  • 地址落入到N个cache line上
  • N-1个relay(N个事务)
  • BUS利用率 324B/(N128B)
  • warp需要128个字节,但是miss时需要通过bus传输N*128个字节

image.png

  • 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, 类型转换

image.png

image.png

Constant Cache

  • 限制最大:64KB total, 4B per clock per SM
  • 当所有的线程使用相同的小数据集有用

内存优化的目标:最大化带宽使用率

最大化的使用总线上传输的字节。

  • 内存是按warp访问的
  • 内存访问时是按离散的块访问
  • 尽可能的并行访问内存。