CUDA中关于C++特性的限制

CUDA官方文档中对C++语言的支持和限制,懒得每次看英文文档,自己尝试翻译一下(没有放lambda表达式的相关内容,太过于复杂,我选择不用)。官方文档https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#c-cplusplus-language-support

__CUDA_ARCH__

在如下3种情况下,开发者不应该依赖于__CUDA_ARCH__宏是否定义或__CUDA_ARCH__的具体值来确定代码:

typedef

typedef用于下列几种情况时:

  • __global__函数及其函数模版
  • __device____constant__变量
  • 纹理和surfaces

如下所示:

#if !defined(__CUDA_ARCH__)
typedef int mytype;
#else
typedef double mytype;
#endif __device__ mytype xxx; // error: xxx's type depends on __CUDA_ARCH__
__global__ void foo(mytype in, // error: foo's type depends on __CUDA_ARCH__
mytype *ptr) {
*ptr = in;
}

函数模版实例化

__global__函数实例化:

__device__ int result;
template <typename T>
__global__ void kern(T in) {
result = in;
} __host__ __device__ void foo(void) {
#if !defined(__CUDA_ARCH__)
kern<<<1,1>>>(1); // error: "kern<int>" 的实例化必须依赖于__CUDA_ARCH__未定义
}

单独编译

extern的函数或变量定义:

#if !defined(__CUDA_ARCH__)
void foo(void) {/*...*/ } // error: foo函数实现依赖于__CUDA_ARCH__未定义
#endif

特别是若头文件中定义的函数实现依赖于__CUDA_ARCH__,而用到该函数的多个对象在不同arch下编译会导致该函数的行为不一致,从而引起函数冲突。

CUDA还规定,若设备端代码调用了一个被声明为extern的函数,则该函数和设备端代码必须在同一arch下编译。

空间描述符

内存空间描述符

__device____shared____managed____constant__内存空间描述符不可用于如下情况:

  • classstructunion数据成员
  • 形式参数
  • 在主机端执行的函数中不可声明非extern变量
  • 在设备端执行的函数中,除__shared__外另外三个描述符描述的变量不可声明为既非extern亦非static

此外,若有此四类描述符的变量的类型为自定义类(classstruct),则该类必须拥有空构造函数和空析构函数。

关于C++类在什么情况下会拥有空构造函数和空析构函数可参考《深度探索C++对象模型》

在全编译模式下,此四类描述符变量不能被定义为extern变量,无论主机端设备端(当然,动态共享内存声明不包含在内),但在单独编译模式下可以。

__shared__变量不能将初始化作为其声明的一部分。

托管变量

__managed__托管内存变量具有如下限制:

  • 托管变量的地址非常量
  • 托管变量不应有const限定
  • 托管变量不应为引用类型
  • 当CUDA运行时可能不处于有效状态时,不得使用托管变量的地址或值:
    • 在对象的静态/动态初始化或析构过程中,而该对象属于静态或本地作用域
    • 在exit后被调用的代码中(如gcc的__attribute__((destructor))属性)
    • 在CUDA运行时被初始化前的代码中(如gcc的__attribute__((constructor))属性)
  • 托管变量不可作为decltype()表达式的unparenthesized参数
  • 托管变量和动态分配的托管内存一样具有连贯性和一致性行为(coherence and consistency)
  • 当含托管变量的CUDA程序运行在多GPU设备中时,该托管变量只会被分配一次,而不是每个GPU分配一个
  • 在主机端执行的函数中不可使用非外部链接(extern)的托管变量
  • 在设备端执行的函数中不可使用非外部链接(extern)或静态链接(static)的托管变量

示例:

