【问题标题】:Pointer to Array of Pointers to Objects in CUDA指向 CUDA 中对象的指针数组的指针
【发布时间】:2016-12-23 00:42:03
【问题描述】:

我遵循了this questionthis link 提供的指导,它们处理了将指针数组传递给设备并返回的概念,但是当指针指向对象时,我似乎在处理我的具体情况。请参阅下面的示例代码,为简洁起见,我删除了错误检查。

// Kernel
__global__ void myKernel(Obj** d_array_of_objs)
{
    // Change the scalar of each object to 5
    // by dereferencing device array to get 
    // appropriate object pointer.
    *d_array_of_objs->changeToFive();    <--------- SEE QUESTION 4
}

// Entry point
int main()
{

    /********************************/
    /* INITIALISE OBJ ARRAY ON HOST */
    /********************************/

    // Array of 3 pointers to Objs
    Obj* h_obj[3];
    for (int i = 0; i < 3; i++) {
        h_obj[i] = new Obj();       // Create
        h_obj[i]->scalar = i * 10;  // Initialise
    }

    // Write out
    for (int i = 0; i < 3; i++) {
        std::cout << h_obj[i]->scalar << std::endl;
    }


    /**************************************************/
    /* CREATE DEVICE VERSIONS AND STORE IN HOST ARRAY */
    /**************************************************/

    // Create host pointer to array-like storage of device pointers
    Obj** h_d_obj = (Obj**)malloc(sizeof(Obj*) * 3);    <--------- SEE QUESTION 1
    for (int i = 0; i < 3; i++) {
        // Allocate space for an Obj and assign
        cudaMalloc((void**)&h_d_obj[i], sizeof(Obj));
        // Copy the object to the device (only has single scalar field to keep it simple)
        cudaMemcpy(h_d_obj[i], &(h_obj[i]), sizeof(Obj), cudaMemcpyHostToDevice);
    }

    /**************************************************/
    /* CREATE DEVICE ARRAY TO PASS POINTERS TO KERNEL */
    /**************************************************/

    // Create a pointer which will point to device memory
    Obj** d_d_obj = nullptr;
    // Allocate space for 3 pointers on device at above location
    cudaMalloc((void**)&d_d_obj, sizeof(Obj*) * 3);
    // Copy the pointers from the host memory to the device array
    cudaMemcpy(d_d_obj, h_d_obj, sizeof(Obj*) * 3, cudaMemcpyHostToDevice);


    /**********
     * After the above, VS2013 shows the memory pointed to by d_d_obj 
     * to be NULL <------- SEE QUESTION 2.
     **********/


    // Launch Kernel
    myKernel <<<1, 3>>>(d_d_obj);

    // Synchronise and pass back to host
    cudaDeviceSynchronize();
    for (int i = 0; i < 3; i++) {
        cudaMemcpy(&(h_obj[i]), h_d_obj[i], sizeof(Obj), cudaMemcpyDeviceToHost);     <--------- SEE QUESTION 3
    }

    // Write out
    for (int i = 0; i < 3; i++) {
        std::cout << h_obj[i]->scalar << std::endl;
    }

    return 0;
}

所以问题是:

  1. 如果上面SEE QUESTION 1指示的行为指针分配主机内存,并且一旦我在后续循环中使用cudaMalloc分配设备内存,h_d_obj指向的指针就会被设备地址覆盖,这是否意味着我已经为 3 Obj* 分配了主机内存,现在没有指针指向它?

  2. 为什么当我测试返回的状态时cudaMemcpy 成功但显然没有正确复制地址?我期待h_d_objd_d_obj 的内存地址“数组”相同,因为它们应该指向设备地址空间中的相同Obj

  3. SEE QUESTION 3 行,假设我在问题2 中是正确的。我还希望能够使用h_d_objd_d_obj 从设备中检索Obj 对象,因为区别仅在于我是否取消引用主机指针以访问指向Obj 的设备指针或设备指针,这两者我都可以在cudaMemcpy 方法中执行,对吗?如果我使用写入的内容,则复制成功,但h_obj[0] 处的指针已损坏,我无法写出数据。

  4. SEE QUESTION 4 行,为什么我不能取消引用Obj** 以获取Obj* 然后使用-&gt; 运算符调用device 方法?编译器抱怨它不是指向类类型的指针,事实上它是Obj* 告诉我它是。

