《GPU高性能编程CUDA实战》第五章 线程并行
▶ 本章介绍了线程并行,并给出四个例子。长向量加法、波纹效果、点积和显示位图。
● 长向量加法(线程块并行 + 线程并行)
■ 有三个地方和上一章的单线程块并行不同,分别是 tid = threadIdx.x + blockIdx.x * blockDim.x; ; tid += blockDim.x * gridDim.x; ;以及 add <<< , >>> (dev_a, dev_b, dev_c); 。
■ 同时使用线程块并行和线程并行,一次访问的下标范围是 gridDim.x(线程块范围) * blockDim.x(线程范围),因此使用 tid += blockDim.x * gridDim.x; 跳到下一次访问的对应位置上去。
#include <stdio.h>
#include "cuda_runtime.h"
#include "device_launch_parameters.h"
#include "D:\Code\CUDA\book\common\book.h" #define N (33 * 1024) __global__ void add(int *a, int *b, int *c)
{
int tid = threadIdx.x + blockIdx.x * blockDim.x;// 与单线程块并行不同
while (tid < N)
{
c[tid] = a[tid] + b[tid];
tid += blockDim.x * gridDim.x;// 与单线程块并行不同
}
return;
} int main(void)
{
int *a, *b, *c;
int *dev_a, *dev_b, *dev_c; // 申请内存和显存
a = (int*)malloc(N * sizeof(int));
b = (int*)malloc(N * sizeof(int));
c = (int*)malloc(N * sizeof(int));
cudaMalloc((void**)&dev_a, N * sizeof(int));
cudaMalloc((void**)&dev_b, N * sizeof(int));
cudaMalloc((void**)&dev_c, N * sizeof(int)); // 数组填充
for (int i = ; i < N; i++)
{
a[i] = i;
b[i] = * i;
} // 将内存中的a和b拷贝给显存中的dev_a和dev_b
cudaMemcpy(dev_a, a, N * sizeof(int), cudaMemcpyHostToDevice);
cudaMemcpy(dev_b, b, N * sizeof(int), cudaMemcpyHostToDevice); // 调用核函数
add <<< , >>> (dev_a, dev_b, dev_c);// 与单线程块并行不同 // 将显存中的dev_c从显存拷贝回内存中的c
cudaMemcpy(c, dev_c, N * sizeof(int), cudaMemcpyDeviceToHost); // 检验结果
bool success = true;
for (int i = ; i < N; i++)
{
if ((a[i] + b[i]) != c[i])
{
printf("Error at i==%d:\n\t%d + %d != %d\n", i, a[i], b[i], c[i]);
success = false;
break;
}
}
if (success)
printf("We did it!\n"); // 释放内存和显存
free(a);
free(b);
free(c);
cudaFree(dev_a);
cudaFree(dev_b);
cudaFree(dev_c); getchar();
return ;
}
● 波纹效果
■ 二维的坐标映射,将blockId.x,threadIdx.x,blockId.y,threadIdx.y映射到相应的下标上去,经常用得到。
■ 大部分技术封装到了bitmap.anim_and_exit()(接受两个函数指针,生成动画和清理显存),没有太多值得讨论的内容。
#include <stdio.h>
#include "cuda_runtime.h"
#include "device_launch_parameters.h"
#include "D:\Code\CUDA\book\common\book.h"
#include "D:\Code\CUDA\book\common\cpu_anim.h" #define DIM 1024
#define PI 3.1415926535897932f struct DataBlock {
unsigned char *dev_bitmap;
CPUAnimBitmap *bitmap;
}; __global__ void kernel(unsigned char *ptr, int ticks)//计算帧图像中每一点的灰度值
{
//标准的坐标映射
int x = threadIdx.x + blockIdx.x * blockDim.x;
int y = threadIdx.y + blockIdx.y * blockDim.y;
int offset = x + y * blockDim.x * gridDim.x; float fx = x - DIM / ;
float fy = y - DIM / ;
float d = sqrtf(fx * fx + fy * fy);
unsigned char grey = (unsigned char)(128.0f + 127.0f *cos(d / 10.0f - ticks / 7.0f) / (d / 10.0f + 1.0f));
ptr[offset * + ] = grey;
ptr[offset * + ] = grey;
ptr[offset * + ] = grey;
ptr[offset * + ] = ; return;
} void generate_frame(DataBlock *d, int ticks)//生成一帧图像
{
dim3 blocks(DIM / , DIM / );
dim3 threads(, );
kernel << <blocks, threads >> >(d->dev_bitmap, ticks); cudaMemcpy(d->bitmap->get_ptr(), d->dev_bitmap, d->bitmap->image_size(), cudaMemcpyDeviceToHost)); return;
} void cleanup(DataBlock *d)//释放显存
{
cudaFree(d->dev_bitmap);
} int main(void)
{
DataBlock data;
CPUAnimBitmap bitmap(DIM, DIM, &data);
data.bitmap = &bitmap; cudaMalloc((void**)&data.dev_bitmap, bitmap.image_size()); bitmap.anim_and_exit((void(*)(void*, int))generate_frame, (void(*)(void*))cleanup); getchar();
return ;
}
■ 程序输出,动态效果,从中间向四周扩散的波动。

