在Power 8上使用Cuda Unified Memory进行多维数组分配

时间:2016-11-02 19:30:56

标签: multidimensional-array cuda

我试图在Power 8系统上使用CUDA UMA来分配多维数组。但是,我有问题,而规模越来越大。我使用的代码如下。当尺寸为24 x 24 x 24 x 5时工作正常。当我将它增加到64 x 64 x 64 x 8时,我正在使用"内存不足"即使我的设备中有内存。 Afaik,我想能够通过UMA分配内存和GPU设备物理内存一样多。所以我不希望有任何错误。目前我的主要配置是Power 8和Tesla k40,我在运行时遇到seg故障。但是,我尝试了我在x86 + k40机器上提供的代码片。令人惊讶地工作。

顺便说一句,如果除了将我的所有代码从4d数组转换为1d数组之外,如果你告诉我另一种方法,我会非常感激。

提前致谢

司机:Nvidia 361

#include <stdio.h>
#include <string.h>
#include <ctype.h>

#define MAXTOKEN 100
enum { NAME, PARENS, BRACKETS };

int tokentype;
char token[MAXTOKEN]; /*last token string */
char name[MAXTOKEN]; /*identifier name */
char datatype[MAXTOKEN]; /*data type = char, int, etc. */
char out[1000];

void dcl(void);
void dirdcl(void);
int gettoken(void);

/*
Grammar:

    dcl:            optional * direct-dcl
    direct-dcl:     name
                    (dcl)
                    direct-dcl()
                    direct-dcl[optional size]
*/

int main() /* convert declaration to words */
{
    while (gettoken() != EOF) {    /* 1st token on line */

        /* 1. gettoken() gets the datatype from the token  */
        strcpy(datatype, token);

        /* 2. Init out to end of the line? */
        /* out[0] = '\0'; */

         /* parse rest of line */
        dcl();

        if (tokentype != '\n')
            printf("syntax error\n");

        printf("%s: %s %s\n", name, out, datatype);
    }

    return 0;
}

int gettoken(void) /* return next token */
{
    int c, getch(void);
    void ungetch(int);
    char *p = token;

    /* Skip blank spaces and tabs */
    while ((c = getch()) == ' ' || c == '\t')
        ;

    if (c == '(') {

        if ((c = getch()) == ')') {

            strcpy(token, "()");
            return tokentype = PARENS;

        } else {
            ungetch(c);
            return tokentype = '(';
        }

    } else if (c == '[') {

        for (*p++ = c; (*p++ = getch()) != ']'; )
            ;

        *p = '\0';
        return tokentype = BRACKETS;

    } else if (isalpha(c)) {

        /* Reads the next character of input */
        for (*p++ = c; isalnum(c = getch()); ) {
            *p++ = c;
        }

        *p = '\0';
        ungetch(c); /* Get back the space, tab */

        return tokentype = NAME;

    } else
        return tokentype = c;
}

/* dcl: parse a declarator */
void dcl(void)
{
    int ns;

    for (ns = 0; gettoken() == '*'; ) /* count *'s */
        ns++;

    dirdcl();

    while (ns-- > 0)
        strcat(out, " pointer to");
}

/* dirdcl: parse a direct declarator */
void dirdcl(void)
{
    int type;

    if (tokentype == '(') {

        dcl();

        if (tokentype != ')')
            printf("error: missing )\n");

    }
    else if (tokentype == NAME) /* variable name */ {
        strcpy(name, token);
        printf("token: %s\n", token);
    }
    else
        printf("error: expected name or (dcl)\n");

    while ((type = gettoken()) == PARENS || type == BRACKETS) {

        if (type == PARENS)
            strcat(out, " function returning");
        else {
            strcat(out, " array");
            strcat(out, token);
            strcat(out, " of");
        }

    }
}

1 个答案:

答案 0 :(得分:2)

