cudaError_t cudaMalloc(void** devPtr, size_t size)
该函数负责向设备分配一定字节的线性内存,并以devPtr的形式返回指向所分配内存 的指针。cudaMalloc与标准C语言中的malloc函数几乎一样,只是此函数在GPU的内存里分 配内存。通过充分保持与标准C语言运行库中的接口一致性,可以实现CUDA应用程序的 轻松接入。
2. 主机和设备之间的数据传输:cudaMemcpy
cudaError_t cudaMemcpy(void* dst, const void* src, size_t count, cudaMemcpyKind kind)
此函数从src指向的源存储区复制一定数量的字节到dst指向的目标存储区。复制方向 由kind指定。
char* cudaGetErrorString(cudaError_t error)
由一个内核启动所产生的所有线程统称为一个网格。同一网格中的所有线程共享相同 的全局内存空间。一个网格由多个线程块构成,一个线程块包含一组线程。同一个块中的线程之间可以相互协作,不同块内的线程不能协作。
线程格和线程块均使用3个dim3类型的无符号整型字段,而未使用的字段将被初始化 为1且忽略不计
threadIdx blockIdx blockDim gridDim
hernel_name <<<grid, block>>>(argument list);
执行配置的第一个值是网格维度,也就是启动块的数目。第二 个值是块维度,也就是每个块中线程的数目。通过指定网格和块的维度
2. 异步控制
核函数的调用和主线程是异步的,可以使用如下函数强制主机端程序等待所有的核函数执行结束:
cudaError_t cudaDeviceSynchronize(void);
cudaMemcpy函数式隐式同步的,使用该函数在主机和设备之间拷贝数据时,主机端隐式同步,即主机端程序必须等待数据拷贝完成后才能继续执行程序。
cudaError_t cudaMemcpy(void* dst, const void* src, size_t count, cudaMemcpyKind kind)
__global__ void kernel_name(argument list);
__device__和__host__限定符可以一齐使用,这样函数可以同时在主机和设备端进行编译。
__global__ void sumArraysOnGPU(float* A< float* B, float* C){
int i = threadIdx.x;
C{i] = A[i] + B[i];
}
#define CHECK(call) \
{ \
const cudaError_t error = call; \
if(error != cudaSuccess){ \
printf("Error: %s:%d, ", __FILE__, __LINE__); \
printf("code:%d, reason:%s\n", error, cudaGerErrorString(error)); \
exit(1); \
} \
}
使用宏
// 1
CHECK(cudaMemcpy(d_C,gpuRef, nBytes, cudaMemcpyHostToDevice));
// 2
kernel_function<<<grid, block>>>(argument list);
CHECK(cudaDeciveSynchronize());
nvprof [nvprof_args] <application> [application_args] ## 具体用法
nvprof --help ## 获取nvprof帮助信息
nvprof ./sumArraysOnGPU ## 测试内核
#include
#include
#define CHECK(call) \
{ \
const cudaError_t error = call; \
if(error != cudaSuccess){ \
printf("Error: %s:%d, ", __FILE__, __LINE__); \
printf("code:%d, reason:%s\n", error, cudaGerErrorString(error)); \
exit(1); \
} \
}
// 验证核函数
void checkResult(float* hostRef, float* gpuRef, const int N){
double epsilon = 1.0E-8;
int match = 1;
for(int i = 0; i < N; i++){
if(abs(hostRef[i] - gpuRef[i]) > epsilon){
match = 0;
printf("Arrays do not match!\n");
printf("host %5.2f gpu %5.2f at current %d\n", hostRef[i], gpuRef[i], i);
break;
}
}
if(match) printf("Arrays match.\n");
}
void initialData(float* ip, int size){
time_t t;
srand((unsigned int) time(&t));
for(int i = 0; i < size; i++){
ip[i] = (float)(rand() & 0xff) / 10.0f;
}
}
void sumArraysOnHost(float* A, float* B, float* C, const int N){
for(int i = 0; i < N; i++){
C[i] = A[i] + B[i];
}
}
__global__ void sumArraysOnGPU(float* A, float* B, float* C){
int i = threadIdx.x;
C[i] = A[i] + B[i];
}
int main(int argc, char** argv){
printf("%s Starting...\n", argv[0]);
// set up device
int dev = 0;
cudaSetDevice(dev);
//set up data size of vectors
int nElem = 32;
printf("Vector size %d\n", nElem);
// malloc host mmeory
size_t nBytes = nElem * sizeof(float);
float* h_A, *h_B, *hostRef, *gpuRef;
h_A = (float*)malloc(nBytes);
h_B = (float*)malloc(nBytes);
hostRef = (float*)malloc(nBytes);
gpuRef = (float*)malloc(nBytes);
// init data at host side
initialData(h_A, nElem);
initialData(h_B, nElem);
// memset函数功能:为内存填充某个特定的值
memset(hostRef, 0, nBytes);
memset(gpuRef , 0, nBytes);
// malloc device global memory
float* d_A, *d_B, *d_C;
cudaMalloc((float**)&d_A, nBytes);
cudaMalloc((float**)&d_B, nBytes);
cudaMalloc((float**)&d_C, nBytes);
// transfer data from host to device
cudaMemcpy(d_A, h_A, nBytes, cudaMemcpyHostToDevice);
cudaMemcpy(d_B, h_B, nBytes, cudaMemcpyHostToDevice);
// invoke kernel at host side
// 放入一个块内, 一个块有32个线程
dim3 block(nElem);
dim3 grid(nElem/block.x);
sumArraysOnGPU<<<grid, block>>>(d_A, d_B, d_C);
printf("Execution configuration<<%d, %d>>>\n", grid.x, block.x);
// copy kernel result back to host side
cudaMemcpy(gpuRef, d_C, nBytes, cudaMemcpyDeviceToHost);
// add vector at host side for result checks
sumArraysOnHost(h_A, h_B, hostRef, nElem);
// check device results
checkResult(hostRef, gpuRef, nElem);
//free
cudaFree(d_A);
cudaFree(d_B);
cudaFree(d_C);
free(h_A);
free(h_B);
free(gpuRef);
free(hostRef);
return 0;
}
double time = cpuSecond();
矩阵加法
·由二维线程块构成的二维网格
·由一维线程块构成的一维网格
·由一维线程块构成的二维网格
管理3种索引
·线程和块索引
·矩阵中给定点的坐标
·全局线性内存中的偏移量
对于一个给定的线程,首先可以通过把线程和块索引映射到矩阵坐标上来获取线程块 和线程索引的全局内存偏移量,然后将这些矩阵坐标映射到全局内存的存储单元中。
第一步,可以用以下公式把线程和块索引映射到矩阵坐标上:
第二步,可以用以下公式把矩阵坐标映射到全局内存中的索引/存储单元上:
·改变执行配置对内核性能有影响
·传统的核函数实现一般不能获得最佳性能
·对于一个给定的核函数,尝试使用不同的网格和线程块大小可以获得更好的性能
cudaError_t cudaGetDeviceProperties(cudaDeviceProp* prop, int device);
nvidia-smi -q -i 0
-i,–id= Target a specific GPU
-L, --list-gpus Display a list of GPUs connected to the system
-q, --query display GPU or Unit info
-d,–display= Display only selected information: MEMORY UTILIZATION ECC TEMPERATURE POWER CLOCK COMPUTE PIDS PREEREORMANCE SUPPORTED_CLOCKS PAGE_RETIREMENT ACCOUNTING…
内存 设备。。。
运行时设置设备: : CUDA_VISIBLE_DEVICES=*
如果想测试多个设备**=2,3**,nvidia驱动程序将只使用 ID为2和3的设备,并且会将设备ID分别映射为0和1
grid block SM 都有啥区别和联系 线程束 线程块 为啥所有线程束要执行相同指令
找到了一个不错的解答:CUDA中grid、block、thread、warp与SM、SP的关系
GPU中的每一个流式处理器(SM)都能支持数百个线程并发执行,每个GPU通常有多个SM
CUDA采用单指令多线程(SIMT)架构来管理和执行线程,每32个线程为一组,被称为线程束(warp)。线程束中的所有线程同时执行相同的指令,每个SM都将分配给它的线程块划分到包含32个线程的线程束中,然后在可用的硬件资源上调度执行
一个线程块只能在一个SM上被调度。一旦线程块在一个SM上被调度,就会保存在该 SM上直到执行完成。在同一时间,一个SM可以容纳多个线程块。
Fermi的特征是多达512个加速器核心,这被称为CUDA核心。每个CUDA 核心都有一个全流水线的整数算术逻辑单元(ALU)和一个浮点运算单元(FPU),在这 里每个时钟周期执行一个整数或是浮点数指令。CUDA核心被组织到16个SM中,每一个 SM含有32个CUDA核心。Fermi架构有6个384位的GDDR5 DRAM存储器接口,支持多达 6GB的全局机载内存,这是许多应用程序关键的计算资源。主机接口通过PCIe总线将GPU 与CPU相连。GigaThread引擎(图示左侧第三部分)是一个全局调度器,用来分配线程块 到SM线程束调度器上。
包含了15个SM 和6个64位的内存控制器
每个Kepler SM单元包含192个单精度CUDA核心,64个双精度单元,32 个特殊功能单元(SFU)以及32个加载/存储单元(LD/ST)
动态并行是Kepler GPU的一个新特性,它允许GPU动态启动新的网格。有了这个特 点,任一内核都能启动其他的内核,并且管理任何核间需要的依赖关系来正确地执行附加 的工作。
Hyper-Q技术增加了更多的CPU和GPU之间的同步硬件连接,以确保CPU核心能够在 GPU上同时运行更多的任务。当使用Kepler GPU时,既可以增加GPU的利用率,也 可以减少CPU的闲置时间。
CUDA提供了两 个主要的性能分析工具:nvvp,独立的可视化分析器;nvprof,命令行分析器。
有3种常见的限制内核性能的因素: ·存储带宽 ·计算资源 ·指令和内存延迟
线程束是SM中基本的执行单元。当一个线程块的网格被启动后,网格中的线程块分 布在SM中。一旦线程块被调度到一个SM上,线程块中的线程会被进一步划分为线程束。 一个线程束由32个连续的线程组成,在一个线程束中,所有的线程按照单指令多线程 (SIMT)方式执行;也就是说,所有线程都执行相同的指令,每个线程在私有数据上进 行操作。
从逻辑角度来看,线程块是线程的集合,它们可以被组织为一维、二维或三维布局。 从硬件角度来看,线程块是一维线程束的集合。在线程块中线程被组织成一维布局, 每32个连续线程组成一个线程束。
// 不同线程cond值可能不同,导致执行不同分支指令
if(cond){...}
else{...}
在同一线程束中的线程执行不同的指令,被称为线程束分化。
如果一个线程束中的线程产生分化,线程束将连续执行每一个分支路径,而禁用不执行这一路径的线程。线程束分化会导致性能明显地下降。条件分支越多,并行性削弱越严重。在不同的线程束中,不同的条件值不会 引起线程束分化。
为了获得最佳的性能,应该避免在同一线程束中有不同的执行路径。
程序计数器 寄存器 共享内存
当计算资源(如寄存器和共享内存)已分配给线程块时,线程块被称为活跃的块。它所包含的线程束被称为活跃的线程束。活跃的线程束可以进一步被分为以下3种类型:
类型 | 解释 |
---|---|
选定的线程束 | 活跃执行的线程束被称为选定的线程束。 |
阻塞的线程束 | 如果一个线程束没有做好执行的准备,它是一个阻塞的 线程束。 |
符合条件的线程束 | 如果一个活跃的线程束准备执行但尚未执行,它是一个符合条件的线程束。 |
如果同时满足以下两个条件则线程束符合执行条件。
·32个CUDA核心可用于执行 ·当前指令中所有的参数都已就绪
在指令发出和完成之间的时钟周期被定义为指令延迟,GPU的指令延迟被其他线程束的计算隐藏。指令可分为算术指令、内存指令,
估算隐藏延迟需要的活跃的线程束的数量:所需线程束数量=延迟×吞吐量(没懂)
两个执行级别:
·系统级:等待主机和设备完成所有的工作
·块级:在设备执行过程中等待一个线程块中所有线程到达同一点
对于主机来说,由于许多CUDA API调用和所有的内核启动不是同步的, cudaDeviceSyn-chronize函数可以用来阻塞主机应用程序,直到所有的CUDA操作(复制、 核函数等)完成:
cudaError_t cudaDeviceSynchronize(void);
在内核中标记同步点,当__syncthreads被调用时,在同一个线程块中每个线程都必须等待直至该线程块中所 有其他线程都已经达到这个同步点。
在不同的块之间没有线程同步。
__device__ void __syncthreads(void);
线程块中的线程可以通过共享内存和寄存器来共享数据。当线程之间共享数据时,要 避免竞争条件。
运行sumMatrix.cu
运行时间:
并行度:
并行性可以用nvprof和achieved_occupancy指标来验证。一个内核 的可实现占用率被定义为:每周期内活跃线程束的平均数量与一个SM支持的线程束最大 数量的比值。
·因为第二种情况中的块数比第一种情况的多,所以设备就可以有更多活跃的线程 束。其原因可能是第二种情况与第一种情况相比有更高的可实现占用率和更好的性能。
·第四种情况有最高的可实现占用率,但它不是最快的,因此,更高的占用率并不一 定意味着有更高的性能。肯定有其他因素限制GPU的性能。
用gld_throughput指标检查内 核的内存读取效率
更高的加载吞吐量并不一定意味着更高的性能。
用gld_efficiency指标检测全局加载效率(不懂),即被请求的全局加载吞吐量占所需的全局加载吞吐量的比值。
**线程块最内层维度的大小对性能起着的关键的作用。**一个块的最内层维数(block.x)应该是线程束大小的倍数,这样能极大地提高了加载效率。
线程块数量和可实现占用率关系:没啥直接关系
**总结:最好的执行配置既不具有最高的可实现占用率,也不具有最高的加载 吞吐量。**从这些实验中可以推断出,没有一个单独的指标能直接优化性能。我们需要在几 个相关的指标间寻找一个恰当的平衡来达到最佳的总体性能。
int recursiveReduce(int *data, int const size){
// terminate check
if(size == 1) return data[0];
// renew the stride
int const stride = size / 2;
// in-place reduction
for(int i = 0; i < stride; i++)
data[i] += data[i + stride];
// call recursiveley
return recursiveReduce(data, stride);
}
在向量中执行满足交换律和结合律的运算,被称为归约问题。并行归约问题是这种运 算的并行执行。
__global__ void reduceNeighbored (int *g_idata, int *g_odata, unsigned int n)
{
// set thread ID
unsigned int tid = threadIdx.x;
unsigned int idx = blockIdx.x * blockDim.x + threadIdx.x;
// convert global data pointer to the local pointer of this block
int *idata = g_idata + blockIdx.x * blockDim.x;
// boundary check
if (idx >= n) return;
// in-place reduction in global memory
for (int stride = 1; stride < blockDim.x; stride *= 2)
{
if ((tid % (2 * stride)) == 0)
{
idata[tid] += idata[tid + stride];
}
// synchronize within threadblock
__syncthreads();
}
// write result for this block to global mem
if (tid == 0) g_odata[blockIdx.x] = idata[0];
}
因为线程块间无法同步,所以每个线程块产生的部分和被复制回了主机,并且在那儿进行串行求和
if((tid % (2 * stride)) == 0)
会导致很高的线程束分化。在并行归 约的第一次迭代中,只有ID为偶数的线程执行这个条件语句的主体,但是所有的线程都必 须被调度。
改进:
__global__ void reduceNeighboredLess (int *g_idata, int *g_odata,
unsigned int n)
{
// set thread ID
unsigned int tid = threadIdx.x;
unsigned int idx = blockIdx.x * blockDim.x + threadIdx.x;
// convert global data pointer to the local pointer of this block
int *idata = g_idata + blockIdx.x * blockDim.x;
// boundary check
if(idx >= n) return;
// in-place reduction in global memory
for (int stride = 1; stride < blockDim.x; stride *= 2)
{
// convert tid into local array index
int index = 2 * stride * tid;
if (index < blockDim.x)
{
idata[index] += idata[index + stride];
}
// synchronize within threadblock
__syncthreads();
}
// write result for this block to global mem
if (tid == 0) g_odata[blockIdx.x] = idata[0];
}
用inst_per_warp指标来 查看每个线程束上执行指令数量的平均值
nvprof --metrics inst_per_warp ./reduceInteger
用gld_throughput指标来查看内存加载吞吐量:
nvprof --metrics gid_throughput ./reduceInteger
__global__ void reduceInterleaved (int *g_idata, int *g_odata, unsigned int n)
{
// set thread ID
unsigned int tid = threadIdx.x;
unsigned int idx = blockIdx.x * blockDim.x + threadIdx.x;
// convert global data pointer to the local pointer of this block
int *idata = g_idata + blockIdx.x * blockDim.x;
// boundary check
if(idx >= n) return;
// in-place reduction in global memory
for (int stride = blockDim.x / 2; stride > 0; stride >>= 1)
{
if (tid < stride)
{
idata[tid] += idata[tid + stride];
}
__syncthreads();
}
// write result for this block to global mem
if (tid == 0) g_odata[blockIdx.x] = idata[0];
}
reduceInterleaved函数和reduceNeigh- boredLess函数维持相同的线程束分化。
循环展开是一个尝试通过减少分支出现的频率和循环维护指令来优化循环的技术。循环体的复制数量被称为循环展开因子,迭代次数就变为了原始循环迭代次数除以循环展开因子
__global__ void reduceUnrolling2 (int *g_idata, int *g_odata, unsigned int n)
{
// set thread ID
unsigned int tid = threadIdx.x;
unsigned int idx = blockIdx.x * blockDim.x * 2 + threadIdx.x;
// convert global data pointer to the local pointer of this block
int *idata = g_idata + blockIdx.x * blockDim.x * 2;
// unrolling 2
if (idx + blockDim.x < n) g_idata[idx] += g_idata[idx + blockDim.x];
__syncthreads();
// in-place reduction in global memory
for (int stride = blockDim.x / 2; stride > 0; stride >>= 1)
{
if (tid < stride)
{
idata[tid] += idata[tid + stride];
}
// synchronize within threadblock
__syncthreads();
}
// write result for this block to global mem
if (tid == 0) g_odata[blockIdx.x] = idata[0];
}
每个线程都添加一个来自于相邻数据块的元素,为现在每个线程块处理两个数据块。
if (idx + blockDim.x < n) g_idata[idx] += g_idata[idx + blockDim.x];
只剩下32个或更少线程(即一个线程束)的情况,因为线程束的执 行是SIMT(单指令多线程)的,每条指令之后有隐式的线程束内同步过程。因此,归约 循环的最后6个迭代可以用下述语句来展开:
__global__ void reduceUnrollWarps8 (int *g_idata, int *g_odata, unsigned int n)
{
// set thread ID
unsigned int tid = threadIdx.x;
unsigned int idx = blockIdx.x * blockDim.x * 8 + threadIdx.x;
// convert global data pointer to the local pointer of this block
int *idata = g_idata + blockIdx.x * blockDim.x * 8;
// unrolling 8
if (idx + 7 * blockDim.x < n)
{
int a1 = g_idata[idx];
int a2 = g_idata[idx + blockDim.x];
int a3 = g_idata[idx + 2 * blockDim.x];
int a4 = g_idata[idx + 3 * blockDim.x];
int b1 = g_idata[idx + 4 * blockDim.x];
int b2 = g_idata[idx + 5 * blockDim.x];
int b3 = g_idata[idx + 6 * blockDim.x];
int b4 = g_idata[idx + 7 * blockDim.x];
g_idata[idx] = a1 + a2 + a3 + a4 + b1 + b2 + b3 + b4;
}
__syncthreads();
// in-place reduction in global memory
for (int stride = blockDim.x / 2; stride > 32; stride >>= 1)
{
if (tid < stride)
{
idata[tid] += idata[tid + stride];
}
// synchronize within threadblock
__syncthreads();
}
// unrolling warp
if (tid < 32)
{
volatile int *vmem = idata;
vmem[tid] += vmem[tid + 32];
vmem[tid] += vmem[tid + 16];
vmem[tid] += vmem[tid + 8];
vmem[tid] += vmem[tid + 4];
vmem[tid] += vmem[tid + 2];
vmem[tid] += vmem[tid + 1];
}
// write result for this block to global mem
if (tid == 0) g_odata[blockIdx.x] = idata[0];
}
意变量vmem是和volatile修饰符一起被声明的,它告诉编译器每次赋值时必须将 vmem[tid]的值存回全局内存中。如果省略了volatile修饰符,这段代码将不能正常工作, 因为编译器或缓存可能对全局或共享内存优化读写。如果位于全局或共享内存中的变量有 volatile修饰符,编译器会假定其值可以被其他线程在任何时间修改或使用。因此,任何 参考volatile修饰符的变量强制直接读或写内存,而不是简单地读写缓存或寄存器。
如果编译时已知一个循环中的迭代次数,就可以把循环完全展开
__global__ void reduceCompleteUnrollWarps8 (int *g_idata, int *g_odata,
unsigned int n)
{
// set thread ID
unsigned int tid = threadIdx.x;
unsigned int idx = blockIdx.x * blockDim.x * 8 + threadIdx.x;
// convert global data pointer to the local pointer of this block
int *idata = g_idata + blockIdx.x * blockDim.x * 8;
// unrolling 8
if (idx + 7 * blockDim.x < n)
{
int a1 = g_idata[idx];
int a2 = g_idata[idx + blockDim.x];
int a3 = g_idata[idx + 2 * blockDim.x];
int a4 = g_idata[idx + 3 * blockDim.x];
int b1 = g_idata[idx + 4 * blockDim.x];
int b2 = g_idata[idx + 5 * blockDim.x];
int b3 = g_idata[idx + 6 * blockDim.x];
int b4 = g_idata[idx + 7 * blockDim.x];
g_idata[idx] = a1 + a2 + a3 + a4 + b1 + b2 + b3 + b4;
}
__syncthreads();
// in-place reduction and complete unroll
if (blockDim.x >= 1024 && tid < 512) idata[tid] += idata[tid + 512];
__syncthreads();
if (blockDim.x >= 512 && tid < 256) idata[tid] += idata[tid + 256];
__syncthreads();
if (blockDim.x >= 256 && tid < 128) idata[tid] += idata[tid + 128];
__syncthreads();
if (blockDim.x >= 128 && tid < 64) idata[tid] += idata[tid + 64];
__syncthreads();
// unrolling warp
if (tid < 32)
{
volatile int *vsmem = idata;
vsmem[tid] += vsmem[tid + 32];
vsmem[tid] += vsmem[tid + 16];
vsmem[tid] += vsmem[tid + 8];
vsmem[tid] += vsmem[tid + 4];
vsmem[tid] += vsmem[tid + 2];
vsmem[tid] += vsmem[tid + 1];
}
// write result for this block to global mem
if (tid == 0) g_odata[blockIdx.x] = idata[0];
}
使用模板函数有助于进一步减少分支消耗,可以指定块的大小作为模板函数的参数。
相比reduceCompleteUnrollWarps8,唯一的区别是使用了模板参数替换了块大小。检查 块大小的if语句将在编译时被评估,如果这一条件为false,那么编译时它将会被删除,使得内循环更有效率。
template <unsigned int iBlockSize>
__global__ void reduceCompleteUnroll(int *g_idata, int *g_odata, unsigned int n)
{
// set thread ID
unsigned int tid = threadIdx.x;
unsigned int idx = blockIdx.x * blockDim.x * 8 + threadIdx.x;
// convert global data pointer to the local pointer of this block
int *idata = g_idata + blockIdx.x * blockDim.x * 8;
// unrolling 8
if (idx + 7 * blockDim.x < n)
{
int a1 = g_idata[idx];
int a2 = g_idata[idx + blockDim.x];
int a3 = g_idata[idx + 2 * blockDim.x];
int a4 = g_idata[idx + 3 * blockDim.x];
int b1 = g_idata[idx + 4 * blockDim.x];
int b2 = g_idata[idx + 5 * blockDim.x];
int b3 = g_idata[idx + 6 * blockDim.x];
int b4 = g_idata[idx + 7 * blockDim.x];
g_idata[idx] = a1 + a2 + a3 + a4 + b1 + b2 + b3 + b4;
}
__syncthreads();
// in-place reduction and complete unroll
if (iBlockSize >= 1024 && tid < 512) idata[tid] += idata[tid + 512];
__syncthreads();
if (iBlockSize >= 512 && tid < 256) idata[tid] += idata[tid + 256];
__syncthreads();
if (iBlockSize >= 256 && tid < 128) idata[tid] += idata[tid + 128];
__syncthreads();
if (iBlockSize >= 128 && tid < 64) idata[tid] += idata[tid + 64];
__syncthreads();
// unrolling warp
if (tid < 32)
{
volatile int *vsmem = idata;
vsmem[tid] += vsmem[tid + 32];
vsmem[tid] += vsmem[tid + 16];
vsmem[tid] += vsmem[tid + 8];
vsmem[tid] += vsmem[tid + 4];
vsmem[tid] += vsmem[tid + 2];
vsmem[tid] += vsmem[tid + 1];
}
// write result for this block to global mem
if (tid == 0) g_odata[blockIdx.x] = idata[0];
}
CUDA的动态并行允许在GPU端直接创建和同步新的GPU内核。
有了动态并行,可以推迟到运行时决定需要在GPU上创建多少个块和网格,可以动态地利用GPU硬件调度器和加载平衡器,并进行调整以适应数据驱动或工作负载。
子网格必须在父线 程、父线程块或父网格完成之前完成。只有在所有的子网格都完成之后,父母才会完成。
主机线程配置和启动父网格,父网格配置 和启动子网格。
设备线程中的网格启动,在线程块间是可见的。在线程块中,只有当所有线程创建的所 有子网格完成之后,线程块的执行才会完成。
当父母启动一个子网格,父线程块与孩子显式同步之后,孩子才能开始执行。
父网格和子网格共享相同的全局和常量内存存储,但它们有不同的局部内存和共享内 存。
__global__ void nestedHelloWorld(int const iSize, int iDepth)
{
int tid = threadIdx.x;
printf("Recursion=%d: Hello World from thread %d block %d\n", iDepth, tid,
blockIdx.x);
// condition to stop recursive execution
if (iSize == 1) return;
// reduce block size to half
int nthreads = iSize >> 1;
// thread 0 launches child grid recursively
if(tid == 0 && nthreads > 0)
{
nestedHelloWorld<<<1, nthreads>>>(nthreads, ++iDepth);
printf("-------> nested execution depth: %d\n", iDepth);
}
}
int main(int argc, char **argv)
{
int size = 8;
int blocksize = 8; // initial block size
int igrid = 1;
if(argc > 1)
{
igrid = atoi(argv[1]);
size = igrid * blocksize;
}
dim3 block (blocksize, 1);
dim3 grid ((size + block.x - 1) / block.x, 1);
printf("%s Execution Configuration: grid %d block %d\n", argv[0], grid.x,
block.x);
nestedHelloWorld<<<grid, block>>>(block.x, 0);
CHECK(cudaGetLastError());
CHECK(cudaDeviceReset());
return 0;
}
累了,毁灭吧
__global__ void gpuRecursiveReduce (int *g_idata, int *g_odata,
unsigned int isize)
{
// set thread ID
unsigned int tid = threadIdx.x;
// convert global data pointer to the local pointer of this block
int *idata = g_idata + blockIdx.x * blockDim.x;
int *odata = &g_odata[blockIdx.x];
// stop condition
if (isize == 2 && tid == 0)
{
g_odata[blockIdx.x] = idata[0] + idata[1];
return;
}
// nested invocation
int istride = isize >> 1;
if(istride > 1 && tid < istride)
{
// in place reduction
idata[tid] += idata[tid + istride];
}
// sync at block level
__syncthreads();
// nested invocation to generate child grids
if(tid == 0)
{
gpuRecursiveReduce<<<1, istride>>>(idata, odata, istride);
// sync all child grids launched in this block
cudaDeviceSynchronize();
}
// sync at block level again
__syncthreads();
}
特点:慢
当一个子网格被调用后,它看到的内存与父线程是完全一样的。因为每一个子线程只 需要父线程的数值来指导部分归约,所以在每个子网格启动前执行线程块内部的同步是没 有必要的。所以可以去除所有同步操作,但还是很慢
需要考虑如何减少由大量的子网格启动引起的消耗。
__global__ void gpuRecursiveReduce2(int *g_idata, int *g_odata, int iStride,
int const iDim)
{
// convert global data pointer to the local pointer of this block
int *idata = g_idata + blockIdx.x * iDim;
// stop condition
if (iStride == 1 && threadIdx.x == 0)
{
g_odata[blockIdx.x] = idata[0] + idata[1];
return;
}
// in place reduction
idata[threadIdx.x] += idata[threadIdx.x + iStride];
// nested invocation to generate child grids
if(threadIdx.x == 0 && blockIdx.x == 0)
{
gpuRecursiveReduce2<<<gridDim.x, iStride / 2>>>(g_idata, g_odata,
iStride / 2, iDim);
}
}