文章目录

6.1 简介

传统CPU中,内存是线性内存或平面内存,单个CPU核可访问任何地址的内存。

  • 在CPU的实现中,有(L1)、L2及L3缓存。
  • 善于对CPU代码进行优化及有(HPC)背景的人,很熟悉缓存。
  • 对多数程序员而言,这些概念却抽象。

抽象已在现代程序语言中成了趋势。

  • 它使程序员离底层硬件越来越远,以确保程序员不必过多了解底层硬件就可以编写程序。
  • 虽然抽象将问题提升到一个更高的层次,使得问题解决更加简单,程序的生产率也提高了一个等级,但它仍需要灵活的编译器将上层的抽象转换成底层硬件能够理解的形式。
  • 理论上,抽象是一个非常伟大的概念,但事实上却很难将其毫无瑕疵地实现。
  • 相信在未来的几十年里,我们可以看到编译器与程序语言得到巨大改善,使其能自动地利用并行硬件。
  • 但目前而言,要想程序在不同平台获得高性能,还必须了解硬件是如何工作的,这是问题的关键所在

在一个基于CPU的系统中,如果要获取好的性能,就必须了解缓存是如何工作的。

  • 先看CPU端的缓存,然后将其与GPU端的缓存进行对比,看看有哪些相似之处。
  • 从指令流的角度来看,大多数程序都是以顺序方式执行,其中包含了各种各样的循环结构。
  • 如果程序调用了一个函数,很有可能该程序会很快再次调用这个函数,如果程序对某一块特殊的内存进行了访问,很有可能在很短的时间内程序会再次对这块内存进行访问。
  • 当对某块数据已经使用过一次后还可能再次使用,某个函数执行一次之后还可能再次执行,这就是时间
    局部性( temporal locality)原则。

从一个计算机系统中的主内存DRAM中获取数据会非常的慢。

  • 相对于处理器的时钟频率,DRAM的存取速度一直都非常慢。
  • 随着处理器时钟频率的不断提高,DRAM的存取速度就更跟不上处理器的时钟频率了。

目前处理器搭配的DDR-3DRAM一般1.6GHz。

  • 若配备一些高速的模块及更好的处理器,能达到2.6
  • 然而,CPU的每个核能达到3.0,如果没有缓存进行快速存取,对CPU而言,DRAM的带宽非常不足。
  • 由于代码与数据都在DRAM中,若无法快速地从DRAM获取程序与数据,
    • CPU的指令带宽(一个时间帧内指令执行的数量)会明显受到限制。

两重要的概念,

  • 存储带宽( memory bandwidth),即在一定时间内从DRAM读出或写入的数据量。
  • 另一个是延迟( latency),即响应一个获取内存的请求所花费的时间,通常这个时间会是上百个处理器周期。
  • 如果一个程序需要从内存获取四个元素,则将所有请求一起处理然后等待所有数据到达,总要好过处理一个请求然后等待数据到达,处理下一个后再等待。
  • 因此,如果没有缓存,处理器的存储带宽将会受到很大限制,延迟也会增大很多

用超市的收银过程作例子,来形象化地介绍存储带宽与延迟。

  • 一家超市有N个收银台,并不是每个收银台都有员工在工作。
  • 如果只有两个收银台正常工作,顾客将会在这两个收银台后面排起很长的队等待付款。
  • 每个收银台在一定时间内(例如,一分钟)处理的顾客的人数就是吞吐量或者带宽。
  • 而顾客在队列中需要等待的时间则为延迟,即顾客从进入队列直到付款离开所花费的时间。

由于队伍越来越长,店主可能会增加更多收银台进行工作,顾客就会分流到到新的收银

台。

  • 如果新开两个收银台,收银台的带宽将增加一倍,因为在相同的时间内可以服务两倍于之前的人数。
  • 此外,延迟也会半,因为平均每个队列只有原先的一半长,等待的时间也就减少了一半。

这不是免费的。

  • 更多的收银台意味要花更多的钱雇更多的收银员;
  • 超市更多的零售空间需要分配给收银台,货架空间就会减少。
  • 从内存总线带宽与内存设备时钟频率的角度来看,处理器的设计也出现了相同的权衡点。
  • 设备上硅片占用的空间是有限的,外部内存总线的宽度通常会受到处理器上物理引脚数目的限制。

