《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. ...
随机推荐
- NP完全性理论与近似算法
转自:http://www.cnblogs.com/chinazhangjie/archive/2010/12/06/1898070.html 一.图灵机 根据有限状态控制器的当前状态及每个读写头读到 ...
- Daubechies小波介绍
Daubechies小波是正交.连续且紧支撑的. 正交条件下,$H(\omega)$必须满足下式: $|H(\omega)|^2+|H(\omega + \pi)|^2 =1$ 连续紧支撑条件下,$H ...
- Gerrit安装
1.安装gerrit [sisi@pre-srv44 ~]$ su - gerrit2Password: [gerrit2@pre-srv44 ~]$ lltotal 83872-rw-r--r-- ...
- Maven 专题
目录: Maven的安装 Eclipse安装Maven插件 Nexus私服搭建 Maven+Nexus配置 发布自己的构件(至Nexus) 创建maven多模块工程group 整理中[...] 先放一 ...
- 2、以自定义struct或struct指针作为map的Key
若干问题: struct Node { int k, b; friend bool operator <(Node a, Node b) { return a.k < b.k; } }no ...
- Tomcat中Url中文乱码解决办法
引自:http://thoughtfly.iteye.com/blog/1533481 默认的tomcat容器如果直接使用get方式在url中传中文时,传到后台接收会是乱码. 乱码问题 原因: tom ...
- hierarchical_mutex函数问题(C++ Concurrent in Action)
C++ Concurrent in Action(英文版)书上(No.52-No.53)写的hierarchical_mutex函数,只适合结合std::lock_guard使用,直接使用如果不考虑顺 ...
- Oracle空查询删除
- HDOJ 2003 求绝对值
#include<cstdio> #include<cmath> int main() { double a; while (scanf_s("%lf", ...
- [UE4]AWP开镜时模糊
一.Add to Viewport的Zorder越大,添加进来的UI越靠近前面.也就是大的Zorder会覆盖Zorder小的UI. 二.镜头模糊,在专心UI中添加一个模糊滤镜设置模糊值,并放在最上层.