● 点积(使用共享内存)
■ 在考虑线程块大小的时候经常用到向上取整,这里使用了技巧 ceil( a / b ) == floor( (a-1) / b) + 1
■ 算法总体想法是在GPU中将很长的向量分段放入GPU的各线程块中,每个线程块利用共享内存和多线程分别计算乘法和加法。结果整理为每个线程块输出一个浮点数,置于全局内存中,这样就将待计算的元素数量降到了 gridDim.x 的水平,再返回CPU中完成剩下的加法。
■ 算法预先规定了每个线程块使用256个线程(blockDim.x == 256),那么使用的线程块数量应该满足 gridDim.x * blockDim.x ≥ N(待计算的向量长度),另外代码中规定线程块数量至少为32(?书中说“选择其他的只可能产生更高或更差的性能,这取决于CPU和GPU的相对速度”)
■ 在核函数中使用了既定大小的共享内存 __shared__ float cache[threadsPerBlock]; ,并采用 __syncthreads(); 函数进行线程同步(因为接下来要进行规约运算,前提就是该线程块内所有的线程已经独立计算完毕)。
#include <stdio.h>
#include "cuda_runtime.h"
#include "device_launch_parameters.h"
#include "D:\Code\CUDA\book\common\book.h" #define imin(a,b) (a<b?a:b)
#define sum_squares(x) (x*(x+1)*(2*x+1)/6)//平方和计算式 const int N = * ;
const int threadsPerBlock = ;
const int blocksPerGrid = imin(, (N + threadsPerBlock - ) / threadsPerBlock); __global__ void dot(float *a, float *b, float *c)
{
__shared__ float cache[threadsPerBlock];
int tid = threadIdx.x + blockIdx.x * blockDim.x;
int cacheIndex = threadIdx.x; float temp = 0.0f;
while (tid < N)
{
temp += a[tid] * b[tid];
tid += blockDim.x * gridDim.x;
} cache[cacheIndex] = temp;//局地内存转入共享内存 __syncthreads();//线程同步 int i = blockDim.x / ;//二分规约,要求每个线程块的线程数必须是2^k形式
while (i != )
{
if (cacheIndex < i)
cache[cacheIndex] += cache[cacheIndex + i];
__syncthreads();
i /= ;
} if (cacheIndex == )//每个线程块的0号线程将,将计算结果从共享内存转入全局内存
c[blockIdx.x] = cache[]; return;
} int main(void)
{
int i;
float *a, *b, c, *partial_c;
float *dev_a, *dev_b, *dev_partial_c; a = (float*)malloc(N * sizeof(float));
b = (float*)malloc(N * sizeof(float));
partial_c = (float*)malloc(blocksPerGrid * sizeof(float));
cudaMalloc((void**)&dev_a, N * sizeof(float));
cudaMalloc((void**)&dev_b, N * sizeof(float));
cudaMalloc((void**)&dev_partial_c, blocksPerGrid * sizeof(float)); for (i = ; i < N; i++)
{
a[i] = i;
b[i] = * i;
} cudaMemcpy(dev_a, a, N * sizeof(float), cudaMemcpyHostToDevice);
cudaMemcpy(dev_b, b, N * sizeof(float), cudaMemcpyHostToDevice); dot <<< blocksPerGrid, threadsPerBlock >>> (dev_a, dev_b, dev_partial_c); cudaMemcpy(partial_c, dev_partial_c,blocksPerGrid * sizeof(float),cudaMemcpyDeviceToHost); //结果在CPU中汇总
for (i = , c = 0.0f; i < blocksPerGrid; c += partial_c[i], i++);
printf("\n\tAnswer:\t\t%.6g\n\tGPU value:\t%.6g\n", * sum_squares((float)(N - )), c); free(a);
free(b);
free(partial_c);
cudaFree(dev_a);
cudaFree(dev_b);
cudaFree(dev_partial_c); getchar();
return;
}
■ 错误的优化,想法是“只等待那些需要写入的线程来进行同步”,但是会导致有的线程无法抵达 __syncthreads() 函数而使程序停止响应。
while (i != )
{
if (cacheIndex < i)
{
cache[cacheIndex] += cache[cacheIndex + i];
__syncthreads();
}
i /= ;
}
■ 正确同步的输出(左图)与不正确同步的输出(右图),共享内存中是否同步对程序结果的影响

■ 有趣的改动,将核函数染色部分的代码改为 ptr[offset * + ] = shared[threadIdx.x][threadIdx.y]; (其他部分都不变),得到如下左图的圆形图案。特别的,如果只改 threadId.x 不改 15-threadIdx.y,得到水平方向上渐变,竖直方向上离散的右图效果。