另个要理解的概念为事务开销( transaction overhead)。

  • 当收银员为每名顾客处理付款操作时就有明显的开销。
  • 有些顾客的购物篮中只有两三件物品,而有些购物车则充满了物品。
  • 店主会更喜欢那些购满整个购物车的顾客,因为他们可以更加高效地处理,即收银员会花费更多的时间在清点货物上,而不是处理付款。

在GPU中,能看到类似的机理。

  • 有些内存事务,相较于处理它们的开销来说,是属于轻量级的。
  • 因此,内存单元获取的时间相对于开销而言也就较少,换言之,即达到最高效率的百分比很低。
  • 但有些事务比较庞大,要花大量的时间才能完成,这些事务能高效地处理并能达到接近最高的存储传输速率。
  • 它们在一端转化成基于字节的内存事务,而在另端转换成基于字的存储事务。
  • 为获得最高的存储效率,GPU需要大量的庞大事务和尽可能少的轻量级事务。

6.2 高速缓存

  • 高速缓存是硬件上非常接近处理器核的高速存储器组。
  • 硅制成,但硅很贵,
    • 高速缓存的价格也贵。
  • 硅还可用来制成更大的芯片
    • 及低产却价格更贵的处理器。
  • 服务器上的Xeon比台式机的处理器贵得多,
    • 因为它巨大的三级缓存,普通处理器搭载的缓存较小

高速缓存的最大速度与大小成反比。

  • 一级最快,大小16KB、32KB或者64KB。
    • 通常每个CPU核会分配一个单独的一级缓存。
  • 二级相对慢,但大,有256KB~512KB。
  • 三级有也可能无,若存在,几兆
  • 二级或三级一般在处理器的核间是共享的,或者作为连接于特定处理器核的独立缓存来维护。
  • 传统的CPU上,一般而言,至少三级在处理器核间是共享的。
    • 处理器核便可通过设备上这块共享内存快速地进行通信

G80与GT200系列GPU没有与CPU中高速缓存等价的存储器,

  • 但却有两块基于硬件托管的缓存,即常量内存与纹理内存,类似CPU中的只读缓存。
  • 与CPU不同,
  • GPU主要依赖基于程序员托管的缓存或共享内存区。

费米架构的GPU中,

  • 第一次引入不基于程序员托管的数据缓存这个概念。
  • 此架构的GPU中每个SM有个一级缓存,它存既是基于程序员托管的又是基于硬件托管的。
  • 在所有的SM间有一个共享的二级缓存

缓存在处理器的核或SM之间共享有什么意义?

-这主要是为了让设备间能够通过相同的共享缓存进行通信。

  • 共享缓存允许处理器之间不需要每次都通过全局内存进行通信。
  • 这在进行原子操作的时候特别有用,由于二级缓存是统一的,所有的SM在给出的内存地址处获取一致版本的数据。
  • 处理器无须将数据写回缓慢的全局内存中,然后再读,它只需要保证处理器核之间的数据一致性。
  • 在G80与GT200上,无统一的高速缓存,因此与费米以及后续的设备相比,在它们上面做原子操作很慢

对大多数程序,高速缓存很有用。

  • 很多人不关心如何在编写软件时获取更好性能。
  • 缓存的引入使大多数程序能更加合理地运转,程序员也不需要过多地了解硬件是如何工作的。
  • 但这种编程方式只适合最初的,多数情况下,需要更加深入地了解

CUDA初学者与CUDA专家相比,差距很大。

  • 希望读者通过阅读此书,能够迅速学会编写CUDA代码,用并行代码替换你原来的串行代码,使你的程序加速
    好几倍。

数据存储类型

GPU提供了不同层次的若干区域供程序员存放数据,

  • 每块区域根据其能达到的最大带宽以及延迟而定义,如表6-1

6 CUDA内存处理

6 CUDA内存处理

最快速是设备中的寄存器,接着是共享内存,如基于程序员托管的一级缓存,

  • 然后是常量内存、纹理内存、常规设备内存,最后则是主机端内存。
  • 注意不同存储器之间的存储速度的数量级的变化规律。
  • 本章将依次介绍这几种存储器的使用
    • 及如何最大化地利用它们。