您的交叉发布here上有相当多的对话,包括对您的主要问题的回答。我将使用这个答案来总结那里的内容并特别回答这个问题:

  顺便说一句,如果除了将我的所有代码从4d数组转换为1d数组之外,如果你告诉我另一种方法,我会非常感激。

  1. 您的一个主张是您正在进行正确的错误检查(&#34;我发现了错误的错误。&#34;)。你不是。 CUDA运行时API调用(包括cudaMallocManaged)本身 not 生成C ++样式异常,因此throw运算符定义上的new specification是无意义的。 CUDA运行时API调用返回错误代码。如果要进行正确的错误检查,则必须收集此错误代码并进行处理。如果您收集错误代码,可以使用它来生成异常,如果您愿意,可以使用规范proper CUDA error checking问题包含一个如何执行此操作的示例,作为Jared Hoberock的答案之一。由于这种疏忽,当您的分配最终失败时,您忽略了这一点,然后当您尝试将这些(非)分配区域用于后续指针存储时,会生成seg错误。

  2. 分配失败的近因是您实际上内存不足,如交叉发布中所述。您可以通过正确的错误检查轻松确认。托管分配具有粒度,因此当您请求相对较少量的分配时,实际上使用的内存比您想象的要多 - 您请求的小分配每个都被舍入到分配粒度。分配粒度的大小因系统类型而异,因此您运行的OpenPower系统具有比您比较的x86系统大得多的分配粒度,因此您在x86系统上没有耗尽内存,但你是在电力系统上。正如您在交叉发布中所讨论的那样,通过对cudaMemGetInfo的战略调用很容易验证。

  3. 从性能角度来看,这是一个非常糟糕的多维分配方法,原因如下:

    1. 您创建的分配是不相交的,通过指针连接。因此,要通过指针解除引用来访问元素,它需要3或4个这样的解引用来通过4个下标的指针数组。这些解引用中的每一个都涉及设备存储器访问。与将模拟4-D访问用于1-D(平坦)分配相比,这将明显变慢。与将4-D模拟访问转换为单个线性索引相关的算法将比通过指针追逐遍历内存快得多。

    2. 由于您创建的分配是不相交的,因此托管内存子系统无法将它们合并为单个传输,因此,在引擎盖下,将发生大量等于前3个维度的产品的转移,在内核启动时(并且可能在终止时,即在下一个cudaDeviceSynchronize()调用时)。当然,这些数据必须全部转移,但与单一转移相比,您将进行大量非常小的转移。分配。大量小额转移的相关开销可能很大。

    3. 正如我们所见,分配粒度会严重影响这种分配方案的内存使用效率。什么应该只使用一小部分系统内存最终使用所有系统内存。

    4. 处理来自&#34; row&#34;的连续数据的操作到&#34;行&#34;这样的分配将失败,因为分配是不相交的。例如,这样的矩阵或这种矩阵的子部分不能可靠地传递给CUBLAS线性代数例程,因为对该矩阵的期望将与与其相关联的存储器中的行存储具有连续性。

    5. 理想的解决方案是创建单个平面分配,然后使用模拟的4-D索引创建单个线性索引。这种方法将解决上述所有4个问题。但是,它可能需要大量的代码重构。

      然而,我们可以提出一种替代方法,该方法保留4下标索引,但通过创建单个基础平面分配来解决上述第2,3和4项中的问题。

      以下是一个有效的例子。我们实际上将创建2个托管分配:一个用于数据存储的底层平面分配,以及一个用于指针存储的底层平面分配(无论维度如何)。可以将这两者组合成一个单独的分配,并进行一些仔细的调整工作,但这并不是实现任何建议的好处所必需的。

      SO标签上的各种其他CUDA问题涵盖了基本方法,但大多数问题都在主机端使用(仅限),因为它们没有UM视图。但是,UM允许我们将方法扩展到主机和设备端使用。我们将首先创建一个单一的基地&#34;分配存储数据所需的大小。然后我们将为指针数组创建一个分配,然后我们将通过指针数组,修复每个指针以指向指针数组中的正确位置,或者指向&#34; base&#中的正确位置34;数据阵列。

      这是一个有用的示例,演示了主机和设备的使用情况,并包括正确的错误检查:

      $ cat t1271.cu
      #include <iostream>
      #include <assert.h>
      
      template<typename T>
      T**** create_4d_flat(int a, int b, int c, int d){
          T *base;
          cudaError_t err = cudaMallocManaged(&base, a*b*c*d*sizeof(T));
          assert(err == cudaSuccess);
          T ****ary;
          err = cudaMallocManaged(&ary, (a+a*b+a*b*c)*sizeof(T*));
          assert(err == cudaSuccess);
          for (int i = 0; i < a; i++){
            ary[i] =  (T ***)((ary + a) + i*b);
            for (int j = 0; j < b; j++){
              ary[i][j] = (T **)((ary + a + a*b) + i*b*c + j*c);
              for (int k = 0; k < c; k++)
                ary[i][j][k] = base + ((i*b+j)*c + k)*d;}}
          return ary;
      }
      
      template<typename T>
      void free_4d_flat(T**** ary){
          if (ary[0][0][0]) cudaFree(ary[0][0][0]);
          if (ary) cudaFree(ary);
      }
      
      
      template<typename T>
      __global__ void fill(T**** data, int a, int b, int c, int d){
        unsigned long long int val = 0;
        for (int i = 0; i < a; i++)
          for (int j = 0; j < b; j++)
            for (int k = 0; k < c; k++)
              for (int l = 0; l < d; l++)
                data[i][j][k][l] = val++;
      }
      
      void report_gpu_mem()
      {
          size_t free, total;
          cudaMemGetInfo(&free, &total);
          std::cout << "Free = " << free << " Total = " << total <<std::endl;
      }
      
      int main() {
         report_gpu_mem();
      
         unsigned long long int ****data2;
         std::cout << "allocating..." << std::endl;
         data2 = create_4d_flat<unsigned long long int>(64, 63, 62, 5);
      
         report_gpu_mem();
      
         fill<<<1,1>>>(data2, 64, 63, 62, 5);
         cudaError_t err = cudaDeviceSynchronize();
         assert(err == cudaSuccess);
      
         std::cout << "validating..." << std::endl;
         for (int i = 0; i < 64*63*62*5; i++)
           if (*(data2[0][0][0] + i) != i) {std::cout << "mismatch at "  << i << " was " << *(data2[0][0][0] + i) << std::endl; return -1;}
         free_4d_flat(data2);
         return 0;
      }
      $ nvcc -arch=sm_35 -o t1271 t1271.cu
      $ cuda-memcheck ./t1271
      ========= CUDA-MEMCHECK
      Free = 5904859136 Total = 5975900160
      allocating...
      Free = 5892276224 Total = 5975900160
      validating...
      ========= ERROR SUMMARY: 0 errors
      $
      

      注意:

      1. 这仍然涉及指针追逐效率低下。我不知道在不删除多个下标排列的情况下避免这种情况的方法。

      2. 我选择在主机和设备代码中使用2种不同的索引方案。在设备代码中,我使用普通的4下标索引来演示它的实用程序。在主机代码中,我正在使用&#34; flat&#34; index,用于证明底层存储是连续且连续可寻址的。