【发布时间】:2019-11-19 19:56:24
【问题描述】:
我试图了解 NVIDIA GPU/CUDA 上的内存合并与 x86-SSE/C++ 上的矢量化内存访问之间的关系。
据我了解:
- 内存合并是内存控制器的运行时优化(在硬件中实现)。完成warp的加载/存储需要多少内存事务是在运行时确定的。除非有完美的合并,否则扭曲的加载/存储指令可能是 issued repeatedly。
- 内存矢量化是一种编译时优化。矢量化加载/存储的内存事务数是固定的。每个向量加载/存储指令只发出一次。
- 可合并的 GPU 加载/存储指令比 SSE 向量加载/存储指令更具表现力。例如,
st.global.s32PTX 指令可以存储到 32 个任意内存位置(warp 大小为 32),而movdqaSSE 指令只能存储到连续的内存块中。 - CUDA 中的内存合并似乎保证了高效的矢量化内存访问(当访问可合并时),而在 x86-SSE 上,我们不得不希望编译器实际上对代码进行了矢量化(它可能无法这样做)或使用 SSE 内在函数手动矢量化代码,这对程序员来说更难。
这是正确的吗?我是否错过了一个重要方面(可能是线程屏蔽)?
现在,为什么 GPU 具有运行时合并功能?这可能需要额外的硬件电路。与 CPU 中的编译时合并相比,主要优势是什么?是否存在由于缺少运行时合并而更难在 CPU 上实现的应用程序/内存访问模式?
【问题讨论】:
-
将内存合并表征为“运行时优化”并不真正正确。它更像是 GPU 的默认执行模型。发散是指当一条指令不能在一个扭曲中以锁步方式执行时会发生这种情况,当一条内存指令不能在单个事务中提供服务时就会发生这种情况
-
合并发生(或不发生)作为经纱中每个线程提供给给定 LD 或 ST 指令的 LD/ST 单元的地址的函数。内存控制器查看实际地址并确定哪些可以组合成特定的高速缓存行或内存段,然后向这些行/段发出请求。由于在运行时(在一般情况下)之前无法知道 warp 中每个线程指示的地址,因此无法在编译时预先计算此活动。
-
内存合并提供的寻址灵活性(与您的
movdqa示例相比)允许 CUDA 程序员编写任意线程代码并期望功能正确的结果。这大概有一些价值。程序员被允许做低效的事情以简化编程,但有一个路线图可以最大限度地利用内存子系统的性能/效率。为程序员提供两种选择被认为是有价值的。
标签: cuda gpu cpu-architecture simd coalescing