__device__ __managed__ int xxx = 10;         // OK
struct S1_t {
int field;
S1_t(void) : field(xxx) { };
};
S1_t temp1; // error: use of managed variable
// (xxx) in dynamic initialization
struct S2_t {
~S2_t(void) { xxx = 10; }
};
S2_t temp2; // error: use of managed variable
// (xxx) in the destructor of
// object with static storage
// duration
__device__ __managed__ const int yyy = 10; // error: const qualified type
__device__ __managed__ int &zzz = xxx; // error: reference type
int *ptr = &xxx; // error: use of managed variable
// (xxx) in static initialization
template <int *addr> struct S3_t { };
S3_t<&xxx> temp; // error: address of managed
// variable(xxx) not a
// constant expression
__global__ void kern(int *ptr) {
assert(ptr == &xxx); // OK
xxx = 20; // OK
}
int main(void) {
int *ptr = &xxx; // OK
kern<<<1,1>>>(ptr);
cudaDeviceSynchronize();
xxx++; // OK
decltype(xxx) qqq; // error: managed variable(xxx) used
// as unparenthized argument to
// decltype
decltype((xxx)) zzz = yyy; // OK
}

const常量

__device__, __constant____shared__变量不允许被声明为constexpr的。

若常量V是一个被constconstexpr 限定符修饰的变量或类的静态变量,且V没有被内存空间描述符(__device____shared____constant__)修饰,则常量V是一个主机端常量。但常量V仍然可以被设备端代码直接访问,只要V满足如下条件:

  • V在使用点前用一个常量表达式初始化
  • V没有被volatile限定符修饰
  • V是一个内置整型(int)或内置浮点型(float),但constexprconst宽松,只要是非long double的标量即可。

对于constexpr常量V,若函数F是一个__device__ constexpr__host__ __device__ constexpr函数,且该函数被常量表达式调用,则即使常量V是一个非标量类型,也可以被函数F直接使用。

显然,设备端代码不能引用V或取V的地址。

默认情况下,一个constexpr函数不能被执行空间不兼容的另一个函数调用,但可以通过nvcc选项--expt-relaxed-constexpr移除这个限制,从而可以在主机端调用__device__ constexpr函数,反之亦然,也可以在设备端调用__host__ constexpr函数。开发者可以通过__CUDACC_RELAXED_CONSTEXPR__宏是否定义来判断编译器是否开启这个选项。

需要注意即使模版函数被constexpr关键字标记,但该模版函数的实例化函数不一定就是constexpr函数。

示例:

const int xxx = 10;
struct S1_t { static const int yyy = 20; }; constexpr int host_arr[] = { 1, 2, 3};
constexpr __device__ int get(int idx) { return host_arr[idx]; } extern const int zzz;
const float www = 5.0;
__device__ void foo(void) {
int local1[xxx]; // OK
int local2[S1_t::yyy]; // OK int val1 = xxx; // OK
const float val5 = www; // OK
int val2 = S1_t::yyy; // OK int val3 = zzz; // error: zzz在使用点前没有被常量表达式初始化
const int &val3 = xxx; // error: __device__不能引用一个主机端常量
const int *val4 = &xxx; // error: __device__不能取一个主机端常量的地址 int v1 = xxx + 4 + S1_t::yyy; // OK
v1 += get(2); // OK
v1 += get(idx); // get(idx)不是一个常量表达式
v1 += host_arr[2]; // host_arr[2]不是一个标量
}
const int zzz = 20; // error: 注意zzz是在使用点后被初始化的

函数和类

__global__ 函数

__global__ 函数传参是通过常量内存传入设备端的,且规定参数大小不得大于4KB。此外__global__ 函数不支持可变长参数。另外,开发者不能将一个操作符函数(如operator+、operator-等等)声明为__global__的,目前__global__函数尚不支持递归,不支持作为类的静态成员函数,支持类的友元声明但不支持在友元声明同时进行定义,例如:

class S1_t {
friend __global__
void foo1(void); // OK: 友元声明但未定义
template<typename T>
friend __global__
void foo2(void); // OK: __global__函数模版也是一样 friend __global__
void foo3(void) { } // error: 友元声明的同时进行定义 template<typename T>
friend __global__
void foo4(void) { } // error: __global__函数模版也是一样
};

