我正在研究需要大量共享内存的N体问题。
基本上,有N
个独立任务,每个任务使用4个双精度变量,即32个字节。并且一个任务由一个线程执行。
为了快速起见,我一直在使用共享内存来处理这些变量(假设线程也使用了寄存器)。由于在编译时不知道任务的数量N
,因此动态分配共享内存。
根据N
和块大小计算网格的维度和共享内存:
const size_t BLOCK_SIZE = 512;
const size_t GRID_SIZE = (N % BLOCK_SIZE) ? (int) N/BLOCK_SIZE : (int) N/BLOCK_SIZE +1;
const size_t SHARED_MEM_SIZE = BLOCK_SIZE * 4 * sizeof(double);
然后使用这3个变量启动内核。
kernel_function<<<GRID_SIZE, BLOCK_SIZE, SHARED_MEM_SIZE>>>(N, ...);
对于小N
,这样可以正常工作并且内核执行时没有错误。
但如果超过N = 1500
,则内核启动失败(以下消息多次出现):
========= Invalid __global__ write of size 8
=========
========= Program hit cudaErrorLaunchFailure (error 4) due to "unspecified launch failure" on CUDA API call to cudaLaunch.
据我了解,这是由于尝试写出已分配的共享内存的边界。当在内核中全局内存被复制到共享内存中时会发生这种情况:
__global__ void kernel_function(const size_t N, double *pN, ...)
{
unsigned int idx = threadIdx.x + blockDim.x * blockIdx.x;
if(idx<N)
{
extern __shared__ double pN_shared[];
for(int i=0; i < 4; i++)
{
pN_shared[4*idx + i] = pN[4*idx + i];
}
...
}
}
此错误仅在N > 1500
时发生,因此当共享内存的总量超过 48kB (1500 * 4 * sizeof(double) = 1500 * 32 = 48000
)时。
无论网格和块大小如何,此限制都是相同的。
如果我已正确理解CUDA如何工作,网格使用的共享内存的累计数量不限于 48kB ,这只是<的限制<可以由单个线程块使用的em>共享内存。
这个错误对我没有意义,因为共享内存的累积量应该只影响在流式多处理器之间调度网格的方式(而且GPU设备有15个SM可供使用)。
答案 0 :(得分:3)
您在此处动态分配的共享内存量:
kernel_function<<<GRID_SIZE, BLOCK_SIZE, SHARED_MEM_SIZE>>>(N, ...);
^^^^^^^^^^^^^^^
是每个线程块的金额</ em>,该金额限制为48KB(49152,而不是48000)。因此,如果您尝试在那里分配超过48KB,则在检查时会出现错误。
但是我们可以从中得出两个结论:
========= Invalid __global__ write of size 8
所以一般来说我认为你的结论是不正确的,你可能需要做更多的调试,而不是得出关于共享内存的结论。
如果要跟踪内核中特定代码行的无效全局写入的来源,可能会感兴趣this question/answer。
答案 1 :(得分:1)
您正在索引idx * 4 + 0:3访问共享阵列。从N&gt;开始,程序不正确。 BLOCK_SIZE。 幸运的是它似乎可以工作到1500.但是使用cuda mem-check应该指出问题。 在相关主题上,请注意在另一个位置静态分配的共享内存可能使用共享内存。打印出指针的值有助于搞清楚。
答案 2 :(得分:0)
我认为这里的问题是块内的所有线程必须在同一个SM中运行。因此,每个块仍然具有48kB共享内存的硬限制。在该块中运行多少线程并不重要。调度无关紧要,因为GPU无法跨多个SM拆分块中的线程。如果可以,我会尝试减少BLOCK_SIZE,因为这将直接确定每个块的共享内存量。但是,如果将其减少太多,则可能会遇到未充分利用SM中的计算资源的问题。这是一种平衡行为,根据我的经验,CUDA架构提供了许多有趣的权衡取舍。
同样在你的情况下,我甚至不确定你需要共享内存。我只想使用局部变量。我认为局部变量存储在全局内存中,但对它们的访问是一致的,所以它非常快。如果你想用共享内存做一些整洁的事情来改善性能,那就是我的N-Body模拟器的OpenCL内核。使用共享内存为块中的每个线程创建缓存为我提供了大约10倍的加速。
在这个模型中,每个线程负责计算由于每个其他身体上的重力吸引力而在一个身体上的加速度。这需要每个线程循环遍历所有N个主体。这是通过共享内存缓存增强的,因为块中的每个线程可以将不同的主体加载到共享内存中,并且它们可以共享它们。
__kernel void acceleration_kernel
(
__global const double* masses,
__global const double3* positions,
__global double3* accelerations,
const double G,
const int N,
__local double4* cache //shared memory cache (local means shared memory in OpenCL)
)
{
int idx = get_global_id(0);
int lid = get_local_id(0);
int lsz = get_local_size(0);
if(idx >= N)
return;
double3 pos = positions[idx];
double3 a = { };
//number of loads required to compute accelerating on Body(idx) from all other bodies
int loads = (N + (lsz - 1)) / lsz;
for(int load = 0; load < loads; load++)
{
barrier(CLK_LOCAL_MEM_FENCE);
//compute which body this thread is responsible for loading into the cache
int load_index = load * lsz + lid;
if(load_index < N)
cache[lid] = (double4)(positions[load_index], masses[load_index]);
barrier(CLK_LOCAL_MEM_FENCE);
//now compute the acceleration from every body added to the cache
for(int i = load * lsz, j = 0; i < N && j < lsz; i++, j++)
{
if(i == idx)
continue;
double3 r_hat = cache[j].xyz - pos;
double over_r = rsqrt(0.0001 + r_hat.x * r_hat.x + r_hat.y * r_hat.y + r_hat.z * r_hat.z);
a += r_hat * G * cache[j].w * over_r * over_r * over_r;
}
}
accelerations[idx] = a;
}
double3 pos = positions[idx];
double3 a = { };
int loads = (N + (lsz - 1)) / lsz;
for(int load = 0; load < loads; load++)
{
barrier(CLK_LOCAL_MEM_FENCE);
int load_index = load * lsz + lid;
if(load_index < N)
cache[lid] = (double4)(positions[load_index], masses[load_index]);
barrier(CLK_LOCAL_MEM_FENCE);
for(int i = load * lsz, j = 0; i < N && j < lsz; i++, j++)
{
if(i == idx)
continue;
double3 r_hat = cache[j].xyz - pos;
double over_r = rsqrt(0.0001 + r_hat.x * r_hat.x + r_hat.y * r_hat.y + r_hat.z * r_hat.z);
a += r_hat * G * cache[j].w * over_r * over_r * over_r;
}
}
accelerations[idx] = a;
}