大多数书从全局内存开始,因为它在性能优化中扮演着关键。

  • 除非获取全局内存的模式正确,否则一旦模式错误,就无必要再去谈优化。
  • 本章先介绍如何高效地使用最内部的设备,
  • 由内而外,
  • 最后介绍全局内存及主机端内存,
    • 让读者能理解不同层次的存储器的效率
      • 及知道如何获得高效。

大多数CUDA程序都是逐渐成熟的。

  • 一开始使用全局内存初始化,初始化完毕之后再考虑使用其他类型的内存,
    • 如,零复制内存、共享内存、常量内存,最终寄存器也被考虑进来。
    • 为优化程序,我们需要在开发过程中考虑这些问题。
  • 程序之初就要考虑用较快的存储器,且准确知道在何处以及如何提高程序性能,而不是在程序写完之后才想到用哪些快速的存储器对程序优化。
  • 另外,不仅要思考如何高效访问全局内存,也要想办法减少对全局内存的访问次数,
    • 尤其在数据会被重复利用时

6.3 寄存器的用法

与CPU不同

  • G的每个SM有上千奇
  • 一个SM可以看作是一个多线程的CPU核。
  • CPU有二、四、六或八个核。
  • 高端的费米GF100系列的顶级设备上有16个SM,
    • GT200系列的的SM多达32个,
    • G80系列SM最多也有16个。

费米架构设备上的SM比早期的一些设备要少。

  • 因为费米架构设备上每个SM拥有更多的SP(流处理器),
    • 而所有的工作其实都是SP处理的。
  • 每个核上SP数目不同,因此每个核支持的线程数也不同。
  • CPU每个核会支持一到两个硬件线程。
  • GPU的每个核8-192个SP,
    • 每个SM能同时运行这些数目的硬件线程

GPU上的应用线程进入流水线、进行上下文切换并分配到多个SM中。

  • 意味在一台GPU设备的所有SM中活跃的线程数通常数以万。

CPU与GPU架构的区别是CPU与GPU映射寄存器的方式。

  • CPU通过使用寄存器重命名和栈来执行多线程。
  • 为运行一个新任务,CPU需要进行上下文切换,将当前所有寄存器的状态保存到栈(系统内存)上,然后从栈中恢复当前需要执行的新线程上次的执行状态。
  • 这些操作通常需要花费上百个CPU时钟周期。
  • 如果在CPU上开启过多的线程,时间几乎都将花费在上下文切换过程中寄存器内容的换进/换出操作上。
  • 因此,如果在CPL开启过多的线程,有效工作的吞吐量将会快速降低

GPU相反。

  • 它用多线程隐藏了内存获取与指令执行带来的延迟。
    • so GPU上开启少的线程
    • 会因为等待内存事务使GPU处于闲置
  • GPU不用寄存器重命名,而是为每个线程都分配真实的寄存器。
    • 因此,当要上下文切换时,就将指向当前寄存器组的选择器(或指针)更新,
    • 以指向下个执行的线程束的寄存器组,因此几乎零开销

注意这里用到了线程東的概念。

  • 5章介绍线程的部分已详细地介绍了这个概念
  • 线程東即同时调度的一组线程。
  • 当前的硬件中,一个线程束含32个线程。
    • 在一个SM中,每次换进/换出、调度都是32个线程同时执行

每个SM能调度若干个线程块。

  • 在SM层,线程块即若干个独立线程束的逻辑组。
  • 编译时会计算出每个内核线程需要的寄存器数目。
  • 所有的线程块都具有相同的大小,并拥有已知数目的线程,
    • 每个线程块需要的寄存器数目也就是已知和固定的。
  • 因此,GPU就能为在硬件上调度的线程块分配固定数目的寄存器组
    here!!!

相关文章:

  • 2022-02-08
  • 2022-12-23
  • 2022-12-23
  • 2021-07-03
  • 2021-12-12
  • 2021-12-09
  • 2021-10-22
猜你喜欢
  • 2021-10-25
  • 2022-12-23
  • 2021-10-01
  • 2022-12-23
  • 2022-12-23
  • 2021-09-21
相关资源
相似解决方案