修改
在初始发布的代码段(见下文)中,我没有正确地将struct
发送到device
,这已得到修复,但结果仍然相同。在我的完整代码中,这个错误并不存在。 (在我的初始发布中,该命令有两个错误 - 一个,结构是从HostToDevice
复制的,但实际上是相反的,副本的大小也是错误的。道歉;两个错误都是固定的,但重新编译的代码仍然显示下面描述的零现象,我的完整代码也是如此。)
编辑2
在我对de-proprietar化重写代码的匆忙中,我做了一些dalekchef向我指出的错误(设备的struct
副本是在设备上分配之前执行的,在我的重写代码和设备cudaMalloc
调用没有乘以sizeof(...)
数组元素的类型。我添加了这些修复,重新编译和重新测试,但它没有解决问题。还要双重检查我的原始代码 - 它没有那些错误。再次道歉,因为混乱。
我正在尝试从大型模拟程序中转储统计信息。下面显示了类似的削减代码。两个代码都表现出相同的问题 - 当它们输出平均值时,它们输出零。
#include "stdio.h"
struct __align__(8) DynamicVals
{
double a;
double b;
int n1;
int n2;
int perDump;
};
__device__ int *dev_arrN1, *dev_arrN2;
__device__ double *dev_arrA, *dev_arrB;
__device__ DynamicVals *dev_myVals;
__device__ int stepsA, stepsB;
__device__ double sumA, sumB;
__device__ int stepsN1, stepsN2;
__device__ int sumN1, sumN2;
__global__ void TEST
(int step, double dev_arrA[], double dev_arrB[],
int dev_arrN1[], int dev_arrN2[],DynamicVals *dev_myVals)
{
if (step % dev_myVals->perDump)
{
dev_arrN1[step/dev_myVals->perDump] = 0;
dev_arrN2[step/dev_myVals->perDump] = 0;
dev_arrA[step/dev_myVals->perDump] = 0.0;
dev_arrB[step/dev_myVals->perDump] = 0.0;
stepsA = 0;
stepsB = 0;
stepsN1 = 0;
stepsN2 = 0;
sumA = 0.0;
sumB = 0.0;
sumN1 = 0;
sumN2 = 0;
}
sumA += dev_myVals->a;
sumB += dev_myVals->b;
sumN1 += dev_myVals->n1;
sumN2 += dev_myVals->n2;
stepsA++;
stepsB++;
stepsN1++;
stepsN2++;
if ( sumA > 100000000 )
{
dev_arrA[step/dev_myVals->perDump] +=
sumA / stepsA;
sumA = 0.0;
stepsA = 0;
}
if ( sumB > 100000000 )
{
dev_arrB[step/dev_myVals->perDump] +=
sumB / stepsB;
sumB = 0.0;
stepsB = 0;
}
if ( sumN1 > 1000000 )
{
dev_arrN1[step/dev_myVals->perDump] +=
sumN1 / stepsN1;
sumN1 = 0;
stepsN1 = 0;
}
if ( sumN2 > 1000000 )
{
dev_arrN2[step/dev_myVals->perDump] +=
sumN2 / stepsN2;
sumN2 = 0;
stepsN2 = 0;
}
if ((step+1) % dev_myVals->perDump)
{
dev_arrA[step/dev_myVals->perDump] +=
sumA / stepsA;
dev_arrB[step/dev_myVals->perDump] +=
sumB / stepsB;
dev_arrN1[step/dev_myVals->perDump] +=
sumN1 / stepsN1;
dev_arrN2[step/dev_myVals->perDump] +=
sumN2 / stepsN2;
}
}
int main()
{
const int TOTAL_STEPS = 10000000;
DynamicVals vals;
int *arrN1, *arrN2;
double *arrA, *arrB;
int statCnt;
vals.perDump = TOTAL_STEPS/10;
statCnt = TOTAL_STEPS/vals.perDump+1;
vals.a = 30000.0;
vals.b = 60000.0;
vals.n1 = 10000;
vals.n2 = 20000;
cudaMalloc( (void**)&dev_arrA, statCnt*sizeof(double) );
cudaMalloc( (void**)&dev_arrB, statCnt*sizeof(double) );
cudaMalloc( (void**)&dev_arrN1, statCnt*sizeof(int) );
cudaMalloc( (void**)&dev_arrN2, statCnt*sizeof(int) );
cudaMalloc( (void**)&dev_myVals, sizeof(DynamicVals));
cudaMemcpy(dev_myVals, &vals, sizeof(DynamicVals),
cudaMemcpyHostToDevice);
arrA = (double *)malloc(statCnt * sizeof(double));
arrB = (double *)malloc(statCnt * sizeof(double));
arrN1 = (int *)malloc(statCnt * sizeof(int));
arrN2 = (int *)malloc(statCnt * sizeof(int));
for (int i=0; i< TOTAL_STEPS; i++)
TEST<<<1,1>>>(i, dev_arrA,dev_arrB,dev_arrN1,dev_arrN2,dev_myVals);
cudaMemcpy(arrA,dev_arrA,statCnt * sizeof(double),cudaMemcpyDeviceToHost);
cudaMemcpy(arrB,dev_arrB,statCnt * sizeof(double),cudaMemcpyDeviceToHost);
cudaMemcpy(arrN1,dev_arrN1,statCnt * sizeof(int),cudaMemcpyDeviceToHost);
cudaMemcpy(arrN2,dev_arrN2,statCnt * sizeof(int),cudaMemcpyDeviceToHost);
for (int i=0; i< statCnt; i++)
{
printf("Step: %d ; A=%g B=%g N1=%d N2=%d\n",
i*vals.perDump,
arrA[i], arrB[i], arrN1[i], arrN2[i]);
}
}
输出:
Step: 0 ; A=0 B=0 N1=0 N2=0
Step: 1000000 ; A=0 B=0 N1=0 N2=0
Step: 2000000 ; A=0 B=0 N1=0 N2=0
Step: 3000000 ; A=0 B=0 N1=0 N2=0
Step: 4000000 ; A=0 B=0 N1=0 N2=0
Step: 5000000 ; A=0 B=0 N1=0 N2=0
Step: 6000000 ; A=0 B=0 N1=0 N2=0
Step: 7000000 ; A=0 B=0 N1=0 N2=0
Step: 8000000 ; A=0 B=0 N1=0 N2=0
Step: 9000000 ; A=0 B=0 N1=0 N2=0
Step: 10000000 ; A=0 B=0 N1=0 N2=0
现在,如果我使用一小段时间进行转储,或者我的#s较小,我可以直接使用
...算法,但我使用临时总和,否则我的int
会溢出(double
不会溢出,但我担心它会失去精度)。
如果我将上述直接算法用于较小的值,我得到正确的非零值,但第二个我使用中间词(例如stepsA
,sumA
等),这些值变为零。
我知道我在这里做些傻事......我错过了什么?
备注:
答:是的,我知道上述形式的代码并不是平行的,并且本身并不保证并行化。它是收集更长代码的小型统计信息的一部分。在该代码中,它被包含在线程索引特定的条件逻辑中以防止冲突(使其并行)并且用作数据收集到模拟程序(其保证并行化)。希望您能够理解上述代码的来源,并避免对其缺乏线程安全性的讽刺评论。 (这个免责声明是根据过去的经验而添加的,这些经验是那些不理解我发布摘录的人的非生产性评论,而不是完整的代码,尽管我用不那么明确的术语写作。)
B.)是的,我知道变量的名称含糊不清。这就是重点。我正在处理的代码是专有的,但它最终将是开源的。我只是写这个,因为我过去发布了类似的匿名代码,并收到了关于我的命名惯例的粗鲁评论。
C.)是的,我已经多次阅读CUDA manual,虽然我确实犯了错误但我承认有一些我不理解的功能。我这里没有使用共享内存,但我在完整代码中使用共享内存(OF COURSE)。
D.)是的,上面的代码确实代表了与我的非工作代码的数据转储部分完全相同的特性,删除了与此特定问题无关的逻辑,并且具有线程安全条件。变量名称已更改,但在算法上它应该不变,并且通过完全相同的非工作输出(零)验证。
E.)我确实意识到上述代码段中的“动态”struct
具有非动态值。我将结构命名为,因为在完整代码中,此struct
包含模拟数据,并且是动态的。精简代码中的静态性质不应该使统计信息收集代码失败,它只是意味着每个转储的平均值应该是常量(并且非零)。
答案 0 :(得分:1)
有几件事:
在你为它调用cudaMalloc之前,似乎你正在为dev_MyVals调用cudaMemcpy。这不是它应该如何。
另外:当你进行cudaMalloc调用时,你不会乘以sizeof int。
您应该检查所有CUDA调用cudaMalloc / cudaMemcpy是否有错误代码。它们都应该返回错误或CUDA_SUCCESS。我相信CUDA的例子都显示了如何做到这一点。
另外,为了将来参考,永远不要在CUDA中使用模运算符,这是非常慢的。对于一些替代方案,只需谷歌“Modulo CUDA”。
让我知道它是怎么回事,这可能需要几次迭代来解决。
答案 1 :(得分:0)
我在这里看到的最大问题是范围之一。编写此代码的方式使我得出结论,您可能不了解C ++中的变量作用域通常如何工作,以及设备和主机代码作用域如何在CUDA中工作。几点意见:
当您在代码中执行此类操作时:
__device__
double *dev_arrA, *dev_arrB;
__global__
void TEST(int step, double dev_arrA[], double dev_arrB[], ....)
你有一个可变范围问题。在编译单元范围和功能范围内声明__device__
。这两个声明不引用同一个变量 - 函数单元范围声明(在内核中)优先于内核中的编译单元范围声明。您修改该变量,您正在修改内核范围声明,而不是__global__
变量。这可能会导致各种微妙和无意义的行为。最好避免永远在多个范围内声明相同的变量。
使用dev_arrA
说明符声明变量时,它应仅设备上下文符号,并且只能直接在设备代码中使用。所以像这样:
__device__
__device__
是非法的。您无法直接在变量上调用
之类的API函数。即使它会编译(因为主机和设备代码的CUDA编译tradjectories中涉及hackery),这样做是不正确的。在上面的示例中,__device__
double *dev_arrA;
int main()
{
....
cudaMalloc( (void**)&dev_arrA, statCnt*sizeof(double) );
....
}__device__
是设备符号。您可以通过API符号操作调用与它进行交互,但这在技术上是合法的。在您的代码中,用于保存设备指针并作为内核参数传递的变量(如cudaMalloc
)应在__device__
范围内声明,并通过值传递给内核。
这是上述两件事的组合,可能会导致您的问题。
但困难在于你选择发布粗糙的150行代码(其中很多代码是多余的)作为repro案例。我怀疑是否有人关心你的问题,用精细的牙齿梳理那么多代码并找出精确问题的位置。此外,你习惯在你的问题中做这些令人讨厌的“顶级编辑”很快就会把合理写好的起点变成难以理解的伪造的变更日志,这些日志非常难以理解并且不太可能对任何人有所帮助。此外,温和的被动攻击性笔记部分没有任何实际意义 - 它没有增加任何有价值的问题。
因此,我将为您发布一个大大简化的代码版本,我认为这些代码包含您尝试工作的所有基本内容。我把它留作“读者的练习”,把它变回你想做的任何事情。
dev_arrA