我有以下最小的非工作示例:
#include <cstdio>
#include <cuda_runtime_api.h>
/* this declaration would normally be in a header, but it doesn't matter */
template<class T_PREC> __global__ void testKernel( T_PREC );
template<class T_PREC> __global__ void testKernel( T_PREC const x )
{
printf( "%f", x );
}
int main()
{
printf("calling kernel...");
testKernel<<<1,1>>>( 3.0f );
cudaDeviceSynchronize();
printf("OK\n");
return 0;
}
我用
编译和运行nvcc simple.cu && ./a.out
输出结果为:
calling kernel...
意味着程序崩溃之前它既不能打印“OK”也不能打印浮点数。这不是分段错误,所以我不能回溯任何东西。我正在使用CUDA 7.0.27。在gdb
中运行时,消息为:
[Inferior 1 (process 27899) exited with code 01]
上面的例子有四件事可以解决:
不要使用CUDA:
template<class T_PREC> void testKernel( T_PREC );
template<class T_PREC> void testKernel( T_PREC const x )
{
printf( "%f", x );
}
int main()
{
printf("calling kernel...");
testKernel( 3.0f );
cudaDeviceSynchronize();
printf("OK\n");
return 0;
}
不要使用模板:
__global__ void testKernel( float );
__global__ void testKernel( float const x )
{
printf( "%f", x );
}
省略声明(如果我需要从库中获取声明,则不是选项)
//template<class T_PREC> void testKernel( T_PREC );
不要在声明中省略const
限定符:
template<class T_PREC> __global__ void testKernel( T_PREC const );
这是最合理的选择,但我不明白我为什么要这样做。在普通的C ++中,按值调用的const
限定符不应更改函数签名。即使它确实如此,它也不应该链接,只会在执行时崩溃。那么为什么CUDA表现不同,为什么只有模板?
额外考试:
由于汇编代码本身对我来说太难了,我查看了创建的可执行文件:
nvcc sameTypeQualifier/main.cu -o same.o
[no warning output whatsoever]
nvcc diffTypeQualifier/main.cu -o diff.o
diffTypeQualifier/main.cu: In instantiation of ‘void __wrapper__device_stub_testKernel(T_PREC* const&) [with T_PREC = float]’:
diffTypeQualifier/main.cu:8:45: required from ‘void testKernel(T_PREC*) [with T_PREC = float]’
diffTypeQualifier/main.cu:15:67: required from here
diffTypeQualifier/main.cu:7:86: warning: unused parameter ‘x’ [-Wunused-parameter]
template<class T_PREC> __global__ void testKernel( T_PREC * const x )
^
diff <(nm -C same.o | sed 's/^[0-9a-f]*//') <(nm -C diff.o | sed 's/^[0-9a-f]*//')
389a390
> t void __wrapper__device_stub_testKernel<float>(float*&)
419c420
< t __sti____cudaRegisterAll_39_tmpxft_000050c8_00000000_9_main_cpp1_ii_main()
---
> t __sti____cudaRegisterAll_39_tmpxft_0000511c_00000000_9_main_cpp1_ii_main()
cudaRegisterAll中的十六进制数字甚至在相同源代码的两个编译之间有所不同,因此可以忽略它。由于某种原因,非工作示例具有额外的包装函数。
答案 0 :(得分:1)
恕我直言,你只是误导了编译器,导致未定义的行为。实际上(我希望有更深入的C ++知识的人会发表评论)你告诉编译器会有一个函数接受带有声明的int ,然后让编译器生成一个接受一个函数的函数常数int。从我的观点来看,编译器应该告诉你 - 不,不,这里存在歧义,我有一个未解决的符号并且失败。
为什么呢?好吧,一种可能的情况是由于一些奇怪的优化,因为定义采用一个不会被修改的常量int,因此不需要存储它可以减少到编译时常量。另一方面,为调用而生成的代码假定它必须通过它。我不确定它是否如此(理解的最好方法是反汇编这个例子)但我认为这样的例子足以怀疑示例本身的正确性。
为什么不像其他任何情况那样保持声明和定义相同?
答案 1 :(得分:0)
在比较中间文件时,可以发现一些有趣的东西:
nvcc --keep [...]
colordiff -r c/ nc/
[...]
diff c/main.cu.cpp.ii nc/main.cu.cpp.ii
32767c32767
< template< class T_PREC> static void __wrapper__device_stub_testKernel(const T_PREC &); template< class T_PREC> void testKernel(const T_PREC);
---
> template< class T_PREC> static void __wrapper__device_stub_testKernel(T_PREC &); template< class T_PREC> void testKernel(T_PREC);
[...]
diff c/main.cudafe1.cpp nc/main.cudafe1.cpp
70764c70764
< template< class T_PREC> static void __wrapper__device_stub_testKernel(const T_PREC &); template< class T_PREC> void testKernel(const T_PREC);
---
> template< class T_PREC> static void __wrapper__device_stub_testKernel(T_PREC &); template< class T_PREC> void testKernel(T_PREC);
[...]
我从差异中剥离了线条,这些线条只有(const float)
而不是(float)
在我看来,在创建模板化声明的中间包装函数时,nvcc
中存在一个错误。因为类型是复制粘贴并更改为逐个引用调用,所以内核本身可能是相同的,但不是包装器调用,因为如果它是一个const调用引用或非const调用,它会有所不同 - 引用。此外,在我看来,这是一个错误,仅仅是声明首先创建了一个包装调用。
这是一个C ++示例,演示了发生的问题:
#include<cstdio>
void f( float const & x ) { printf( "float const &\n", x ); }
void f( float & x ) { printf( "float &\n", x ); }
int main( void )
{
f( 3.0 );
float x = 3.0;
f( x );
}
该计划的输出是:
float const &
float &
当greper for wrapper函数时,我们发现如何定义和调用重载函数:
grep -C20 '__wrapper__device_stub_testKernel' nc/main.cu.cpp.ii
和输出:
# 4 "main.cu"
template< class T_PREC> static void __wrapper__device_stub_testKernel(T_PREC &);
template< class T_PREC> void testKernel(T_PREC);
# 5 "main.cu"
template< class T_PREC> static void __wrapper__device_stub_testKernel(const T_PREC &x)
{
exit(1);
}
# 5 "main.cu"
template< class T_PREC> void testKernel(const T_PREC x)
{
# 6 "main.cu"
__wrapper__device_stub_testKernel<T_PREC>(x);
# 8 "main.cu"
return;
}
# 10 "main.cu"
int main()
# 11 "main.cu"
{
# 12 "main.cu"
printf("calling kernel...");
# 13 "main.cu"
(cudaConfigureCall(1, 1)) ? (void)0 : (testKernel)((3.0F));
# 14 "main.cu"
cudaDeviceSynchronize();
# 15 "main.cu"
printf("OK\n");
# 16 "main.cu"
return 0;
# 17 "main.cu"
}
[...]
static void __device_stub__Z10testKernelIfEvT_(float __par0)
{
if (cudaSetupArgument((void *)(char *)&__par0, sizeof(__par0), (size_t)0UL) != cudaSuccess)
return;
{
volatile static char *__f __attribute__((unused));
__f = ((char *)( (void ( *)(float))testKernel<float> ) );
(void)cudaLaunch( ((char *)((void ( *)(float))testKernel<float> )) );
};
}
[...]
template<> void __wrapper__device_stub_testKernel<float>( float &__cuda_0)
{
__device_stub__Z10testKernelIfEvT_( __cuda_0);
}
(注意:我添加了一些缩进和换行符以提高可读性)
因此,当非const const-by-reference函数调用可能的内核时,const call-by-reference重载函数调用exit(1)
。
由于某些原因,除了将const copy-by-value转换为const call-by-reference的错误之外,似乎nvcc
混淆了原始文件中的“two”内核。非const声明被转换为一个调用__device_stub__Z10testKernelIfEvT_
的包装函数,而带有const copy-by-value参数的函数定义被转换为一个调用exit(1)
的包装器。
不幸的是,我不是CUDA开发人员所以我不能提交错误报告,但也许我说服任何人为我做这件事。或者也许一些反馈意见证明我的答案是错误的。