【问题讨论】:

    标签: c++ c arrays pointers cuda


    【解决方案1】:

    首先,如果您提供完整的代码(包括Obj 类的定义)会很方便。我根据对您的代码的检查和一些猜测提供了一个。

    其次,您在这里的大部分困惑似乎是在 C(或 C++)中使用指针的不太清晰的工具。在主机和设备之间使用具有双指针结构 (**) 的 CUDA API 需要清晰的理解和可视化正在发生的事情的能力。

    如果上面 SEE QUESTION 1 指示的行为指针分配主机内存,并且一旦我在后续循环中使用 cudaMalloc 分配设备内存,h_d_obj 指向的指针将被设备地址覆盖,这是否意味着我已经为 3 个 Obj* 分配了主机内存,但现在没有指针指向它?

    没有。 h_d_objmalloc 操作建立(即赋予有意义的值)。之后您所做的任何操作都不会修改 h_d_obj 的值。

    为什么当我测试返回的状态时 cudaMemcpy 成功但显然没有正确复制地址?我期望h_d_objd_d_obj 的内存地址“数组”是相同的,因为它们应该指向设备地址空间中的同一个Obj。

    到目前为止,我认为您的代码没有任何问题。 h_d_obj 的值是(以前)由malloc 建立的,它的数值是主机内存中的一个地址。 d_d_obj 的值是由cudaMalloc 建立的,它的数值是设备内存中的一个地址。在数值上,我希望它们会有所不同。

    在第 3 行请参阅问题 3,假设我在问题 2 中是正确的。我还希望能够使用 h_d_objd_d_obj 从设备中检索 Obj 对象,因为区别仅在于是否我取消引用主机指针以访问指向 Obj 的设备指针或设备指针,这两者我都可以在 cudaMemcpy 方法中执行,对吗?如果我使用写入的内容,则复制成功,但 h_obj[0] 处的指针已损坏,我无法写出数据。

    没有。您不能取消引用主机代码中的设备指针,甚至如果它是cudaMemcpy 中的参数。这在 cudaMemcpy 操作中作为源或目标是合法的:

    h_d_obj[i]
    

    这是不合法的:

    d_d_obj[i]
    

    原因是为了获得实际的目标地址,我必须在第一种情况下取​​消引用主机指针(即访问主机上的内存位置),而在第二种情况下取​​消引用设备指针。从主机代码中,我可以检索到h_d_obj[i] 的内容。我不允许尝试在主机代码中检索d_d_obj[i] 的内容(cudaMemcpy 的参数操作是 host 代码)。 d_d_obj 的值可用作主机代码的目标。 d_d_obj[i] 不能。

    在 SEE QUESTION 4 行,为什么我不能取消引用 Obj** 以获取 Obj* 然后使用 -> 运算符调用设备方法?编译器抱怨它不是指向类类型的指针,它是 Obj* 的事实告诉我它是。

    编译器对您大吼大叫,因为您不了解您正在使用的各种运算符(*-&gt;)之间的操作顺序。如果您添加括号以标识正确的顺序:

    (*d_array_of_objs)->changeToFive(); 
    

    那么编译器不会反对(尽管我会在下面做一些不同的事情)。

    这是您的代码的修改版本,添加了Obj 定义,对内核进行了轻微更改,以便独立线程在独立对象上工作,以及其他一些修复。您的代码大部分是正确的:

    $ cat t1231.cu
    #include <iostream>
    
    class Obj{
    
      public:
      int scalar;
      __host__ __device__
      void changeToFive() {scalar = 5;}
    };
    
    // Kernel
    __global__ void myKernel(Obj** d_array_of_objs)
    {
        // Change the scalar of each object to 5
        // by dereferencing device array to get
        // appropriate object pointer.
        int idx = threadIdx.x+blockDim.x*blockIdx.x;
        // (*d_array_of_objs)->changeToFive();  //  <--------- SEE QUESTION 4 (add parenthesis)
        d_array_of_objs[idx]->changeToFive();
    }
    
    // Entry point
    int main()
    {
    
        /********************************/
        /* INITIALISE OBJ ARRAY ON HOST */
        /********************************/
    
        // Array of 3 pointers to Objs
        Obj* h_obj[3];
        for (int i = 0; i < 3; i++) {
            h_obj[i] = new Obj();       // Create
            h_obj[i]->scalar = i * 10;  // Initialise
        }
    
        // Write out
        for (int i = 0; i < 3; i++) {
            std::cout << h_obj[i]->scalar << std::endl;
        }
    
    
        /**************************************************/
        /* CREATE DEVICE VERSIONS AND STORE IN HOST ARRAY */
        /**************************************************/
    
        // Create host pointer to array-like storage of device pointers
        Obj** h_d_obj = (Obj**)malloc(sizeof(Obj*) * 3); //    <--------- SEE QUESTION 1
        for (int i = 0; i < 3; i++) {
            // Allocate space for an Obj and assign
            cudaMalloc((void**)&h_d_obj[i], sizeof(Obj));
            // Copy the object to the device (only has single scalar field to keep it simple)
            cudaMemcpy(h_d_obj[i], &(h_obj[i]), sizeof(Obj), cudaMemcpyHostToDevice);
        }
    
        /**************************************************/
        /* CREATE DEVICE ARRAY TO PASS POINTERS TO KERNEL */
        /**************************************************/
    
        // Create a pointer which will point to device memory
        Obj** d_d_obj = NULL;
        // Allocate space for 3 pointers on device at above location
        cudaMalloc((void**)&d_d_obj, sizeof(Obj*) * 3);
        // Copy the pointers from the host memory to the device array
        cudaMemcpy(d_d_obj, h_d_obj, sizeof(Obj*) * 3, cudaMemcpyHostToDevice);
    
    
        /**********
         * After the above, VS2013 shows the memory pointed to by d_d_obj
         * to be NULL <------- SEE QUESTION 2.
         **********/
    
    
        // Launch Kernel
        myKernel <<<1, 3>>>(d_d_obj);
    
        // Synchronise and pass back to host
        cudaDeviceSynchronize();
        for (int i = 0; i < 3; i++) {
            cudaMemcpy(h_obj[i], h_d_obj[i], sizeof(Obj), cudaMemcpyDeviceToHost);  //   <--------- SEE QUESTION 3  remove parenthesis
        }
    
        // Write out
        for (int i = 0; i < 3; i++) {
            std::cout << h_obj[i]->scalar << std::endl;
        }
    
        return 0;
    }
    $ nvcc -o t1231 t1231.cu
    $ cuda-memcheck ./t1231
    ========= CUDA-MEMCHECK
    0
    10
    20
    5
    5
    5
    ========= ERROR SUMMARY: 0 errors
    $
    

    h_d_objd_d_obj 的图表可能会有所帮助:

    HOST                               |    DEVICE
    h_d_obj-->(Obj *)-------------------------->Obj0<---(Obj *)<----|
              (Obj *)-------------------------->Obj1<---(Obj *)     |
              (Obj *)-------------------------->Obj2<---(Obj *)     |
                                       |                            |
    d_d_obj---------------------------------------------------------|
    HOST                               |    DEVICE
    

    您可以访问上图左侧 (HOST)、主机代码或 cudaMemcpy 操作中的任何数量(位置)。您不能在主机代码中访问右侧的任何数量(位置)。

    【讨论】:

    • 感谢@Robert Crovella 的回答。该图非常有帮助。还要感谢您如此礼貌地将我的困惑描述为“不太清晰”,非常感谢您:)
    • 我想我理解我对问题 1 的误解。Obj** h_d_obj = (Obj**)malloc(sizeof(Obj*) * 3); 分配的主机内存足以容纳 3 个指针,并将指向内存的指针返回给 h_d_obj 然后 cudaMalloc((void**)&amp;h_d_obj[i], sizeof(Obj)); 取消引用 h_d_obj[i]获取主机内存中的第 i 个指针(尽管该指针从未设置过),然后使用&amp; 获取此指针的地址,cudaMalloc 返回设备内存中Obj 的地址。所以h_d_obj 用设备指针指向主机内存,虽然h_d_obj 已经改变h_d_obj 没有。
    猜你喜欢
    • 2012-03-09
    • 2020-08-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-03-01
    • 2023-02-09
    • 2016-07-30
    相关资源
    最近更新 更多