CUDA中关于C++特性的限制
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__内存空间描述符不可用于如下情况:
class、struct、union数据成员- 形式参数
- 在主机端执行的函数中不可声明非extern变量
- 在设备端执行的函数中,除
__shared__外另外三个描述符描述的变量不可声明为既非extern亦非static
此外,若有此四类描述符的变量的类型为自定义类(class或struct),则该类必须拥有空构造函数和空析构函数。
关于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是一个被const或constexpr 限定符修饰的变量或类的静态变量,且V没有被内存空间描述符(__device__、__shared__、__constant__)修饰,则常量V是一个主机端常量。但常量V仍然可以被设备端代码直接访问,只要V满足如下条件:
V在使用点前用一个常量表达式初始化V没有被volatile限定符修饰V是一个内置整型(int)或内置浮点型(float),但constexpr比const宽松,只要是非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_list或va_list类型,不能为右值引用类型。
不允许将带有虚函数的类的对象作为参数传递给__global__函数,同样的,不允许将虚继承的派生类的对象作为参数传递给__global__函数。
不能在__global__函数实例化或__device__、__constant__变量实例化的类型模板参数、无类型模板参数、模板模板参数中使用如下类型或模版:
- 定义为
__host__、__host__ __device__的类型或模版 - 类型或模版是某个类的
private或protected成员且该类的父类(若有)没有定义在__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__
}
需要注意,当派生类重写基类的虚函数时,必须保证执行空间与基类虚函数的执行空间一致。但若函数F、D为虚析构函数,且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不允许对内置变量(gridDim、blockIdx、blockDim、threadIdx、warpSize)赋值。
CUDA在设备端同样支持volatile限定符。
默认情况下,std::move和std::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_info、dynamic_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++特性的限制的更多相关文章
- CUDA中并行规约(Parallel Reduction)的优化
转自: http://hackecho.com/2013/04/cuda-parallel-reduction/ Parallel Reduction是NVIDIA-CUDA自带的例子,也几乎是所有C ...
- CUDA 中的计时方法
问题描述:一般利用CUDA进行加速处理时,都需要测试CUDA程序的运行时间,来对比得到的加速效果. 解决方法: 1).GPU端计时,即设备端计时. 2).CPU端计时,即主机端计时. 设备端计时有两种 ...
- Java语言中的面向对象特性总结
Java语言中的面向对象特性 (总结得不错) [课前思考] 1. 什么是对象?什么是类?什么是包?什么是接口?什么是内部类? 2. 面向对象编程的特性有哪三个?它们各自又有哪些特性? 3. 你知 ...
- ASP.NET 5与MVC 6中的新特性
差点忘了提一句,MVC 6中默认的渲染引擎Razor也将得到更新,以支持C# 6中的新语法.而Razor中的新特性还不只这一点. 在某些情况下,直接在Web页面中嵌入某些JSON数据的方式可能比向服务 ...
- PHP 5.4中的traits特性
Trait 是 PHP5. 中的新特性,是 PHP 多重继承的一种解决方案.例如,需要同时继承两个 Abstract Class, 这将会是件很麻烦的事情,Trait 就是为了解决这个问题. 简单使用 ...
- ASP.NET MVC4中的bundles特性引发服务器拒绝访问(403错误)
在ASP.NET MVC4中微软引入了bundles特性,这个特性可以将服务器端的多个Javascript或多个css文件捆绑在一起作为一个单一的URL地址供客户端浏览器调用,从而减少了页面上Http ...
- 关于C# 中的Attribute 特性
关于C# 中的Attribute 特性 作者: 钢钢 来源: 博客园 发布时间: 2011-01-09 23:30 阅读: 13921 次 推荐: 12 原文链接 [收藏] 摘要:纠结地说 ...
- cuda中时间用法
转载:http://blog.csdn.net/jdhanhua/article/details/4843653 在CUDA中统计运算时间,大致有三种方法: <1>使用cutil.h中的函 ...
- 专家解读Linux操作系统内核中的GCC特性
专家解读Linux操作系统内核中的GCC特性 Linux内核使用GNU Compiler Collection (GCC)套件的几个特殊功能.这些功能包括提供快捷方式和简化以及向编译器提供优化提示 ...
随机推荐
- 138 张图带你 MySQL 入门
SQL 基础使用 MySQL 是一种关系型数据库,说到关系,那么就离不开表与表之间的关系,而最能体现这种关系的其实就是我们接下来需要介绍的主角 SQL,SQL 的全称是 Structure Query ...
- 说说TCP的三次握手和四次挥手
一.传输控制协议TCP简介 1.1 简介 TCP(Transmission Control Protocol) 传输控制协议,是一种 面向连接的.可靠的.基于字节流的传输层 通信协议. TCP是一种面 ...
- python的常用魔法方法详细总结
构造和初始化 __init__我们很熟悉了,它在对象初始化的时候调用,我们一般将它理解为"构造函数". 实际上, 当我们调用x = SomeClass()的时候调用,__init_ ...
- mysql错误详解(1819):ERROR 1819 (HY000): Your password does not satisfy the current policy requirements
O(∩_∩)O哈哈~ 在学习 Mysql 的时候又遇到了新问题了 o(╥﹏╥)o 当我在准备为用户授权的时候: grant all privileges on *.* to 'root'@'%' id ...
- Python 简明教程 --- 16,Python 高阶函数
微信公众号:码农充电站pro 个人主页:https://codeshellme.github.io 对于那些快速算法,我们总是可以拿一些速度差不多但是更容易理解的算法来替代它们. -- Douglas ...
- JavaScript this 关键词
this是什么呢? JavaScript this 关键词指的是它所属的对象. 它拥有不同的值,具体取决于它所使用的位置: 在方法中,this 指的是所有者对象. 单独的情况下,this 指的是全局对 ...
- 程序员必备基础:Git 命令全方位学习
前言 掌握Git命令是每位程序员必备的基础,之前一直是用smartGit工具,直到看到大佬们都是在用Git命令操作的,回想一下,发现有些Git命令我都忘记了,于是写了这篇博文,复习一下~ https: ...
- day12—列表、元组、字典基本语法
一.list类中提供的方法 **********************灰魔法************************** 1. 原来值最后追加 append() li = [11, 22, ...
- js事件入门(6)
7.事件冒泡机制 7.1.什么是事件冒泡 当一个元素接收到一个事件以后,会将事件传播给它的父级元素,它的负级元素会一层一层往上传播,直到最顶层window,这种事件传播机制叫作事件冒泡. <!D ...
- gdi和gdi+比较
http://www.360doc.com/content/10/1013/17/1066008_60709410.shtmlhttp://www.360doc.com/content/10/1013 ...