如同昨天说过的所有静态/动态分配的shared memory/global memory一样, unified memory也有两种分配方式.
本章节说的__managed__即是静态分配的. 例如说:
__managed__ int a;
只要你的kernel能运行, 它就可以直接使用a, 而不需要考虑a的空间是何时分配的之类的问题, 比较简便.
此外, 如同所有的unified memory特性一样, a能被CPU和GPU都访问到(还记得我们之前的章节说过, unified memory是升级版本的zero-copy memory吗?)
在很多场合用起来非常简单. 特别的是, 当kernel产生了一个较大的结果, 例如填充了128MB的结果缓冲区,
而你只需要根据结果缓冲区中的情况, 不可在写代码的时刻预测的, 只使用里面的, 例如16MB的内容,
那么应当考虑使用unified memory, 较新的GPU硬件能按需的为你回传你需要访问的内容.
感兴趣的人可以看一下unified memory里面的如何CPU或者GPU按需的page-fault, 然后自动传输的.
此时的性能将可能会超过手工的传输(全部).
但这里不详细说明unified memory, 后面将有章节单独描述它. 这是一个很给力的特性, 但一般不推荐在Windows上使用(Windows上一定情况下退化成普通的zero-copy memory, 而且性能很惨)
这是说的静态分配. 和__managed__对应的是cudaMallocManaged, 它将动态的分配一块managed/unified memory(就如同普通的__device__对应的是cudaMalloc*()一样, 后者将动态的分配普通显存)。
其实静态分配有很多好处, 有些要求严格的项目是很多时候不允许动态分配的
因为静态分配只要运行起来了, 存储器的使用情况不会发生变化, 程序要么一切正常的运行了, 要么运行不起来(资源不足)
而动态的则可能随着运行中的数据变化(例如你的分配情况依赖于一个具体的数据), 有不可预测的后果.
例如运行正常15小时后, 突然挂掉.这对类似雷达测量之类使用GPU的场合有时候是不可接受的.
等等了. 所以本章节(和昨天的那部分)中的静态和动态分配都有各自的用途*
然后本章节的另外一部分则告诉你, 如何更有效的使用指针.
CUDA引入了一个__restrict__的扩展来设定C99的Restricted Pointer
(VC和GCC有各自的关键字, 无非是前后的下划线和restrict的位置的区别不同, 而CUDA C的版本则是前后都有两个下划线)
例如VC版本的是__restrict
使用restrict解决了一个重要的C语言里的问题.就是指针不再像以前那样能乱指了(被restrict了么)
如同本章节的说法, 有的时候, 编译器无法进行一些优化, 因为它会按照最坏情况进行估计, 例如本章代码的:
void foo(const float* a,
const float* b,
float* c)
{
c[0] = a[0] * b[0];
c[1] = a[0] * b[0];
c[2] = a[0] * b[0] * a[1];
c[3] = a[0] * a[1];
c[4] = a[0] * b[0];
c[5] = b[0];
...
}
这里面使用了3个指针a,b,c,无法确定这三个指针是否有任何重叠, 例如实际上可能只有1个缓冲区, 而a,b,c只是它们的别名而已.
例如昨天的章节有人问, 使用了多个extern __shared__ 会如何?此时将会产生重叠/重名的指针(alias)
通过一个指针写入, 很可能改变另外一个指针指向的内容的状态.而__restrict__的出现改变了这一点,
type * __restrict__ a
type * __restrict__ b
type * __restrict__ c
有这样的3个指针. 通过__restrict__修饰后, 你暗示编译器各自指向的内容只能通过各自的a,b,c指针进行访问. 暗示它通过一个指针的写入改变了另外一个指针的值.因此编译器可以放心的进行一些优化, 例如通过b写入后, 不必担心a里面之前读取到的值是否已经改变, 是否需要重新读取, 而可以安心的使用老值.
此时有助于减少无辜的生成的指令. 提高性能.请注意, 这只是一个暗示,
如果你暗示了编译器是一套, 但是做的是另外一套做法,例如, 有的读者比较调皮, 想尝试一下使用重叠的3个指针, 却告诉了编译器是__restrict__的, 结果会如何?那么编译器编译出来的代码很可能运行出错, 请不要这样做(编译器无法在编译时刻检测到你的指针有重叠, 也无法在运行时刻检测到你有重叠, CUDA C和C均不是具有完备的Runtime的语言, 这样做将导致未定义的结果)
所以如果一旦要使用__restrict__来暗示CUDA C编译器, 就一定要做到你的指针使用行为和你所暗示过的一致. 否则将导致未知后果(例如kernel挂掉),类似的, 本章节还提到, 含有数组元素访问, 例如a[0] * b[1] + c[2] - d[3]这种代码的公用表达式,
在使用了__restrict__的指针a,b,c,d后, 编译器可以安心的做公用表达式消除优化,(这是一种常见的优化, 也是很多新人常见的问题: