在单个内核中分配和释放内存比在一个内核中分配然后在另一个内核中释放更慢

时间:2014-07-16 16:51:03

标签: c++ memory-management cuda

背景

我有CUDA应用程序,我希望重现我如何改进性能。我最终编写了整个代码,但无法重现性能提升。然后我注意到原始代码中有内存泄漏。所以我添加了所需的删除,然后我的表现暴跌。

现在我理解内存分配和删除应该有一些开销。但我接着做了一个测试,我发现如果我在一个内核中分配然后在另一个内核中执行内存解除分配,我就不会受到性能损失。

我有一些示例代码和性能输出。从示例中你平均得到2.64加速(忽略真正奇怪的第一个)。

在我的真实世界的例子中,虽然我看到了大约10的加速。例如,我使用两个内核需要45分钟,一个内核需要8小时。

问题

有没有人知道发生了什么?

我唯一能想到的是这两种情况:

  1. 有一个错误
  2. 删除确实需要很长时间
    • 在第一个内核中等待它完成
    • 在第二个内核中,它调用delete并立即返回。
  3. 设置

    操作系统:

    Windows 7 64位

    nvcc --version:

    建于Fri_Mar_14_19:30:01_PDT_2014 Cuda编译工具,6.0版,V6.0.1

    编译器选项:

    nvcc -O3 -arch = sm_35 forty_hours_debugging.cu

    --- --- EDIT

    GPU:

    GTX 780

    代码

    #include <iostream>
    #include <string>
    
    __device__ int *D_DATA[1000];
    
    __global__ void MyAllocate(int size,bool perform_delete) {
        int *data;
        __shared__ int *t_data;
    
        if(threadIdx.x==0) {
            t_data = new int[size];
        }__syncthreads();
    
        if(t_data==NULL) return;
    
        D_DATA[blockIdx.x] = data= t_data;  
    
        memset(data,0,size);
        __syncthreads();
    
        for(int i = threadIdx.x;i < size; i+= blockDim.x) {
                data[i] = i * i + perform_delete * i;
        }__syncthreads();
    
        // If we should delete then do so, otherwise another kernel (hopefully) will
        if(threadIdx.x==0 && perform_delete) {
            delete data;
        }
    }
    
    __global__ void MyDelete() {
        delete D_DATA[blockIdx.x];
    }
    
    int main(int argc,char **argv) {    
    
        cudaEvent_t start,stop;
        float time;
        const std::string pre[2] = {"One Kernel ","Two Kernels "};  
    
        for(int size = 1000000; size < 1024000000; size *= 2) {
            for(int i = 0; i < 2; ++i) {
    
                cudaEventCreate(&start);
                cudaEventCreate(&stop);
    
                cudaEventRecord(start,0);
                MyAllocate<<<1000,128>>>(size,i==0);
                if(i!=0) {
                    MyDelete<<<1000,1>>>();
                }
                cudaEventRecord(stop,0);
                cudaEventSynchronize(stop);
                cudaEventElapsedTime(&time, start, stop);
                std::cout << pre[i] << "Time : " << (time) << "ms" << std::endl;
    
                cudaEventDestroy(start);
                cudaEventDestroy(stop);
            }
            cudaDeviceReset();
            std::cout << std::endl;
        }
        return 0;
    }
    

    结果

    One Kernel Time : 88.3073ms
    Two Kernels Time : 88.3073ms
    
    One Kernel Time : 0.109024ms
    Two Kernels Time : 0.042912ms
    
    One Kernel Time : 0.11184ms
    Two Kernels Time : 0.042272ms
    
    One Kernel Time : 0.1072ms
    Two Kernels Time : 0.042688ms
    
    One Kernel Time : 0.098464ms
    Two Kernels Time : 0.042208ms
    
    One Kernel Time : 0.103776ms
    Two Kernels Time : 0.0432ms
    
    One Kernel Time : 0.111776ms
    Two Kernels Time : 0.04256ms
    
    One Kernel Time : 0.114592ms
    Two Kernels Time : 0.0424ms
    
    One Kernel Time : 0.109888ms
    Two Kernels Time : 0.042656ms
    
    One Kernel Time : 0.119456ms
    Two Kernels Time : 0.042336ms
    

