最靠近流处理器的是寄存器文件(register file),每个寄存器文件是32bit。对线程来说,寄存器都是私有的,不允许其它线程染指。由于更靠近流处理器,寄存器具有最快的速度,GT200的每个SM拥有64KB的寄存器文件(Register Files),故一个块内最多可分配16K个寄存器,而G80中每个SM只有32KB,故一个块最多可分配8K个寄存器。最新加入的64bit数据类型(双精度浮点和64位整数型)将占用两个相邻的寄存器单元。CUDA的运行环境能够动态的为线程块分配寄存器,而每个线程块中的线程占用的寄存器大小则是静态分配的,在线程块寿命期间都不会更改,因此一个线程占用的寄存器数目是它在运行时占用的最大数目。如果寄存器被消耗完,数据将被存储在本地存储器(local memory)。对每个线程来说,本地存储器也是私有的,但是本地存储器是显存中的一个分区,速度很慢,而且使用本地存储器过多的话,程序也会终止,因此,编程时要尽量保证不能将数据放到本地存储器中,这可以通过修改块大小,使用共享存储器等方法来解决。
共享存储器是可以被同一块中的所有线程访问的可读写存储器,它的生存期就是块的生命期。在没有冲突的情况下,访问共享存储器几乎与访问寄存器一样快,是实现线程间通信的最好方法。共享存储器可以实现许多不同的功能,如用于保存共用的计数器或者块内的公用结果(例如reduction)。在同一个块内,所有的线程都能够读写共享存储器中的数据,相比于AMD的显卡来说,共享存储器是NVIDIA显卡的一项特色。一般而言,在kernel运行时,要先将数据从全局存储器写入共享存储器;计算完成后要将共享存储器中的结果转存入全局存储器。
Tesla的每个SM拥有16KB共享存储器,用于同一个线程块内的线程间通信。为了使一个half-warp内的线程能够在一个内核周期中并行访问,共享存储器被组织成16个bank,每个bank拥有32bit的宽度,故每个bank可保存256个整形或单精度浮点数,或者说目前的bank组织成了256行16列的矩阵。如果一个half-warp中有一部分线程访问属于同一bank的数据,则会产生bank conflict,降低访存效率,在冲突最严重的情况下,速度会比全局显存还慢,但是如果half-warp的线程访问同一地址的时候,会产生一次广播,其速度反而没有下降。在不发生bank conflict时,访问共享存储器的速度与寄存器相同。在不同的块之间,共享存储器是毫不相关的。 Normal 0 7.8 磅 0 2 false false false EN-US ZH-CN X-NONE
在实现中,GPU要把显存中的数据写到共享存储器中,必须先把数据写到寄存器里,再转移到共享存储器中,在编程时,这是隐式实现的。所以如果没有块内的数据共享千万不能用共享存储器,否则会降低速度。但是如果由于寄存器使用过量,那么我们可以使用共享存储器来当寄存器使用,此时比纯使用寄存器慢一点,但是是值得的。
Tesla能够在共享存储器内进行高速的原子操作。这里的原子操作是指保证每个线程能够独占的访问存储器,即只有当一个线程完成对存储器的某个位置的操作以后,其他线程才能访问这一位置。G80只支持对global memory的原子操作。访问global memory需要很长的访存延迟(长达数百个时钟周期),性能很低。在GT200及以后的GPU上,可以支持对shared memory中的原子操作指令(其中包括CAS指令,并且支持64位)。但是,CUDA并不提供对浮点数的原子操作(只有一个赋值的浮点原子指令),而在科学计算中,浮点数的使用远比整数要多,而且在Fermi的特性列表中,也没有看到加入浮点原子指令的信息,这不能不说是一个遗憾。 Normal 0 7.8 磅 0 2 false false false EN-US ZH-CN X-NONE
除此以外,多处理器上,还有两种只读的存储器:常数存储器(constant memory)和纹理存储器(texture memory),它们是利用GPU用于图形计算的专用单元实现的。常数存储器空间较小(只有64KB),属于片外存储器,其速度比shared要慢,但是它具有缓存,并且无须考虑冲突问题,主要用来加速对常数的访问。
从物理上说,纹理存储器不是存储器,它只是利用了纹理缓存而已。纹理缓存与CPU的缓存有很大的不同。首先,CPU的缓存往往是一维的,因为大多数的架构中的存储器地址是线性的。当访问一个只有4-8Byte的数据字时,会取出一个缓存单元中所有的64B数据。根据局部性原理,CPU处理的数据往往有很强的时空相关性,因此多取出的相邻的数据极有可能会被用到。CPU处理的数据只有一维,因而其缓存也只是在一个维度上是连续的;GPU需要处理的纹理则是连续的二维图像,因此纹理缓存也必须是在两个维度上连续分布的。典型的存储器控制器会将二维的纹理存储器空间映射为一维。其次,纹理缓存是只读的,也不满足数据一致性。当纹理被修改以后,必须更新整个纹理缓存,而不是纹理缓存中被修改的一小部分。第三,纹理缓存的主要功能是为了节省带宽和功耗,而CPU的缓存则是为了实现较低的延迟。第四,纹理可以实现对数据的特殊处理,比如怎样处理越界数据,自动实现插值等。
最后是全局存储器(global memory),使用的是普通的显存。整个网格中的任意线程都能读写全局存储器的任意位置。目前对Global memory的访问没有缓存,因此显存的性能对GPU至关重要。为了能够高效的访问显存,读取和存储必须对齐,宽度为4Byte。如果没有正确的对齐,读写将被编译器拆分为多次操作,极大的影响效率。此外,多个half-warp的读写操作如果能够满足合并访问(coalesced access),那么多次访存操作会被合并成一次完成,从而提高访问效率。
G80的合并访存条件十分严格。首先,访存的开始地址必须对齐:16x32bit的合并必须对齐到64Byte(即访存起始地址必须是64Byte的整数倍);16x64bit的合并访存起始必须对齐到128Byte;16x128bit合并访存的起始地址必须对齐到128Byte,但是必须横跨连续的两个128Byte区域。其次,只有当第K个线程访问的就是第K个数据字时,才能实现合并访问,否则half warp中的16个访存指令就会被发射成16次单独的访存。
GT200不仅放宽了合并访问条件,而且还能支持对8bit和16bit数据字的合并访问(分别使用32Byte和64Byte传输)。在一次合并传输的数据中,并不要求线程编号和访问的数据字编号相同。其次,当访问128Byte数据时如果地址没有对齐到128Byte,在G80中会产生16次访存指令发射,而在GT200中只会产生两次合并访存。而且,这两次合并访存并不是两次128Byte的。例如,一次128Byte访存中有32Byte在一个区域中,另外一个区域中有96Byte,那么只会产生一次32Byte合并访存(对有32Byte数据的区域)和一次128Byte(对有96Byte数据的区域)。 Normal 0 7.8 磅 0 2 false false false EN-US ZH-CN X-NONE
除了device端存储器外,还有存在于host端的存储器,即内存。在CUDA中,主机端内存分为两种:Pageable host memory和Page-locked host memory,其中Page-locked host memory保证位在于物理内存中,并且能够通过DMA加速与显卡的通信,提高数据传输速度,但是如果主机的内存不够用的话,会减弱系统的性能,但是一般不会出现这种情况。
来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/23057064/viewspace-626987/,如需转载,请注明出处,否则将追究法律责任。
转载于:http://blog.itpub.net/23057064/viewspace-626987/