《GPU高性能编程CUDA实战》第五章 线程并行的更多相关文章
- 《GPU高性能编程CUDA实战》第九章 原子性
▶ 本章介绍了原子操作,给出了基于原子操作的直方图计算的例子. ● 章节代码 #include <stdio.h> #include "cuda_runtime.h" ...
- [问题解决]《GPU高性能编程CUDA实战》中第4章Julia实例“显示器驱动已停止响应,并且已恢复”问题的解决方法
以下问题的出现及解决都基于"WIN7+CUDA7.5". 问题描述:当我编译运行<GPU高性能编程CUDA实战>中第4章所给Julia实例代码时,出现了显示器闪动的现象 ...
- 《GPU高性能编程CUDA实战》附录一 高级原子操作
▶ 本章介绍了手动实现原子操作.重构了第五章向量点积的过程.核心是通过定义结构Lock及其运算,实现锁定,读写,解锁的过程. ● 章节代码 #include <stdio.h> #incl ...
- 《GPU高性能编程CUDA实战》第十一章 多GPU系统的CUDA C
▶ 本章介绍了多设备胸膛下的 CUDA 编程,以及一些特殊存储类型对计算速度的影响 ● 显存和零拷贝内存的拷贝与计算对比 #include <stdio.h> #include " ...
- 《GPU高性能编程CUDA实战》第四章 简单的线程块并行
▶ 本章介绍了线程块并行,并给出两个例子:长向量加法和绘制julia集. ● 长向量加法,中规中矩的GPU加法,包含申请内存和显存,赋值,显存传入,计算,显存传出,处理结果,清理内存和显存.用到了 t ...
- 《GPU高性能编程CUDA实战》第七章 纹理内存
▶ 本章介绍了纹理内存的使用,并给出了热传导的两个个例子.分别使用了一维和二维纹理单元. ● 热传导(使用一维纹理) #include <stdio.h> #include "c ...
- 《GPU高性能编程CUDA实战》第六章 常量内存
▶ 本章介绍了常量内存的使用,并给光线追踪的一个例子.介绍了结构cudaEvent_t及其在计时方面的使用. ● 章节代码,大意是有SPHERES个球分布在原点附近,其球心坐标在每个坐标轴方向上分量绝 ...
- 《GPU高性能编程CUDA实战》第三章 CUDA设备相关
▶ 这章介绍了与CUDA设备相关的参数,并给出了了若干用于查询参数的函数. ● 代码(已合并) #include <stdio.h> #include "cuda_runtime ...
- 《GPU高性能编程CUDA实战中文》中第四章的julia实验
在整个过程中出现了各种问题,我先将我调试好的真个项目打包,提供下载. /* * Copyright 1993-2010 NVIDIA Corporation. All rights reserved. ...
随机推荐
- Vistual Studio XML 智能提示
开发中经常遇到要和各种各样的 XML 打交道,编辑 XML 文件时最头痛的便是要记住许多 XML 元素名称.属性名称. 幸运的是,Vistual Studio 的 XML 智能提示功能可以大大地减轻这 ...
- 关于Zookeeper选举机制
zookeeper集群 配置多个实例共同构成一个集群对外提供服务以达到水平扩展的目的,每个服务器上的数据是相同的,每一个服务器均可以对外提供读和写的服务,这点和redis是相同的,即对客户端来讲每个服 ...
- elasticsearch 5.1 别的机器无法访问9200端口
版权声明:作者:jiankunking 出处:http://blog.csdn.net/jiankunking 本文版权归作者和CSDN共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显 ...
- gcc编译工具生成动态库和静态库之一----介绍
1.库的分类 根据链接时期的不同,库又有静态库和动态库之分. 静态库是在链接阶段被链接的(好像是废话,但事实就是这样),所以生成的可执行文件就不受库的影响了,即使库被删除了,程序依然可以成功运行. ...
- 【模态窗口-Modeldialog】提交请求时禁止在新窗口打开页面的处理方法
在使用Window.ShowModalDialog()打开模态窗口后,在模态窗口内提交时总是会在新窗口中打开. 解决办法: 在要弹出的窗口的<head>之间加: <base targ ...
- 峰Redis学习(3)Redis 数据结构(字符串、哈希)
第一节:Redis 数据类型介绍 五种数据类型: 字符串(String) 字符串列表(list) 有序字符串集合(sorted set) 哈希(hash) 字符串集合(set) 第二节:Redis ...
- C++之单例类模板
单例类模板:一个类只能有一个对象,比如超市收银系统中清点物品的仪器只有一个 设计思路: 1.构造函数,拷贝构造函数必须设计为private,防止自己生成新的对象 2.且类的指针要设计为static类型 ...
- js多次触发事件,在一定延迟内只执行一次 (事件累加)
js多次触发事件,在一定延迟内只执行一次的案例: <!DOCTYPE html> <html> <head> <meta charset="UTF- ...
- 为什么QQ空间和QQ邮箱都是IE默认打开?
为什么QQ空间和QQ邮箱都是IE默认打开? 我已经设置成chrome为默认浏览器了 原文转载至:https://zhidao.baidu.com/question/390662851068217285 ...
- 00013 - top命令详解
top命令是Linux下常用的性能分析工具,能够实时显示系统中各个进程的资源占用状况,类似于Windows的任务管理器. top显示系统当前的进程和其他状况,是一个动态显示过程,即可以通过用户按键来不 ...