我们可以取到函数的函数指针,但主机端代码获取到的__global__函数指针不可用于设备端代码,反之亦然。显然,主机端代码不能获取device函数的函数指针,设备端代码也不能获取__host__函数的函数指针。类似的,不允许在设备端调用创建于主机端的对象的虚函数,反之亦然。

__global__函数或模版不能被声明为constexpr的,其参数不能为std::initializer_listva_list类型,不能为右值引用类型。

不允许将带有虚函数的类的对象作为参数传递给__global__函数,同样的,不允许将虚继承的派生类的对象作为参数传递给__global__函数。

不能在__global__函数实例化或__device____constant__变量实例化的类型模板参数、无类型模板参数、模板模板参数中使用如下类型或模版:

  • 定义为__host____host__ __device__的类型或模版
  • 类型或模版是某个类的privateprotected成员且该类的父类(若有)没有定义在__device____global__函数中
  • 匿名类型
  • 上述任何类型的复合

例:

template <typename T>
__global__ void myKernel(void) { } //__global__模版函数
class myClass {
private:
struct inner_t { };
public:
static void launch(void) {
myKernel<inner_t><<<1,1>>>(); // error: inner_t类是private成员
}
}; template <typename T> __device__ T d1; //__device__模版变量
template <typename T1, typename T2> __device__ T1 d2;
void fn() {
struct S1_t { };
d1<S1_t> = {}; // error: S1_t是__host__的 auto lam1 = [] { };
d2<int, decltype(lam1)> = 10; // error: lam1是一个匿名类型
}

__global__支持可变参数模版,但只允许一个pack参数,且该pack参数必须置于模版参数最后。

static修饰符

CUDA尚不支持类的static静态数据成员,除非同时被static const限定符修饰。而在__device____global__执行空间的函数中,仅允许普通变量(无任何内存空间描述符)和__shared__变量使用static修饰符,而在__device__ __host__函数中则只允许普通变量使用static修饰符。此外,需要注意static变量类型为自定义类class,则该类必须拥有空构造函数和空析构函数。此外,static变量不允许动态初始化,例如:

struct S1_t {
int x;
};
struct S2_t {
int x;
__device__ S2_t(void) { x = 10; } //非空构造函数
};
struct S3_t {
int x;
__device__ S3_t(int p) : x(p) { } //非空构造函数
};
__device__ void f1() {
static int i1; // OK
static S1_t i3; // OK,空构造函数 static int i2 = 11; // OK,静态初始化
static S1_t i4 = {22}; // OK,静态初始化 static __shared__ int i5; // OK,__device__函数__shared__变量可用static修饰 int x = 33;
static int i6 = x; // error: 动态初始化
static S1_t i7 = {x}; // error: 动态初始化 static S2_t i8; // error: 非空构造函数
static S3_t i9(44); // error: 非空构造函数
}

函数的执行空间

若函数F在首次声明时被显式或隐式声明为默认函数(如果首次显式声明为默认函数时有指定执行空间,指定的执行空间会被忽略,但如果不是在首次声明时显式默认,则执行空间为指定的执行空间,下述规则忽略),则函数F的执行空间描述符(__host____device__)为所有调用函数F的函数的执行空间描述符的集合(__global__也视为__device__)。例如:

class Base {
int x;
public:
__host__ __device__ Base(void) : x(10) {}
}; class Derived : public Base { //隐式声明了一个默认构造函数
int y;
};
class Other: public Base { //隐式声明了一个默认构造函数
int z;
};
__device__ void foo(void) {
Derived D1; //Derived的默认构造函数仅被foo函数调用,因此
//Derived::Derived()的执行空间为__device__
Other D2;
}
__host__ void bar(void) {
Other D3; //Other的默认构造函数被foo和bar调用,因此
//Other::Other()的执行空间为__host__ __device__
}

