2

这个问题是关于 cuda 中的堆大小限制。访问了一些关于这个主题的问题,包括这个: 内核中的新运算符..奇怪的行为 我做了一些测试。给定一个内核如下:

#include <cuda.h>
#include <cuda_runtime.h>
#define CUDA_CHECK( err ) __cudaSafeCall( err, __FILE__, __LINE__ )
#define CUDA_CHECK_ERROR()    __cudaCheckError( __FILE__, __LINE__ )
inline void __cudaSafeCall( cudaError err, const char *file, const int line )
{
    if ( cudaSuccess != err )
    {
        fprintf( stderr, "cudaSafeCall() failed at %s:%i : %s\n",
                file, line, cudaGetErrorString( err ) );
        exit( -1 );
    }
    return;
}

inline void __cudaCheckError( const char *file, const int line )
{
    cudaError err = cudaGetLastError();
    if ( cudaSuccess != err )
    {
        fprintf( stderr, "cudaCheckError() failed at %s:%i : %s\n",
                file, line, cudaGetErrorString( err ) );
        exit( -1 );
    }
    return;
}
#include <stdio>
#define NP 900000
__device__ double *temp;
__device__ double *temp2;
__global__
void test(){
int i = blockDim.x*blockIdx.x + threadIdx.x;
if(i==0){
    temp = new double[NP];
    //temp2 = new double[NP];
}

if(i==0){
    for(int k=0;k<NP;k++){
        temp[i] = 1.;
        if(k%1000 == 0){
            printf("%d : %g\n", k, temp[i]);
        }
    }
}
if(i==0){
    delete(temp);
    //delete(temp2);
}
}
int main(){
    //cudaDeviceSetLimit(cudaLimitMallocHeapSize, 32*1024*1024);
    //for(int k=0;k<2;k++){
        test<<<ceil((float)NP/512), 512>>>();
        CUDA_CHECK_ERROR();
    //}
    return 0;
}

我想测试堆大小限制。

  1. 使用一个线程动态分配一个数组(临时),其大小大约超过 960,000*sizeof(double)(接近 8MB,这是堆大小的默认限制)会产生错误:好的。900,000 件作品。(有人知道如何计算真正的极限吗?)
  2. 提高堆大小限制允许分配更多内存:正常,好的。
  3. 回到 8MB 堆大小,为每个线程分配一个数组和两个线程(因此,将 if (i==0) 替换为 if(i==0 || i==1),每个 900,000 * sizeof(double) 失败. 但是每个 450,000*sizeof(double) 都可以。仍然可以。
  4. 我的问题来了:用一个线程分配两个数组(因此,线程 0 的 temp 和 temp2),每个 900,000 * sizeof(double) 也可以,但它不应该吗?事实上,当我尝试在两个数组中写入时,它都会失败。但是任何人都有一个想法,为什么在使用带有一个线程的两个数组而不是带有两个线程的两个数组时会出现这种不同的分配行为?

编辑:另一个测试,我觉得对于像我一样学习堆使用的人来说很有趣: 5. 执行内核两次,一个大小为 900,000 * sizeof(double) 的数组由单线程 0 分配,如果有删除工作。如果 delete 省略,第二次会失败,但会执行第一次调用。

编辑 2:如何分配设备范围的变量一次但可由所有线程写入(不是来自主机,在设备代码中使用动态分配)?

4

1 回答 1

3

可能您没有在new操作上测试返回的空指针,这是C++ 中操作员报告失败的有效方法

当我如下修改您的代码时,我收到消息“第二个新失败”:

#include <stdio.h>

 #define NP 900000
__device__ double *temp;
__device__ double *temp2;
__global__
void test(){
int i = blockDim.x*blockIdx.x + threadIdx.x;
if(i==0){
    temp = new double[NP];
    if (temp == 0) {printf("first new failed\n"); return;}
    temp2 = new double[NP];
    if (temp2 == 0) {printf("second new failed\n"); return;}
}

if(i==0){
    for(int k=0;k<NP;k++){
        temp[i] = 1.;
        if(k%1000 == 0){
            printf("%d : %g\n", k, temp[i]);
        }
    }
}
if(i==0){
    delete(temp);
    delete(temp2);
}
}

int main() {

  test<<<1,1>>>();
  cudaDeviceSynchronize();
  return 0;
}

如果你提供一个完整的、可编译的代码供其他人使用,就像我一样,那会很方便。

对于您的第一个 EDIT 问题,如果第一个被删除,第二个 new 将起作用也就不足为奇了。第一个分配了几乎所有可用的 8MB。如果您删除该分配,那么第二个分配将成功。参考文档,我们看到以这种方式动态分配的内存在 cuda 上下文的整个生命周期中都存在,或者直到执行相应的删除操作(即不只是单个内核调用。内核的完成不一定释放分配。)

对于您的第二个 EDIT 问题,您已经使用__device__ double *temp;指针演示了一种方法,一个线程可以通过该方法分配所有线程都可以访问的存储空间。但是,跨块会出现问题,因为无法保证块之间的同步顺序或块之间的执行顺序,因此如果您从块 0 中的线程 0 进行分配,则只有在块 0 在其他块之前执行时才有用。您可以提出一个复杂的方案来检查变量分配是否已经完成(可能通过测试指针是否为 NULL,也可能使用原子),但它会创建脆弱的代码。最好提前计划您的全局分配,并从主机进行相应的分配。

于 2013-07-29T16:53:16.613 回答