【发布时间】:2014-09-22 21:40:38
【问题描述】:
我有以下简单的代码:
#include<stdio.h>
#define BLOCKSIZE_X 32
#define BLOCKSIZE_Y 1
int iDivUp(int a, int b) { return ((a % b) != 0) ? (a / b + 1) : (a / b); }
#define gpuErrchk(ans) { gpuAssert((ans), __FILE__, __LINE__); }
inline void gpuAssert(cudaError_t code, char *file, int line, bool abort=true)
{
if (code != cudaSuccess)
{
fprintf(stderr,"GPUassert: %s %s %d\n", cudaGetErrorString(code), file, line);
if (abort) exit(code);
}
}
__global__ void kernel0(float *d_a, float *d_b, const unsigned int M, const unsigned int N)
{
const int tidx = threadIdx.x + blockIdx.x * blockDim.x;
const int tidy = threadIdx.y + blockIdx.y * blockDim.y;
if ((tidx < M)&&(tidy < N)) {
d_b[tidy * M + tidx] = d_a[tidy * M + tidx];
}
}
void main()
{
const unsigned int M = 32;
const unsigned int N = 1;
float *d_a; cudaMalloc((void**)&d_a, M*N*sizeof(float));
float *d_b; cudaMalloc((void**)&d_b, M*N*sizeof(float));
dim3 dimGrid(iDivUp(M, BLOCKSIZE_X), iDivUp(N, BLOCKSIZE_Y));
dim3 dimBlock(BLOCKSIZE_X, BLOCKSIZE_Y);
kernel0<<<dimGrid, dimBlock>>>(d_a, d_b, M, N);
gpuErrchk(cudaPeekAtLastError());
gpuErrchk(cudaDeviceSynchronize());
cudaDeviceReset();
}
执行两个32 floats 数组之间的赋值。我试图了解全局内存合并访问与全局内存加载/存储效率以及其他指标/事件之间的关系。
Visual Profiler 显示以下指标:
Global Memory Load Efficiency = 50%
Global Memory Store Efficiency = 100%
全局内存负载效率的值让我感到惊讶。在这两种情况下,我都期待100% 的效率,因为我相信我正在执行完美合并的内存访问。所以我的问题是:
为什么当我执行合并的内存访问时,我的全局内存加载效率为 50%,而我却拥有 100% 的全局内存存储效率?
我还调查了其他可能有用的指标/事件:
gld_inst_32bit = 32 (Number of 32-bit global memory load transactions)
gst_inst_32bit = 32 (Number of 32-bit global memory store transactions)
确实,我要求加载/写入32 floats。
uncached global load transaction = 0 (Number of uncached global load transactions)
l1 global load miss = 2 (Number of global load misses in L1 cache)
根据我(可能是错误的)理解,上述两个事件似乎是矛盾的。在l1 缓存未命中的情况下,我预计第一个事件与0 不同。
gld_request = 1 (Number of executed global load instructions per warp in a SM)
gst_request = 1 (Number of executed global store instructions per warp in a SM)
这似乎与我正在执行完美合并的内存访问这一事实一致。
反汇编代码如下:
/*0000*/ MOV R1, c[0x1][0x100]; /* 0x2800440400005de4 */
/*0008*/ S2R R3, SR_CTAID.Y; /* 0x2c0000009800dc04 */
/*0010*/ S2R R4, SR_TID.Y; /* 0x2c00000088011c04 */
/*0018*/ IMAD R4, R3, c[0x0][0xc], R4; /* 0x2008400030311ca3 */
/*0020*/ S2R R0, SR_CTAID.X; /* 0x2c00000094001c04 */
/*0028*/ ISETP.LT.U32.AND P0, PT, R4, c[0x0][0x2c], PT; /* 0x188e4000b041dc03 */
/*0030*/ S2R R2, SR_TID.X; /* 0x2c00000084009c04 */
/*0038*/ IMAD R0, R0, c[0x0][0x8], R2; /* 0x2004400020001ca3 */
/*0040*/ ISETP.LT.U32.AND P0, PT, R0, c[0x0][0x28], P0; /* 0x18804000a001dc03 */
/*0048*/ @!P0 BRA.U 0x78; /* 0x40000000a000a1e7 */
/*0050*/ @P0 IMAD R2, R4, c[0x0][0x28], R0; /* 0x20004000a04080a3 */
/*0058*/ @P0 ISCADD R0, R2, c[0x0][0x20], 0x2; /* 0x4000400080200043 */
/*0060*/ @P0 ISCADD R2, R2, c[0x0][0x24], 0x2; /* 0x4000400090208043 */
/*0068*/ @P0 LD R0, [R0]; /* 0x8000000000000085 */
/*0070*/ @P0 ST [R2], R0; /* 0x9000000000200085 */
/*0078*/ EXIT; /* 0x8000000000001de7 */
编辑
我的配置:CUDA 6.5、GeForce GT540M、Windows 7。
如果我将M 从32 增加到64 以启动两个块并使我卡的两个可用流式多处理器忙,那么全局内存负载效率将变为100%,这些是新指标/事件:
gld_inst_32bit = 64
gst_inst_32bit = 64
uncached global load transaction = 0
l1 global load miss = 2
gld_request = 2
gst_request = 2
gld_inst_32bit、gst_inst_32bit、gld_request 和 gst_request 的增加是预期的并且是一致的,因为现在我正在加载 7 存储 64 floats 和 2 全局内存加载/存储合并请求。但是我仍然不明白uncached global load transaction 和l1 global load miss 是如何保持不变的,而全局内存负载吞吐量会发生变化以提供100% 的效率。
编辑
M=32 在 Kepler K20c 上的结果:
Global Memory Load Efficiency = 100%
Global Memory Store Efficiency = 100%
gld_inst_32bit = 64
gst_inst_32bit = 64
gld_request = 1
gst_request = 1
uncached global load transaction = 1
l1 global load miss = 0
l1 global load hit = 0
现在,Visual Profiler 报告一个未缓存的全局加载事务,但没有l1 全局加载未命中。
编辑
我对此问题进行了更多调查,增加了M 的值并保持BLOCKSIZE_X 不变。
当块数为奇数时,即我的GT540M卡的两个Streaming Multiprocessors上的负载不平衡,则全局内存负载效率小于100%,否则为偶数情况下100%。只要在奇数情况下增加块数,全局内存加载效率就会慢慢趋于100%。
如果我按照@Jez 的建议通过使用-Xptxas -dlcm=cg 编译禁用L1 缓存,那么全局内存加载效率始终等于100%,因为它是全局内存存储效率。我知道全局内存存储不使用L1缓存,而只使用L2。
一些图片显示,对于不同的M值,全局内存加载效率的行为
M=32
M=64
M=96
M=128
M=160
M=192
请注意,M 是 32 的整数倍,以便通过单个 warp 加载整个缓存行。
通过禁用L1,我有:
M=32
M=64
M=96
编辑 - 特斯拉 C2050 的结果
M = 32 33.3%
M = 64 28.6%
M = 96 42.9%
M = 128 57.1%
M = 160 71.4%
M = 192 85.7%
M = 224 100%
M = 256 114%
M = 288 90%
同样,如果我禁用 L1 缓存,我在所有情况下都有 100% 全局内存加载效率。
【问题讨论】:
-
您使用的是什么计算能力和CUDA版本?
-
@Jez 谢谢。我已经添加了我的配置细节和新测试用例的结果。
-
关于
uncached global load transactionvsl1 global load miss:有两种类型的全局加载事务。缓存和非缓存。缓存的事务被缓存在 L1 中,未缓存的则不是。在 Fermi 上,默认是缓存在 L1 中,因此您没有未缓存的全局加载事务。如果您使用参数-Xptxas -dlcm=cg进行编译,您将生成未缓存在L1 中的负载,并且uncached global load transaction计数器将增加。注意:这只是 L1,不是 L2。 -
您有一个 L1 缓存未命中,因为您的内核请求的数据 (
d_a) 在 Fermi 案例中最初不在 SM 的 L1 缓存中。那是一个“错过”。在 Kepler 案例中,L1 cache is disabled for global loads,因此所有加载都是“未缓存”的,并且不会有 L1 未命中,因为没有 L1 在运行。 -
我在这个问题中没有看到任何异常,除了你有一个扭曲 (M=32) 并且获得 50% 的负载效率的情况。我无法重现它,并且一旦您进入多个经线,它就会消失(对您而言)。这是一个奇怪的事情,但我个人不能太激动。它可能是分析器中的一个错误,或者在这种病理情况下是一个实际的非明显功能异常(例如 2 个负载,其中一个未使用)。我无法解释或重现它。
标签: cuda