需要注意,当派生类重写基类的虚函数时,必须保证执行空间与基类虚函数的执行空间一致。但若函数FD为虚析构函数,且D没有隐式定义或在非首次声明的声明中显式默认,则F覆盖的每个虚析构函数D的执行空间的集合既为F的执行空间。例如:

struct Base1 { virtual __host__ __device__ ~Base1() {/*...*/} }; //~Base1()显式定义非默认
struct Derived1 : Base1 { }; // ~Derived1()的执行空间为 __host__ __device__ struct Base2 { virtual __device__ ~Base2(); }; //首次声明非显式默认
__device__ Base2::~Base2() = default; //非首次声明的声明中显示默认
struct Derived2 : Base2 { }; // ~Derived2()的执行空间为__device__

命名空间范围内匿名union的成员变量不能被__global____device__函数引用。

封闭类成员函数的执行空间与定义该封闭类的的最内层的那个指定了执行空间的函数的执行空间相同,若所有嵌套函数都没有指定执行空间,或该类不是定义在函数中,则其成员函数的执行空间为__host__

C++特性

CUDA不允许对内置变量(gridDimblockIdxblockDimthreadIdxwarpSize)赋值。

CUDA在设备端同样支持volatile限定符。

默认情况下,std::movestd::forward函数的执行空间为__host__ __device__,因此开发者同样可以在设备端调用这两个函数。

CUDA新增__int128__Complex__float128类型,但这些类型只能在主机端使用,且__float128类型只支持64位Linux平台,同时注意编译器可能会以精度较低的浮点数类型处理__float128类型的常量表达式。

CUDA设备端代码不支持long double类型,不支持thread_local限定符。

CUDA还支持gcc等编译器的deprecated 属性,nvcc选项-Wno-deprecated-declarations将禁用所有弃用警告,而-Werror=deprecated-declarations选项会将所有弃用警告转换为error。

C++ RTTI(运行时类型识别)特性(typeid 运算符、std::type_infodynamic_cast运算符)仅支持主机端代码,不支持设备端代码。类似的,C++异常仅支持主机端代码,不支持设备端代码(包括__global__函数)。

CUDA目前尚不支持设备端的STL。

默认情况下,std::initializer_list的成员函数默认为__host__ __device__执行空间,因此开发者可以在主机端和设备端都能调用std::initializer_list的成员函数,示例:

#include <initializer_list>

__device__ int foo(std::initializer_list<int> in);
__device__ void bar(void) {
foo({4,5,6}); // (a) OK int i = 4;
foo({i,5,6}); // (b) OK
}

