了解内存重播和进行中的请求

Understanding Memory Replays and In-Flight Requests

我正在尝试了解矩阵转置如何能够更快地从列与行中天真地读取。 (示例来自 Professional CUDA C Programming)矩阵按行存储在内存中,即 (0,1),(0,2),(0,3)...(1,1),(1,2)

__global__ void transposeNaiveCol(float *out, float *in, const int nx, const int ny) {
    unsigned int ix = blockDim.x * blockIdx.x + threadIdx.x;
    unsigned int iy = blockDim.y * blockIdx.y + threadIdx.y;

    if (ix < nx && iy < ny) {
           out[iy*nx + ix] = in[ix*ny + iy]; // 
           // out[ix*ny + iy] = in[iy*nx + ix]; // for by row
    }
}

这是我不明白的地方:transposeNaiveCol() 的负载吞吐量是 642.33 GB/s,tranposeNaiveRow() 的负载吞吐量是 129.05 GB/s。作者说:

The results show that the highest load throughput is obtained with cached, strided reads. In the case of cached reads, each memory request is serviced with a 128-byte cache line. Reading data by columns causes each memory request in a warp to replay 32 times (because the stride is 2048 data elements), resulting in good latency hiding from many in-flight global memory reads and then excellent L1 cache hit ratios once bytes are pre-fetched into L1 cache.

我的问题: 我认为 aligned/coalesced 读取是理想的,但在这里似乎跨步读取可以提高性能。

  1. 为什么读取缓存行会导致性能下降 这种情况?
  2. 总的来说重放不是一件坏事吗?它在这里提到它导致 "good latency hiding"。

有效负载吞吐量并不是决定内核性能的唯一指标!具有完美合并负载的内核将始终具有比等效的非合并内核更低的 有效 负载吞吐量,但是 单独 对其执行没有任何说明时间:最后,真正重要的一个指标是内核完成所需的挂钟时间,作者没有提及。

也就是说,内核通常分为两类:

  • 计算绑定内核,其性能可以通过尝试隐藏指令延迟来提高:保持流水线满(最大化 ILP)。
  • I/O 绑定内核,可以通过尝试隐藏内存延迟来提高其性能:保持数据传输(最大化带宽)。

矩阵转置的计算强度非常低,因此受到 I/O 限制,因此要获得更好的性能,您应该尝试增加带宽使用量。

为什么列转置在最大化带宽使用方面更好?

在行转置的情况下,读取被合并:每个 warp 提供一个 128 字节的事务,即每个线程 4 个字节。这 128 个字节被放入缓存但从未被重复使用,因此缓存在这种情况下实际上没有用。

在列转置的情况下,读取不会合并:每个 warp 得到 32 个 128 字节的事务,所有这些都将进入 L1 并将在接下来的 31 次重放中重复使用(假设它们没有被踢出缓存)。对于非常高的有效负载吞吐量和最大缓存使用率,这是非常低的负载效率。

您当然可以通过简单地为每个线程请求更多数据(例如通过每个线程加载 32 float 或 8 float4)或使用 CUDA 的预取在行转置中获得相同的效果能力。