我想知道是否有官方消息来源,为什么以下有效:
#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
答案 0 :(得分:10)
你在做什么是无效的,你应该听警告:
__device__
变量devAr
无法在主机函数中直接读取
首先让我简化您的代码,只显示显示问题所需的大小:
#include <iostream>
__device__ int devAr[1];
int main()
{
devAr[0] = 4;
std::cout << devAr[0] << std::endl;
}
现在发生了什么:
__device__ int devAr[1];
在设备内存中分配固定大小的数组,并将指针存储在devAr
变量中的设备内存中(因此警告)。devAr
地址指向有效的设备内存,但是,即使在主机代码中也可以使用此类地址,因为主机和设备内存使用相同格式的地址。但是,在主机代码中devAr
指向一些随机未初始化的主机内存。devAr[0] = 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主题,大部分引用来自答案下面的评论,这里是:
cudaMemcpy() vs cudaMemcpyFromSymbol():
任何静态定义的设备符号(
__device__
,__constant__
,偶数纹理)都会导致工具链发出两个符号,一个在设备模块中,另一个在主机对象中。 CUDA运行时设置并维护这两个符号之间的动态映射。符号API调用是检索__constant__
和__device__
符号的此映射的方法。纹理API检索纹理符号等的映射。
Usage of global vs. constant memory in CUDA:
*PNT
是__device__
变量,而不是包含设备变量地址的主变量。 (令人困惑,我知道。)因此,如果您尝试在主机上访问它,就像(void**)&PNT
一样,您正试图从主机读取一个不允许的设备变量。从主机代码的角度来看,它只是一个符号,因此您需要使用cudaGetSympolAddress()
将设备地址存储在主机变量中,然后您可以将其传递给cudaMemcpyToSymbol()
,作为@talonmies示出。
有些令人困惑的是,主机代码中的A和B不是有效的设备内存地址。它们是主机符号,它为运行时设备符号查找提供挂钩。将它们传递给内核是非法的 - 如果你想要它们的设备内存地址,你必须使用
cudaGetSymbolAddress
在运行时检索它。
cudaMemcpyToSymbol vs. cudaMemcpy why is it still around (cudaMemcpyToSymbol):
通过CUDA API复制到该地址会因参数无效而失败,因为它不是API先前分配的GPU内存空间中的地址。是的,这适用于通用
__device__
指针和静态声明的设备符号。
cudaMemcpyFromSymbol on a __device__
variable:
问题的根源是你不允许在普通主机代码中获取设备变量的地址:...虽然这似乎正确编译,但传递的实际地址是垃圾。要在主机代码中获取设备变量的地址,我们可以使用
cudaGetSymbolAddress
基于这些证据,我试着从上面更新我原来的3步说明:
__device__ int devAr[1];
在设备内存中分配固定大小的数组,并将&#34;挂钩存储到运行时设备符号查找中。进入devAr
变量的主机版本(参见链接的资源1和3)。devAr
地址从主机的角度来看只是垃圾,只应与符号API调用一起使用,例如cudaGetSymbolAddress
(所有链接的资源似乎都支持这个理论)因为它映射到devAr
变量的设备版本。我无法想出任何东西&#34;更具体的&#34;比如链接到CUDA文档,但我希望现在已经足够清楚了。总而言之,您似乎现在可以保证上述行为(即有devAr
变量的主机和设备版本),但对我来说它更像是一个您不应该依赖的实现细节,不应将devAr
变量的主机版本用于符号API调用以外的目的。