CUDA中关于C++特性的限制的更多相关文章

  1. CUDA中并行规约(Parallel Reduction)的优化

    转自: http://hackecho.com/2013/04/cuda-parallel-reduction/ Parallel Reduction是NVIDIA-CUDA自带的例子,也几乎是所有C ...

  2. CUDA 中的计时方法

    问题描述:一般利用CUDA进行加速处理时,都需要测试CUDA程序的运行时间,来对比得到的加速效果. 解决方法: 1).GPU端计时,即设备端计时. 2).CPU端计时,即主机端计时. 设备端计时有两种 ...

  3. Java语言中的面向对象特性总结

    Java语言中的面向对象特性 (总结得不错) [课前思考]  1. 什么是对象?什么是类?什么是包?什么是接口?什么是内部类?  2. 面向对象编程的特性有哪三个?它们各自又有哪些特性?  3. 你知 ...

  4. ASP.NET 5与MVC 6中的新特性

    差点忘了提一句,MVC 6中默认的渲染引擎Razor也将得到更新,以支持C# 6中的新语法.而Razor中的新特性还不只这一点. 在某些情况下,直接在Web页面中嵌入某些JSON数据的方式可能比向服务 ...

  5. PHP 5.4中的traits特性

    Trait 是 PHP5. 中的新特性,是 PHP 多重继承的一种解决方案.例如,需要同时继承两个 Abstract Class, 这将会是件很麻烦的事情,Trait 就是为了解决这个问题. 简单使用 ...

  6. ASP.NET MVC4中的bundles特性引发服务器拒绝访问(403错误)

    在ASP.NET MVC4中微软引入了bundles特性,这个特性可以将服务器端的多个Javascript或多个css文件捆绑在一起作为一个单一的URL地址供客户端浏览器调用,从而减少了页面上Http ...

  7. 关于C# 中的Attribute 特性

    关于C# 中的Attribute 特性 作者: 钢钢  来源: 博客园  发布时间: 2011-01-09 23:30  阅读: 13921 次  推荐: 12   原文链接 [收藏] 摘要:纠结地说 ...

  8. cuda中时间用法

    转载:http://blog.csdn.net/jdhanhua/article/details/4843653 在CUDA中统计运算时间,大致有三种方法: <1>使用cutil.h中的函 ...

  9. 专家解读Linux操作系统内核中的GCC特性

    专家解读Linux操作系统内核中的GCC特性   Linux内核使用GNU Compiler Collection (GCC)套件的几个特殊功能.这些功能包括提供快捷方式和简化以及向编译器提供优化提示 ...

随机推荐

  1. .Net Core Configuration Etcd数据源

    前言     .Net Core为我们提供了一套强大的Configuration配置系统,使用简单扩展性强.通过这套配置系统我们可以将Json.Xml.Ini等数据源加载到程序中,也可以自己扩展其他形 ...

  2. Linux文件目录和访问权限

    前言 本文知识点是曾经学习过程中收录整理的,方便学习使用,并非在下撰写. 一>Lniux目录结构 /:根目录,一般根目录下只存放目录,在Linux下有且只有一个根目录.所有的东西都是从这里开始. ...

  3. IDEA自定义类注释和方法注释(自定义groovyScript方法实现多行参数注释)

    一.类注释 1.打开设置面板:file -> setting -> Editor -> file and code Templates 选择其中的inclues选项卡,并选择File ...

  4. APP移动端测试

    重点: app测试的内容 add 命令  monkey命令 次重点:模拟器的安装 雷电 夜神 android 自带的模拟器 真机测试 简单了解云测Testing  腾讯云() 了解:市场有点移动端的操 ...

  5. typedef 定义指针数组和数组指针及其使用。

    #include<stdio.h> typedef char arr[][]; typedef char *name[]; // 指针数组 typedef char (*lan)[]; / ...

  6. 微信小程序-返回并更新上一页面的数据

    小程序开发过程中经常有这种需求,需要把当前页面数据传递给上一个页面,但是wx.navigateBack()无法传递数据. 一般的办法是把当前页面数据放入本地缓存,上一个页面再从缓存中取出. 除此之外还 ...

  7. 第一章、认识Shiro

    Shiro简介 Apache Shiro是Java的一个安全框架.目前,使用Apache Shiro的人越来越多,因为它相当简单,对比Spring Security,可能没有Spring Securi ...

  8. 35 _ 队列1 _ 什么是队列.swf

    队列是一种可以实现一个先进先出的存储结构 什么是队列? 队列(Queue)也是一种运算受限的线性表.它只允许在表的一端进行插入,而在另一端进行删除.允许删除的一端称为队头(front),允许插入的一端 ...

  9. webpack4.*入门笔记

    全是跟着示例做的.看下面文章 入门 1.nodejs基础 http://www.runoob.com/nodejs/nodejs-tutorial.html 2.NPM 学习笔记整理 https:// ...

  10. SerializableClob转String

    ORACLE数据库读取CLOB字段,JAVA中获取到类型为SerializableClob,如何转换成String类型那? 以下是代码示例:         SerializableClob sc = ...