提问人:Saitama10000 提问时间:11/17/2023 更新时间:11/20/2023 访问量:85
无法实现最大共享内存带宽
Cannot achieve max shared memory bandwith
问:
我正在使用 RTX 2080 max-q 移动版,计算能力 7.5。我试图理解为什么我无法实现 32 位/周期的共享内存带宽:
从 https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#shared-memory-5-x
共享内存有 32 个存储体,这些存储体的组织方式是连续的 32 位字映射到连续的存储体。每个 bank 的带宽为每时钟周期 32 位。
我写了这个测试代码:
#include <iostream>
#include <algorithm>
#include <numeric>
using T = float;
extern __shared__ T bank[];
constexpr int warps = 8;
constexpr int pitch = 32 * warps;
constexpr int size = 32;
__managed__ long long starts[pitch];
__managed__ long long stops[pitch];
__managed__ long long clocks[pitch];
__global__ void kernel()
{
auto* local_bank = bank + threadIdx.x;
auto* a = local_bank;
auto* b = a + size * pitch;
__syncwarp();
auto start = clock64();
__syncwarp();
for (int i = 0; i < size; i++)
b[i * pitch] = a[i * pitch];
__syncwarp();
auto stop = clock64();
__syncwarp();
auto duration = stop - start;
printf("%5lld %s", duration, threadIdx.x % 32 == 31 ? "\n" : "");
__syncthreads();
starts[threadIdx.x + blockDim.x *blockIdx.x] = start;
stops[threadIdx.x + blockDim.x *blockIdx.x] = stop;
clocks[threadIdx.x + blockDim.x *blockIdx.x] = duration;
}
int main()
{
cudaDeviceSetLimit(cudaLimitStackSize, 64 * 1024);
cudaFuncSetAttribute(kernel, cudaFuncAttributeMaxDynamicSharedMemorySize, 64*1024);
cudaFuncSetCacheConfig(kernel, cudaFuncCachePreferShared);
kernel<<<1, pitch, 2 * size * pitch * sizeof(T)>>>();
cudaDeviceSynchronize();
auto min_clock = *std::min_element(std::begin(clocks), std::end(clocks));
auto min_start = *std::min_element(std::begin(starts), std::end(starts));
auto max_stop = *std::max_element(std::begin(stops), std::end(stops));
auto avg_clock = std::accumulate(std::begin(clocks), std::end(clocks), 0) / (float)(std::end(clocks) - std::begin(clocks));
auto avg_clock_per_access = avg_clock / (float)(warps * size * 2);
printf("min = %lli\n", min_clock);
printf("max = %lli\n", max_stop - min_start);
printf("avg = %f\n", avg_clock_per_access);
}
它以时钟周期为单位测量每个线程的共享内存复制的持续时间,并输出共享内存访问所花费的平均周期数。我有 2 个周期,应该是 1 个周期。我不明白我做错了什么。
注意:我尝试过使用 float4,得到了相同的结果。您可以将类型 T 更改为 float4 并将大小减小到 8,以便使用不超过 64kb 的共享内存。
答:
在以下论文中记录,在考虑 SM 的频率和数量之前,图灵 GPU (CC 7.5) 的共享内存带宽是 Volta (7.0) 或 Ampere (8.x) 值的一半。
请参阅 https://arxiv.org/abs/1903.07486(通过微基准测试剖析 NVidia Turing T4 GPU)和 https://ieeexplore.ieee.org/abstract/document/9893362(Tensor Core 内存层次结构的未来扩展以及使用扭曲间多播消除冗余共享内存流量)。
使用图灵(计算能力 7.5)时,共享内存带宽为 16 通道 * 32 位/周期/SM。因此,32 位访问需要 2 个周期。在 Volta 和 Ampere 上,它们需要 1 个周期。
请注意,这是每个 SM 而不是每个 SM 分区。因此,计算速度比共享内存带宽快得多。
全局内存带宽要求和计算要求较低,但使用大量共享内存的内核很容易受到共享内存带宽的限制。在这种情况下,尝试在线程之间更好地分配计算,或者使用寄存器在线程中缓存数据,并具有潜在的展开循环,以便能够访问寄存器中的数组,其索引在编译时是已知的。
多年来,通过引入和改进 L1/L2 缓存以及不增加共享内存带宽,共享内存(甚至更多的纹理访问)对高性能和架构的重要性已经降低。
共享内存仍然提供了更好的控制,与L1缓存相比,保留了哪些数据,更好的随机访问,而不需要合并,只要可以通过巧妙的数据布局和访问模式来避免银行冲突。此外,随机播放指令也得到了改进,因为它们使用共享内存路由硬件,并且如果唯一的任务是在 warps 内共享一次性数据,则无需存储 + 加载或保留共享内存。此外,Ampere 引入的异步数据加载指令对于使用共享内存也有很大的改进。
评论