从主机

时间:2015-12-02 11:15:45

标签: c++ cuda global-variables

我想知道是否有官方消息来源,为什么以下有效:

#include <iostream>

struct Array{
    int el[10000];
};

__device__ Array devAr;

void test(Array& ar = devAr){
    for(int i=0; i<10000; i++)
        ar.el[i] = i;
    std::cout << ar.el[0] + ar.el[9999] << std::endl;
}

int main(){
    test();
}

如果您尝试直接访问devAr但是通过引用没有这样的警告(有充分理由),您会收到警告“a __device__ variable”devAr“无法在主机函数中直接读取”。但在这两种情况下都可以从主机访问变量。所以看来,该变量有一个主机实例。

我需要知道的是:我可以将此视为理所当然吗?

显示指针值的其他测试用例:

#include <iostream>
#include <cstdio>

__device__ int devAr[2];

__global__ void foo(){
    printf("Device: %p\n", &devAr);
    devAr[0] = 1337;
}

int main()
{
    devAr[0] = 4;
    std::cout << devAr[0] << std::endl;
    void* ad;
    cudaGetSymbolAddress(&ad, devAr);
    std::cout << ad << " " << &devAr << std::endl;
    foo<<<1,1>>>();
    cudaDeviceSynchronize();
    int arHost[2];
    cudaMemcpyFromSymbol(arHost, devAr, sizeof(arHost), 0);
    std::cout << "values: " << arHost[0] << std::endl;
}

输出:

  

4
  0x500bc0000 0x66153c
  设备:0x500bc0000
  值:1337

1 个答案:

答案 0 :(得分:10)

你在做什么是无效的,你应该听警告:

  

__device__变量devAr无法在主机函数中直接读取

首先让我简化您的代码,只显示显示问题所需的大小:

#include <iostream>

__device__ int devAr[1];

int main()
{
    devAr[0] = 4;
    std::cout << devAr[0] << std::endl;
}

现在发生了什么:

  1. __device__ int devAr[1];在设备内存中分配固定大小的数组,并将指针存储在devAr变量中的设备内存中(因此警告)。
  2. devAr地址指向有效的设备内存,但是,即使在主机代码中也可以使用此类地址,因为主机和设备内存使用相同格式的地址。但是,在主机代码中devAr指向一些随机未初始化的主机内存
  3. 根据上述情况,可以说devAr[0] = 4;只是将4写入主机内存中的某个随机未初始化位置。
  4. 尝试运行以下代码,也许它可以帮助您了解幕后发生的事情:

    #include "cuda_runtime.h"
    #include "device_launch_parameters.h"
    #include <iostream>
    
    using namespace std;
    
    __device__ int devAr[1];
    
    __global__ void foo()
    {
        printf("dev: %d \n", devAr[0]);
        devAr[0] = 5;
        printf("dev: %d \n", devAr[0]);
    }
    
    int main()
    {
        cout << "host: " << devAr[0] << endl;
        devAr[0] = 4;
        cout << "host: " << devAr[0] << endl;
    
        foo << <1, 1 >> >();
        cudaDeviceSynchronize();
        cout << "host: " << devAr[0] << endl;
    }
    

    输出将是:

    host: 0
    host: 4
    dev: 0
    dev: 5
    host: 4
    

    <强>更新

    在澄清了你在下面的评论中提出的要求后,我开始深入研究这个问题并发现了几个相关的SO主题,大部分引用来自答案下面的评论,这里是:

    1. cudaMemcpy() vs cudaMemcpyFromSymbol()

        

      任何静态定义的设备符号(__device____constant__,偶数纹理)都会导致工具链发出两个符号,一个在设备模块中,另一个在主机对象中。 CUDA运行时设置并维护这两个符号之间的动态映射。符号API调用是检索__constant____device__符号的此映射的方法。纹理API检索纹理符号等的映射。

    2. Usage of global vs. constant memory in CUDA

        

      *PNT__device__变量,而不是包含设备变量地址的主变量。 (令人困惑,我知道。)因此,如果您尝试在主机上访问它,就像(void**)&PNT一样,您正试图从主机读取一个不允许的设备变量。从主机代码的角度来看,它只是一个符号,因此您需要使用cudaGetSympolAddress()将设备地址存储在主机变量中,然后您可以将其传递给cudaMemcpyToSymbol(),作为@talonmies示出。

    3. CUDA Constant Memory Error

        

      有些令人困惑的是,主机代码中的A和B不是有效的设备内存地址。它们是主机符号,它为运行时设备符号查找提供挂钩。将它们传递给内核是非法的 - 如果你想要它们的设备内存地址,你必须使用cudaGetSymbolAddress在运行时检索它。

    4. cudaMemcpyToSymbol vs. cudaMemcpy why is it still around (cudaMemcpyToSymbol)

        

      通过CUDA API复制到该地址会因参数无效而失败,因为它不是API先前分配的GPU内存空间中的地址。是的,这适用于通用__device__指针和静态声明的设备符号。

    5. cudaMemcpyFromSymbol on a __device__ variable

        

      问题的根源是你不允许在普通主机代码中获取设备变量的地址:...虽然这似乎正确编译,但传递的实际地址是垃圾。要在主机代码中获取设备变量的地址,我们可以使用cudaGetSymbolAddress

    6. 基于这些证据,我试着从上面更新我原来的3步说明:

      1. __device__ int devAr[1];在设备内存中分配固定大小的数组,并将&#34;挂钩存储到运行时设备符号查找中。进入devAr变量的主机版本(参见链接的资源1和3)。
      2. devAr地址从主机的角度来看只是垃圾,只应与符号API调用一起使用,例如cudaGetSymbolAddress(所有链接的资源似乎都支持这个理论)因为它映射到devAr变量的设备版本。
      3. 我无法想出任何东西&#34;更具体的&#34;比如链接到CUDA文档,但我希望现在已经足够清楚了。总而言之,您似乎现在可以保证上述行为(即有devAr变量的主机和设备版本),但对我来说它更像是一个您不应该依赖的实现细节,不应将devAr变量的主机版本用于符号API调用以外的目的。