forked from TrojanXu/cudaProgrammingGuideChinese
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy path2programmingModel.tex
More file actions
executable file
·98 lines (61 loc) · 5.01 KB
/
Copy path2programmingModel.tex
File metadata and controls
executable file
·98 lines (61 loc) · 5.01 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
\chapter{编程模型}
\label{sec:programModel}
本章引入了CUDA编程模型背后的主要概念,方式是概述它们是怎样使用C语言表示的。更多的关于CUDA C的描述在\ref{sec:programInterface}章。
本章使用的向量相加例子的完整代码可在SDK中的vectorAdd代码样本中找到。
\section{内核}
\label{sec:kernels}
CUDA通过允许程序员定义称为内核的C函数扩展了C,内核调用时会被N个CUDA线程执行N次(译者注:这句话要好好理解,其实每个线程只执行了一次),这和普通的C函数只执行一次不同。
内核使用{\_}{\_}global{\_}{\_}声明符定义,使用一种新$<<<...>>>$执行配置语法指定执行某一指定内核的线程数(参看\ref{sec:cextension})。每个执行内核的线程拥有一个独一无二的线程ID,可以通过内置的threadIdx变量在内核中访问(译者注:这只说明在块内是唯一的,并不一定是全局唯一的)。
下面的样本代码将两个长度为N的向量A和B相加,并将结果存入向量C中。
\lstinputlisting{bookSrc/vecadd1.cu}
这里,N个线程中的每一个执行VecAdd()的一次成对加法(译者注:由于只使用了一个块,因此线程ID是唯一的)。
\section{线程层次}
\label{sec:thread}
为简便起见,threadIdx是一个有3个分量的向量,所以线程可以使用一维,二维,三维索引标识,形成一维,二维,三维的线程块。这提供了一种自然的方式来调用作用在域内元素上的计算,如向量,矩阵,体元(volume)。
线程索引和线程ID直接相关:对于一维的块,它们相同;对于二维长度为(Dx,Dy)的块,线程索引为(x,y)的线程ID是(x+yDx);对于三维长度为(Dx,Dy,Dz)的块,索引为(x,y,z)的线程ID为(x+yDx+zDxDy)(译者注:这和我们使用C数组的方式不一样,大家注意理解)。
下面的例子代码将两个长度为N*N的矩阵A和B相加,然后将结果写入矩阵C。
\lstinputlisting{bookSrc/vecadd2.cu}
由于块内的所有线程必须存在于同一个处理器核心中且共享该核心有限的存储器资源,因此,一个块内的线程数目是有限的。在目前的GPU上,一个线程块可以包含多达1024个线程。
然而,一个内核可被多个同样大小的线程块执行,所以总的线程数等于每个块内的线程数乘以线程块数。
线程块被组织成一维、二维或三维的线程网格,如图\ref{fig:gotb}所示。一个网格内的线程块数往往由被处理的数据量而不是系统的处理器数决定,前者往往远超后者。
\begin{figure}[htbp]
\centering
\includegraphics[bb=0 0 310 269]{figures/grid-of-thread-blocks.png}
\caption{线程块网格}
\label{fig:gotb}
\end{figure}
线程块内线程数和网格内线程块数由$<<<...>>>$语法确定,参数可以是整形或者dim3类型。二维的块或网格的尺寸可以以和上一个例子相同的方式指定。
网格内的每个块可以通过一维、二维或三维索引唯一确定,在内核中此索引可通过内置的blockIdx变量访问。块的尺寸(dimension)可以在内核中通过内置变量blockDim访问。
为了处理多个块,扩展前面的MatAdd()例子后,代码成了下面的样子。
\lstinputlisting{bookSrc/vecadd3.cu}
一个长度为16*16(256线程)的块,虽然是强制指定,但是常见。像以前一样,创建了内有足够的块的网格,使得一个线程处理一个矩阵元素。为简便起见,此例假设网格每一维上的线程数可被块内对应维上的线程数整除,尽管这并不是必需。
线程块必须独立执行:而且能够以任意顺序,串行或者并行执行。这种独立性要求使得线程块可以以任何顺序在任意数目核心上调度,如\ref{fig:as}所示,保证了程序员能够写出能够随核心数目扩展的代码(enabling programmers to write code that scales with the number of cores)。
块内线程可通过共享存储器和同步执行协作,共享存储器可以共享数据,同步执行可以协调存储器访问。更精确一点说,可以在内核中调用{\_}{\_}syncthreads()内置函数指明同步点;{\_}{\_}syncthreads()起栅栏的作用,在其调用点,块内线程必须等待,直到所以线程都到达此点才能向前执行。共享存储器节给出了一个使用共享存储器的例子。
为了能有效协作,共享存储器要求是靠近每个处理器核心的低延迟存储器(更像L1缓存),而且{\_}{\_}syncthreads()要是轻量级的。
\section{存储器层次}
\label{sec:mem}
在执行期间,CUDA线程可能访问来自多个存储器空间的数据,如图\ref{fig:mh}所示。每个线程有私有的本地存储器。每个块有对块内所有线程可见的共享存储器,共享存储器的生命期和块相同。所有的线程可访问同一全局存储器。
另外还有两种可被所有线程访问的只读存储器:常量和纹理存储器空间。全局,常量和纹理存储器空间为不同的存储器用途作了优化(参看设备存储器访问)。纹理存储器还为一些特殊数据格式提供了不同的寻址模式和数据滤波(参看纹理和表面存储器)。
在同一应用中发射的内核之间,全局,常量和纹理存储器空间是持久的。
\begin{figure}[htbp]
\centering
\includegraphics[bb=0 0 310 362]{figures/memory-hierarchy.png}
\caption{存储器层次}
\label{fig:mh}
\end{figure}
\section{异构编程}
\label{sec:hetecomputing}
如图\ref{fig:hp}所示,CUDA编程模型假设CUDA线程在物理上独立的设备上执行,设备作为主机的协处理器,主机运行C程序。例如,内核在GPU上执行,而C程序的其它部分在CPU上执行就是这种模式。
CUDA编程模型同时假设主机和设备各自都维护着自己独立的DRAM存储器空间,各自被称为主机存储器空间和设备存储器空间。因此,程序通过调用CUDA 运行时,来管理对内核可见的全局、常量和纹理存储器空间(参看\ref{sec:programInterface})。这包括设备存储器分配和释放,也包括在主机和设备间的数据传输。
\begin{figure}[htbp]
\centering
\includegraphics[bb=0 0 311 429]{figures/heterogeneous-programming.png}
\caption{异构编程}
\label{fig:hp}
\end{figure}
注意:串行代码在主机上执行而并行代码在设备上执行
\section{计算能力}
设备的计算能力由主修订号和次修订号定义。
主修订号相同的设备基于相同的核心架构。基于Kepler架构的设备的主修订号为3,基于Fermi架构的设备的主修订号为2,基于Tesla架构的设备的主修订号为1。
次修订号对应着对核心架构的增量提升,也可能包含了新特性。
支持CUDA的GPU列出了所有支持CUDA的设备和它们的计算能力。\ref{sec:cc}给出了各计算能力设备的技术规范。