C++ 具有动态共享内存的模板化CUDA内核

C++ 具有动态共享内存的模板化CUDA内核,c++,cuda,C++,Cuda,我想在一个程序中调用带有动态分配共享内存的模板化CUDA内核的不同实例。我的第一个天真方法是写: template<typename T> __global__ void kernel(T* ptr) { extern __shared__ T smem[]; // calculations here ...

我想在一个程序中调用带有动态分配共享内存的模板化CUDA内核的不同实例。我的第一个天真方法是写:

template<typename T>
__global__ void kernel(T* ptr)
{
  extern __shared__ T smem[];
  // calculations here ...                                                                                                                                          
}

template<typename T>
void call_kernel( T* ptr, const int n )
{
  dim3 dimBlock(n), dimGrid;
  kernel<<<dimGrid, dimBlock, n*sizeof(T)>>>(ptr);
}

int main(int argc, char *argv[])
{
  const int n = 32;
  float *float_ptr;
  double *double_ptr;
  cudaMalloc( (void**)&float_ptr, n*sizeof(float) );
  cudaMalloc( (void**)&double_ptr, n*sizeof(double) );

  call_kernel( float_ptr, n );
  call_kernel( double_ptr, n ); // problem, 2nd instantiation

  cudaFree( (void*)float_ptr );
  cudaFree( (void*)double_ptr );
  return 0;
}
我知道我遇到了名称冲突,因为共享内存被声明为extern。然而,据我所知,如果我想在运行时定义它的大小,就没有办法解决这个问题


因此,我的问题是:有什么优雅的方法来获得所需的行为吗?使用优雅,我的意思是没有代码复制等。

动态分配的共享内存实际上只是一个大小(以字节为单位)和为内核设置的指针。因此,类似这样的方法应该有效:

替换此项:

extern __shared__ T smem[];
为此:

extern __shared__ __align__(sizeof(T)) unsigned char my_smem[];
T *smem = reinterpret_cast<T *>(my_smem);
extern\uuuuu共享\uuuuuu对齐\uuuuuu(sizeof(T))无符号字符my\u smem[];
T*smem=重新解释铸造(我的smem);
您可以在中看到其他重新转换动态分配的共享内存指针的示例,这些指针可以满足其他需求

编辑:更新了我的答案,以反映@njuffa的评论。

(是@RobertCrovella的变体)

NVCC不愿意接受两个名称相同但类型不同的
extern\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu
数组,即使它们从不在彼此的范围内。我们需要满足NVCC的要求,让我们的模板实例对引擎盖下的共享内存使用相同的类型,同时让使用它们的内核代码看到它喜欢的类型

因此,我们将替换此说明:

extern __shared__ T smem[];
关于这一点:

auto smem = shared_memory_proxy<T>();
auto-smem=shared_memory_proxy();
其中:

template <typename T>
__device__ T* shared_memory_proxy()
{
    // do we need an __align__() here? I don't think so...
    extern __shared__ unsigned char memory[];
    return reinterpret_cast<T*>(memory);
}
模板
__设备\uuuut*共享内存\u代理()
{
//我们需要在这里对齐吗?我不这么认为。。。
外部共享无符号字符内存[];
返回重新解释(内存);
}
在某些设备端代码包含文件中

优点:

  • 在使用现场安装一个衬垫
  • 要记住的语法更简单
  • 关注点分离-无论谁读取内核,都不必考虑为什么要看到
    extern
    、对齐说明符或重新解释转换等

编辑:这是作为我的仅标题库的一部分实现的:(在这里它被命名为
共享内存::dynamic::proxy()
)。

保守地说,难道不应该考虑可能的指针对齐问题吗?我通常将
my_smem[]
声明为类型
double2
,以确保16字节对齐,然后将指针强制转换为类型
T
。这就提出了一个问题,即通过传递给threadblock的动态共享内存分配过程创建的指针是否会对齐16字节(或有任何对齐)。在我看来,它很可能会,但因为我不知道它是具体的,我同意你的方式似乎更好。当然,全局内存分配有一个定义的对齐方式,甚至超过了任何向量类型的对齐方式。修改了我的答案。根据CUDA文档,我不知道有任何对齐保证,这就是为什么我总是使用
double2
方法作为保守方法的原因。当然,使用
\uuuuuuu align\uuuuuuu
属性也应该有效,而且可以说更干净。我认为这不起作用,因为对于模板的不同实例化,对齐规范是不同的。另外,我认为这是不必要的,因为(开始)共享内存应该很好地对齐。还有@njuffa,你怎么看?@einpoklum我已经说了我想说的一切:我的方法是将动态共享内存声明为
double2
类型,以强制16字节对齐,然后将
double2
指针转换为程序所需的
T
指针。当你说“我认为这行不通”时,不清楚“这”指的是什么;您需要更具体。可能是CUDA编译器中的疏忽,因为C++中允许这样做(没有<代码>
template <typename T>
__device__ T* shared_memory_proxy()
{
    // do we need an __align__() here? I don't think so...
    extern __shared__ unsigned char memory[];
    return reinterpret_cast<T*>(memory);
}