@talonmies 回答了您关于如何在内核中动态分配内存的问题。这旨在作为补充答案,解决__device__ malloc()
您可能要考虑的性能和替代方案。
在内核中动态分配内存可能很诱人,因为它允许 GPU 代码看起来更像 CPU 代码。但它会严重影响性能。我写了一个独立的测试并将其包含在下面。该测试启动了大约 260 万个线程。每个线程使用从线程索引派生的一些值填充 16 个整数的全局内存,然后对这些值求和并返回总和。
该测试实现了两种方法。第一种方法使用__device__ malloc()
,第二种方法使用在内核运行之前分配的内存。
在我的 2.0 设备上,内核在使用时运行时间为 1500__device__ malloc()
毫秒,在使用预分配内存时运行时间为 27 毫秒。换句话说,当内存在内核中动态分配时,测试的运行时间要长 56 倍。时间包括外部循环cudaMalloc()
/ cudaFree()
,它不是内核的一部分。如果使用相同数量的线程多次启动同一个内核(通常是这种情况),则cudaMalloc()
/的成本将cudaFree()
在所有内核启动中分摊。这使差异更大,达到 60 倍左右。
推测,我认为性能下降部分是由隐式序列化引起的。GPU 可能必须序列化所有同时调用__device__ malloc()
,以便为每个调用者提供单独的内存块。
不使用的版本会__device__ malloc()
在运行内核之前分配所有的 GPU 内存。指向内存的指针被传递给内核。每个线程计算一个索引到先前分配的内存中,而不是使用__device__ malloc()
.
预先分配内存的潜在问题是,如果只有一些线程需要分配内存,并且不知道这些线程是哪些线程,则有必要为所有线程分配内存。如果没有足够的内存,那么减少每个内核调用的线程数可能比使用__device__ malloc()
. 其他解决方法可能最终会重新实现 __device__ malloc()
在后台执行的操作,并且会看到类似的性能损失。
测试性能__device__ malloc()
:
#include "cuda_runtime.h"
#include "device_launch_parameters.h"
#include <stdio.h>
const int N_ITEMS(16);
#define USE_DYNAMIC_MALLOC
__global__ void test_malloc(int* totals)
{
int tx(blockIdx.x * blockDim.x + threadIdx.x);
int* s(new int[N_ITEMS]);
for (int i(0); i < N_ITEMS; ++i) {
s[i] = tx * i;
}
int total(0);
for (int i(0); i < N_ITEMS; ++i) {
total += s[i];
}
totals[tx] = total;
delete[] s;
}
__global__ void test_malloc_2(int* items, int* totals)
{
int tx(blockIdx.x * blockDim.x + threadIdx.x);
int* s(items + tx * N_ITEMS);
for (int i(0); i < N_ITEMS; ++i) {
s[i] = tx * i;
}
int total(0);
for (int i(0); i < N_ITEMS; ++i) {
total += s[i];
}
totals[tx] = total;
}
int main()
{
cudaError_t cuda_status;
cudaSetDevice(0);
int blocks_per_launch(1024 * 10);
int threads_per_block(256);
int threads_per_launch(blocks_per_launch * threads_per_block);
int* totals_d;
cudaMalloc((void**)&totals_d, threads_per_launch * sizeof(int));
cudaEvent_t start, stop;
cudaEventCreate(&start);
cudaEventCreate(&stop);
cudaDeviceSynchronize();
cudaEventRecord(start, 0);
#ifdef USE_DYNAMIC_MALLOC
cudaDeviceSetLimit(cudaLimitMallocHeapSize, threads_per_launch * N_ITEMS * sizeof(int));
test_malloc<<<blocks_per_launch, threads_per_block>>>(totals_d);
#else
int* items_d;
cudaMalloc((void**)&items_d, threads_per_launch * sizeof(int) * N_ITEMS);
test_malloc_2<<<blocks_per_launch, threads_per_block>>>(items_d, totals_d);
cudaFree(items_d);
#endif
cuda_status = cudaDeviceSynchronize();
if (cuda_status != cudaSuccess) {
printf("Error: %d\n", cuda_status);
exit(1);
}
cudaEventRecord(stop, 0);
cudaEventSynchronize(stop);
float elapsedTime;
cudaEventElapsedTime(&elapsedTime, start, stop);
printf("Elapsed: %f\n", elapsedTime);
int* totals_h(new int[threads_per_launch]);
cuda_status = cudaMemcpy(totals_h, totals_d, threads_per_launch * sizeof(int), cudaMemcpyDeviceToHost);
if (cuda_status != cudaSuccess) {
printf("Error: %d\n", cuda_status);
exit(1);
}
for (int i(0); i < 10; ++i) {
printf("%d ", totals_h[i]);
}
printf("\n");
cudaFree(totals_d);
delete[] totals_h;
return cuda_status;
}
输出:
C:\rd\projects\test_cuda_malloc\Release>test_cuda_malloc.exe
Elapsed: 27.311169
0 120 240 360 480 600 720 840 960 1080
C:\rd\projects\test_cuda_malloc\Release>test_cuda_malloc.exe
Elapsed: 1516.711914
0 120 240 360 480 600 720 840 960 1080