编辑
这是您编译的一个小程序,您可以自己查看这些错误......
//for printf
#include <stdio.h>
#include <cuda.h>
__inline __host__ void gpuAssert(cudaError_t code, char *file, int line,
bool abort=true)
{
if (code != cudaSuccess)
{
fprintf(stderr,"GPUassert: %s %s %d\n", cudaGetErrorString(code),
file, line);
//if (abort) exit(code);
}
}
#define gpuErrchk(ans) { gpuAssert((ans), __FILE__, __LINE__); }
__global__ void myKernel1(int *dev_idx, int *dev_tID, const int offset)
{
int myElement = threadIdx.x + blockDim.x * blockIdx.x;
//
int temp;
temp = myElement+
offset +
(offset==0)?0:(offset&0x01==0x0)?(offset-1)*(offset>>1):
(offset)*(offset>>1);
dev_idx[myElement+offset] = temp;
dev_tID[myElement+offset] = myElement;
}
__global__ void myKernel2(int *dev_idx, int *dev_tID, const int offset)
{
int myElement = threadIdx.x + blockDim.x * blockIdx.x;
//
int temp;
temp = myElement+offset;
if (offset != 0 && offset&0x01==0x0) temp+= (offset-1)*(offset>>1);
if (offset != 0 && offset&0x01!=0x0) temp+= offset*( offset>>1);
dev_idx[myElement+offset] = temp;
dev_tID[myElement+offset] = myElement;
}
__host__ void PrintMethod1(int *h_idx, int * h_tID, const int offset,
const int total, const int h_set)
{
for (int c=(h_set==0)?0:offset;
c < (h_set==0)?offset:total;
c++)
printf("Element #%d --> idx: %d tID: %d\n",
c,h_idx[c],h_tID[c]);
}
__host__ void PrintMethod2(int *h_idx, int * h_tID, const int offset,
const int total, const int h_set)
{
int loopStart = (h_set==0)?0:offset;
int loopEnd = (h_set==0)?offset:total;
printf("Loop Start: %d, Loop End: %d\n",
loopStart, loopEnd);
for (int c=loopStart; c < loopEnd; c++)
printf("Element #%d --> idx: %d tID: %d\n",
c,h_idx[c],h_tID[c]);
}
//Checks if there is a compatible device
bool IsCompatibleDeviceRunning()
{
int *dummy;
return cudaGetDeviceCount(dummy) != cudaSuccess;
}
int main()
{
//Check for compatible device
if (!IsCompatibleDeviceRunning())
{
printf("ERROR: No compatible CUDA devices found!\n");
exit(1);
}
const int total = 30;
const int offset = total/2;
int * h_tID, * dev_tID, * h_idx, * dev_idx, h_set;
h_tID = (int *) malloc(total*sizeof(int));
h_idx = (int *) malloc(total*sizeof(int));
gpuErrchk(cudaMalloc((void **) &dev_tID,total*sizeof(int)));
gpuErrchk(cudaMalloc((void **) &dev_idx,total*sizeof(int)));
myKernel1<<<1,offset>>>(dev_idx, dev_tID, 0);
//myKernel2<<<1,offset>>>(dev_idx, dev_tID, 0);
gpuErrchk(cudaPeekAtLastError());
gpuErrchk(cudaDeviceSynchronize());
myKernel1<<<1,offset>>>(dev_idx, dev_tID, offset);
//myKernel2<<<1,offset>>>(dev_idx, dev_tID, offset);
gpuErrchk(cudaPeekAtLastError());
gpuErrchk(cudaDeviceSynchronize());
gpuErrchk(cudaMemcpy(h_tID, dev_tID, total*sizeof(int),
cudaMemcpyDeviceToHost));
gpuErrchk(cudaMemcpy(h_idx, dev_idx, total*sizeof(int),
cudaMemcpyDeviceToHost));
h_set = 0;
//PrintMethod1(h_idx, h_tID, offset, total, h_set);
PrintMethod2(h_idx, h_tID, offset, total, h_set);
h_set = 1;
//PrintMethod1(h_idx, h_tID, offset, total, h_set);
PrintMethod2(h_idx, h_tID, offset, total, h_set);
return 0;
}
运行时MyKernel2
,将正确的输出写入数组:
Loop Start: 0, Loop End: 15
Element #0 --> idx: 0 tID: 0
Element #1 --> idx: 1 tID: 1
Element #2 --> idx: 2 tID: 2
Element #3 --> idx: 3 tID: 3
Element #4 --> idx: 4 tID: 4
Element #5 --> idx: 5 tID: 5
Element #6 --> idx: 6 tID: 6
Element #7 --> idx: 7 tID: 7
Element #8 --> idx: 8 tID: 8
Element #9 --> idx: 9 tID: 9
Element #10 --> idx: 10 tID: 10
Element #11 --> idx: 11 tID: 11
Element #12 --> idx: 12 tID: 12
Element #13 --> idx: 13 tID: 13
Element #14 --> idx: 14 tID: 14
Loop Start: 15, Loop End: 30
Element #15 --> idx: 120 tID: 0
Element #16 --> idx: 121 tID: 1
Element #17 --> idx: 122 tID: 2
Element #18 --> idx: 123 tID: 3
Element #19 --> idx: 124 tID: 4
Element #20 --> idx: 125 tID: 5
Element #21 --> idx: 126 tID: 6
Element #22 --> idx: 127 tID: 7
Element #23 --> idx: 128 tID: 8
Element #24 --> idx: 129 tID: 9
Element #25 --> idx: 130 tID: 10
Element #26 --> idx: 131 tID: 11
Element #27 --> idx: 132 tID: 12
Element #28 --> idx: 133 tID: 13
Element #29 --> idx: 134 tID: 14
MyKernel1
运行时,使用相同的基于三元的 idx 分配,所有结果都为零:
Loop Start: 0, Loop End: 15
Element #0 --> idx: 0 tID: 0
Element #1 --> idx: 0 tID: 1
Element #2 --> idx: 0 tID: 2
Element #3 --> idx: 0 tID: 3
Element #4 --> idx: 0 tID: 4
Element #5 --> idx: 0 tID: 5
Element #6 --> idx: 0 tID: 6
Element #7 --> idx: 0 tID: 7
Element #8 --> idx: 0 tID: 8
Element #9 --> idx: 0 tID: 9
Element #10 --> idx: 0 tID: 10
Element #11 --> idx: 0 tID: 11
Element #12 --> idx: 0 tID: 12
Element #13 --> idx: 0 tID: 13
Element #14 --> idx: 0 tID: 14
Loop Start: 15, Loop End: 30
Element #15 --> idx: 0 tID: 0
Element #16 --> idx: 0 tID: 1
Element #17 --> idx: 0 tID: 2
Element #18 --> idx: 0 tID: 3
Element #19 --> idx: 0 tID: 4
Element #20 --> idx: 0 tID: 5
Element #21 --> idx: 0 tID: 6
Element #22 --> idx: 0 tID: 7
Element #23 --> idx: 0 tID: 8
Element #24 --> idx: 0 tID: 9
Element #25 --> idx: 0 tID: 10
Element #26 --> idx: 0 tID: 11
Element #27 --> idx: 0 tID: 12
Element #28 --> idx: 0 tID: 13
Element #29 --> idx: 0 tID: 14
当PrintMethod1
(使用三元边界)运行时,它会出现段错误,基本上陷入无限循环。注意,这是在主机端!!
运行时PrintMethod2
,输出打印正常如上所示。
这是我的编译命令:
nvcc --compiler-options -fno-strict-aliasing -DUNIX -m64 -O2 \
--compiler-bindir /usr/bin/g++ \
-gencode=arch=compute_20,code=\"sm_21,compute_20\" \
-I/usr/local/CUDA_SDK/C/common/inc -I/usr/local/CUDA_SDK/shared/inc \
-o TEST Test.cu
关于我唯一的线索是它抱怨两个内核都有一个不正确的参数,尽管它看起来是正确的并且得到了正确的结果MyKernel2
。
我认为上面的例子几乎是评论者可以根据下面的描述自己尝试的,但它可以节省你编写代码的时间和精力!
让我知道是否还有其他可以发布的内容,以帮助解决这个问题。
原始问题
大多数 C 编译器,由 lang. 标准支持三元运算符。
例如
int myVar;
myVar=(testFlg==true)?-1:1;
然而,令人惊讶的是nvcc
,当它们在内核中使用时,CUDA 似乎剥离了一些三元运算符并用零替换它们......
我通过申请cuPrintf
检查有问题的代码块发现了这一点。例如,假设我有两个内核为它们的输出共享一个全局数组。第一个内核处理第一块元素。第二个内核获得一个偏移量,以指示在数组中跳转多远,以免覆盖第一个内核的元素。偶数和奇数的偏移量不同。
所以我可以写:
if (krnl!=0 && offset&0x01==0x0)
idx+=(offset-1)*(offset>>1);
if (krnl!=0 && offset&0x01!=0x0)
idx+=offset*(offset>>1);
但是编写近乎等效的速记语法会更加紧凑和可读(在我看来)。
idx += (krnl==0)?0:(offset&0x01==0)?
(offset-1)*(offset>>1):
offset*(offset>>1);
后一个代码虽然总是会产生一个零,因为 CUDA 的编译器会剪掉速记条件。
我意识到此功能代码被滥用并导致线程分歧,但在简单的情况下,如果编译器正确处理它,它似乎与标准条件没有任何不同。
这是编译器中的错误还是故意不支持?
有谁知道这个功能是否会出现在 CUDA 中?
我很惊讶地发现这是我寻址失败和段错误的根源......
编辑
这是一个标准的 C 功能,我误读并错误地说它是非标准的。
编辑 2
我曾为编译器说“窒息而死”。“死”绝对是不恰当的术语。相反,nvcc
完成了编译,但显然已经剥离了基于三元运算符的赋值并将其替换为零。后来这会回来并咬我,因为没有将内容写入正确的位置,而这些位置又被用作双索引方案中的索引。这些索引是在 CPU 端的总结期间使用的,因此段错误发生在 CPU 端,但由编译器截图驱动。
我正在使用编译器 v4.1 并已-O2
打开。看来优化器可能正在优化三元运算中使用的变量,这可能是此错误的根源。
容易出错的三元运算与我上面给出的示例几乎相同,但涉及到大量的加法运算。
我计划遵循以下评论者的建议并向 NVIDIA 提交错误报告,但我将这篇文章作为对其他人的警告。
编辑 3
这里有一个稍微净化过的完整声明,它总是产生零:
__global__ void MyFunc
( const int offset
const CustomType * dev_P,
...
const int box)
{
int tidx = blockIdx.x * blockDim.x + threadIdx.x;
int idx=0;
...
idx = tidx +
dev_P->B +
(box == 0)?0:(offset&0x01!=0x0):
(offset-1)*(offset>>1):offset*(offset>>1);
//NOTES:
//I put the cuPrintf here.... from it I could see that tidx was diff. ints (as you
//would expect), but that when added together the sum was always "magically"
//becoming zero. The culprit was the nested ternary operator.
//Once I replaced it with the equivalent conditional, the assignment worked as
//expected.
//"offset" is constant on the level of this kernel, but it is not always 0.
//Outside the kernel "offset" varies greatly over the course of the simulation,
//meaning that each time the kernel is called, it likely has a different value.
//"tidx" obviously varies.
//but somehow the above sum gave 0, likely due to an unreported compiler bug.
//box is either 0 or 1. For a certain type of op in my simulation I call this
//kernel twice, once for box value 0 and a second time for box value 1
...
}