1 个答案:

答案 0 :(得分:1)

您错过了一个非常重要的观点,那就是设备堆的大小(设备newmalloc从哪个接收分配)is limited。默认限制为8MB。您可以调整此限制。 (阅读文档。)

您的第一次分配恰好符合8MB限制(== 4MB),因此分配(由单个线程块请求)正在成功。第一次启动时的其他线程块失败,其余的分配都是8MB或更大,并且都失败了。所有这些数据都没有表明你的想法。

我建议在提前退出时提出某种明确的通知(printf或其他):

if(t_data==NULL) return;

这是您的示例的修改版本,完全修复了错误。两个内核时间似乎不比一个内核时间快:

$ cat t482.cu
#include <iostream>
#include <string>


#define cudaCheckErrors(msg) \
    do { \
        cudaError_t __err = cudaGetLastError(); \
        if (__err != cudaSuccess) { \
            fprintf(stderr, "Fatal error: %s (%s at %s:%d)\n", \
                msg, cudaGetErrorString(__err), \
                __FILE__, __LINE__); \
            fprintf(stderr, "*** FAILED - ABORTING\n"); \
            exit(1); \
        } \
    } while (0)

__device__ int *D_DATA[1000];

__global__ void MyAllocate(int size,bool perform_delete) {
    int *data;
    __shared__ int *t_data;

    if(!threadIdx.x)
        t_data = new int[size];
    __syncthreads();

    if(t_data==NULL) {if(!threadIdx.x) printf("oops!\n"); return;}

    if(!threadIdx.x) D_DATA[blockIdx.x] = t_data;
    data = t_data;

    memset(data,0,size);
    __syncthreads();

    for(int i = threadIdx.x;i < size; i+= blockDim.x)
            data[i] = i * i + perform_delete * i;
    __syncthreads();

    // If we should delete then do so, otherwise another kernel (hopefully) will
    if((!threadIdx.x) && (perform_delete))
        delete data;

}

__global__ void MyDelete() {
    delete D_DATA[blockIdx.x];
}

int main(int argc,char **argv) {

    cudaEvent_t start,stop;
    float time;
    const std::string pre[2] = {"One Kernel ","Two Kernels "};

    for(int size = 1000000; size < 100000000; size *= 2) {
        cudaDeviceSetLimit(cudaLimitMallocHeapSize, 500000000);
        cudaCheckErrors("set limit fail");
        for(int i = 0; i < 2; ++i) {

            cudaEventCreate(&start);
            cudaEventCreate(&stop);

            cudaEventRecord(start,0);
            MyAllocate<<<1,128>>>(size,i==0);
            if(i!=0) {
                MyDelete<<<1,1>>>();
            }
            cudaEventRecord(stop,0);
            cudaEventSynchronize(stop);
            cudaEventElapsedTime(&time, start, stop);
            std::cout << pre[i] << "Time : " << (time) << "ms" << std::endl;

            cudaEventDestroy(start);
            cudaEventDestroy(stop);
            cudaCheckErrors("some error");
        }
        cudaDeviceReset();
        std::cout << std::endl;
    }
    return 0;
}
$ nvcc -arch=sm_20 -o t482 t482.cu
$ ./t482
One Kernel Time : 139.846ms
Two Kernels Time : 139.37ms

One Kernel Time : 280.762ms
Two Kernels Time : 274.804ms

One Kernel Time : 559.386ms
Two Kernels Time : 549.536ms

One Kernel Time : 1101.04ms
Two Kernels Time : 1114.58ms

One Kernel Time : 2199.96ms
Two Kernels Time : 2229.1ms

One Kernel Time : 4397.82ms
Two Kernels Time : 4458.15ms

One Kernel Time : 8793.6ms
Two Kernels Time : 8916.23ms

$

请注意,您的cudaDeviceReset()也会重置设备限制,这就是必须将其置于正确循环中的正确位置的原因。