▶ 本章介绍了多设备胸膛下的 CUDA 编程,以及一些特殊存储类型对计算速度的影响

● 显存和零拷贝内存的拷贝与计算对比

 #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 SIZE (33 * 1024 * 1024) const int threadsPerBlock = ;
const int blocksPerGrid = imin(, (SIZE + threadsPerBlock - ) / threadsPerBlock); __global__ void dot(int size, float *a, float *b, float *c)//分段计算点积写入全局内存
{
__shared__ float share[threadsPerBlock];
int tid = threadIdx.x + blockIdx.x * blockDim.x;
int cacheIndex = threadIdx.x; float temp = ;
while (tid < size)
{
temp += a[tid] * b[tid];
tid += blockDim.x * gridDim.x;
} share[cacheIndex] = temp;
__syncthreads(); int i = blockDim.x / ;
while (i != )
{
if (cacheIndex < i)
share[cacheIndex] += share[cacheIndex + i];
__syncthreads();
i /= ;
}
if (cacheIndex == )
c[blockIdx.x] = share[]; return;
} void malloc_test()// 利用显存进行计算
{
cudaEvent_t start, stop;
float *a, *b, *partial_c, c;
float *dev_a, *dev_b, *dev_partial_c;
float elapsedTime; cudaEventCreate(&start);
cudaEventCreate(&stop); a = (float*)malloc(SIZE * sizeof(float));
b = (float*)malloc(SIZE * sizeof(float));
partial_c = (float*)malloc(blocksPerGrid * sizeof(float));
cudaMalloc((void**)&dev_a,SIZE * sizeof(float));
cudaMalloc((void**)&dev_b,SIZE * sizeof(float));
cudaMalloc((void**)&dev_partial_c,blocksPerGrid * sizeof(float)); for (int i = ; i<SIZE; i++)
{
a[i] = i;
b[i] = i * ;
} cudaEventRecord(start, ); cudaMemcpy(dev_a, a, SIZE * sizeof(float),cudaMemcpyHostToDevice);
cudaMemcpy(dev_b, b, SIZE * sizeof(float),cudaMemcpyHostToDevice); dot << <blocksPerGrid, threadsPerBlock >> >(SIZE, dev_a, dev_b,dev_partial_c); cudaMemcpy(partial_c, dev_partial_c,blocksPerGrid * sizeof(float),cudaMemcpyDeviceToHost); cudaEventRecord(stop, );
cudaEventSynchronize(stop);
cudaEventElapsedTime(&elapsedTime,start, stop); c = ;
for (int i = ; i < blocksPerGrid; c += partial_c[i], i++); free(a);
free(b);
free(partial_c);
cudaFree(dev_a);
cudaFree(dev_b);
cudaFree(dev_partial_c);
cudaEventDestroy(start);
cudaEventDestroy(stop); printf("cudaMalloc, time:\t%3.1f ms,value:\t%f\n", elapsedTime, c);
return;
} void cuda_host_alloc_test()// 利用零拷贝内存进行计算
{
cudaEvent_t start, stop;
float *a, *b, *partial_c, c;
float *dev_a, *dev_b, *dev_partial_c;
float elapsedTime; cudaEventCreate(&start);
cudaEventCreate(&stop); cudaHostAlloc((void**)&a,SIZE * sizeof(float),cudaHostAllocWriteCombined |cudaHostAllocMapped);
cudaHostAlloc((void**)&b,SIZE * sizeof(float),cudaHostAllocWriteCombined |cudaHostAllocMapped);
cudaHostAlloc((void**)&partial_c,blocksPerGrid * sizeof(float),cudaHostAllocMapped);
cudaHostGetDevicePointer(&dev_a, a, );
cudaHostGetDevicePointer(&dev_b, b, );
cudaHostGetDevicePointer(&dev_partial_c,partial_c, ); for (int i = ; i < SIZE; i++)
{
a[i] = i;
b[i] = i * ;
} cudaEventRecord(start, ); dot << <blocksPerGrid, threadsPerBlock >> > (SIZE, dev_a, dev_b, dev_partial_c);
cudaThreadSynchronize(); cudaEventRecord(stop, );
cudaEventSynchronize(stop);
cudaEventElapsedTime(&elapsedTime,start, stop); c = ;
for (int i = ; i < blocksPerGrid; c += partial_c[i], i++); cudaFreeHost(a);
cudaFreeHost(b);
cudaFreeHost(partial_c);
cudaEventDestroy(start);
cudaEventDestroy(stop); printf("cudaHostAlloc, time:\t%3.1f ms,value:\t%f\n", elapsedTime, c);
return;
} int main(void)
{
cudaSetDeviceFlags(cudaDeviceMapHost); malloc_test();
cuda_host_alloc_test(); getchar();
return;
}

● 程序输出结果,第一行是利用显存进行计算的结果,第二行是利用零拷贝内存进行计算的结果。

● 零拷贝内存的使用过程:

 float *a, *dev_a;
cudaSetDeviceFlags(cudaDeviceMapHost);// 设置内存标志,表明希望映射主机内存
cudaHostAlloc((void**)&a,SIZE * sizeof(float),cudaHostAllocWriteCombined |cudaHostAllocMapped);
//标记flag,分别是“合并式写入”和“GPU可访问”
cudaHostGetDevicePointer(&dev_a, a, );// 将内存地址映射到GPU上,以后就可以使用dev_a[n]等下标方式访问dev_a foo << <blocksize, threadsize >> > (dev_a); cudaThreadSynchronize();
cudaFreeHost(a);//释放内存

● 合并式写入不会改变应用程序的性能,却可以显著提升GPU读取内存的性能。但是如果CPU也需要读取这部分的内存时,效率较低.

●使用零拷贝内存见减小了PCIE通道的读写延迟,提高了数据访问速率。但是GPU不会缓存零拷贝内存的内容,对于需要多次读写的内存数据效率较低,不如直接在显存中存储。本例子中点积运算满足数据只访问一次的条件,所以效率提高很明显。

● 多设备并行计算(被改成了双线程)

 #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 SIZE (33*1024*1024)
#define THREADSIZE (256)
#define BLOCKSIZE (imin(32, (SIZE / 2 + THREADSIZE - 1) / THREADSIZE)) struct DataStruct
{
int deviceID;
int size;
float *a;
float *b;
float returnValue;
}; __global__ void dot(int size, float *a, float *b, float *c)//分段计算点积写入全局内存
{
__shared__ float share[THREADSIZE];
int tid = threadIdx.x + blockIdx.x * blockDim.x;
int cacheIndex = threadIdx.x; float temp = ;
while (tid < size)
{
temp += a[tid] * b[tid];
tid += blockDim.x * gridDim.x;
} share[cacheIndex] = temp;
__syncthreads(); int i = blockDim.x / ;
while (i != )
{
if (cacheIndex < i)
share[cacheIndex] += share[cacheIndex + i];
__syncthreads();
i /= ;
}
if (cacheIndex == )
c[blockIdx.x] = share[]; return;
} void* routine(void *pvoidData)// C回调函数标准,void*型函数,传入void型的指针,再转化回原来的的格式
{
DataStruct *data = (DataStruct*)pvoidData;
cudaSetDevice(data->deviceID); int i;
float *partial_c, c;
float *dev_a, *dev_b, *dev_partial_c; partial_c = (float*)malloc(BLOCKSIZE * sizeof(float));
cudaMalloc((void**)&dev_a, data->size * sizeof(float));
cudaMalloc((void**)&dev_b, data->size * sizeof(float));
cudaMalloc((void**)&dev_partial_c, BLOCKSIZE * sizeof(float)); cudaMemcpy(dev_a, data->a, data->size * sizeof(float),cudaMemcpyHostToDevice);
cudaMemcpy(dev_b, data->b, data->size * sizeof(float),cudaMemcpyHostToDevice); dot << <BLOCKSIZE, THREADSIZE >> > (data->size, dev_a, dev_b, dev_partial_c); cudaMemcpy(partial_c, dev_partial_c, BLOCKSIZE * sizeof(float),cudaMemcpyDeviceToHost); for (i = ,c=; i < BLOCKSIZE; c += partial_c[i], i++); free(partial_c);
cudaFree(dev_a);
cudaFree(dev_b);
cudaFree(dev_partial_c); data->returnValue = c;
printf("\n\troutine finished!");
return NULL;
} int main(void)
{
float *a = (float*)malloc(sizeof(float) * SIZE);//公用的输入数组
float *b = (float*)malloc(sizeof(float) * SIZE); for (int i = ; i < SIZE; i++)
{
a[i] = i;
b[i] = i * ;
} DataStruct data[];
data[].deviceID = ;
data[].size = SIZE / ;
data[].a = a;
data[].b = b;
data[].deviceID = ;//源代码中这里等于1,使用第二台设备进行计算
data[].size = SIZE / ;
data[].a = a + SIZE / ;
data[].b = b + SIZE / ; // 使用CreateThread()创建新线程来分配计算
HANDLE thread = CreateThread(NULL, , (PTHREAD_START_ROUTINE)routine, &(data[]), , NULL);
routine(&(data[])); WaitForSingleObject(thread, INFINITE);// 等待和关闭线程
CloseHandle(thread); free(a);
free(b); printf("\n\tValue calculated: %f\n",data[].returnValue + data[].returnValue);
getchar();
return ;
}

● 关于创建和销毁线程:

 HANDLE thread = CreateThread(NULL, , (PTHREAD_START_ROUTINE)routine, &(data[]), , NULL);
WaitForSingleObject(thread, INFINITE);
CloseHandle(thread); static HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpsa,//
DWORD dwStackSize,// 堆栈大小
LPTHREAD_START_ROUTINE pfnThreadProc,// 线程过程
void* pvParam,// 需要传递的线程参数
DWORD dwCreationFlags,// 创建标志,0或CREATE_SUSPENDED
DWORD* pdwThreadId// 接收新线程的线程ID_WORD变量地址
) throw();// 函数不会抛出异常 WaitForSingleObject(thread, INFINITE);
// 第1个参数为线程句柄
// 第2个参数为最小等待时间,可取INFINITE HANDLE list[N];
WaitForMultipleObjects(N, list, , );
// 第1个参数为线程个数
// 第2个参数为句柄列表
// 第3个参数为等待状态,0表示等待所有线程结束再继续,1表示一旦有线程结束或到达等待时间就继续
// 第4个参数为最小等待时间

● 使用可移动内存进行多设备并行计算(被改成了双线程)

 #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 SIZE (33*1024*1024)
#define THREADSIZE (256)
#define BLOCKSIZE (imin(32, (SIZE / 2 + THREADSIZE - 1) / THREADSIZE)) struct DataStruct
{
int deviceID;
int size;
float *a;
float *b;
float returnValue;
}; __global__ void dot(int size, float *a, float *b, float *c)//分段计算点积写入全局内存
{
__shared__ float share[THREADSIZE];
int tid = threadIdx.x + blockIdx.x * blockDim.x;
int cacheIndex = threadIdx.x; float temp = ;
while (tid < size)
{
temp += a[tid] * b[tid];
tid += blockDim.x * gridDim.x;
} share[cacheIndex] = temp;
__syncthreads(); int i = blockDim.x / ;
while (i != )
{
if (cacheIndex < i)
share[cacheIndex] += share[cacheIndex + i];
__syncthreads();
i /= ;
}
if (cacheIndex == )
c[blockIdx.x] = share[]; return;
} void* routine(void *pvoidData)
{
DataStruct *data = (DataStruct*)pvoidData;
cudaSetDevice(data->deviceID);
if (data->deviceID != )// 若使用新设备,则要设置内存地址映射(在我的电脑上这部分不会被运行)
cudaSetDeviceFlags(cudaDeviceMapHost); int i;
float *partial_c, c;
float *dev_a, *dev_b, *dev_partial_c; partial_c = (float*)malloc(BLOCKSIZE * sizeof(float));
cudaHostGetDevicePointer(&dev_a, data->a, );
cudaHostGetDevicePointer(&dev_b, data->b, );
cudaMalloc((void**)&dev_partial_c, BLOCKSIZE * sizeof(float)); dot << <BLOCKSIZE, THREADSIZE >> > (data->size, dev_a, dev_b, dev_partial_c); cudaMemcpy(partial_c, dev_partial_c, BLOCKSIZE * sizeof(float), cudaMemcpyDeviceToHost); for (i = , c = ; i < BLOCKSIZE; c += partial_c[i], i++); free(partial_c);
cudaFree(dev_partial_c); data->returnValue = c;
printf("\n\tRoutine finished!");
return ;
} int main(void)
{
float *a, *b;
cudaSetDevice();
cudaSetDeviceFlags(cudaDeviceMapHost);
cudaHostAlloc((void**)&a, SIZE * sizeof(float),cudaHostAllocWriteCombined |cudaHostAllocPortable |cudaHostAllocMapped);
cudaHostAlloc((void**)&b, SIZE * sizeof(float),cudaHostAllocWriteCombined |cudaHostAllocPortable |cudaHostAllocMapped); for (int i = ; i < SIZE; i++)
{
a[i] = i;
b[i] = i * ;
} DataStruct data[];
data[].deviceID = ;
data[].size = SIZE / ;
data[].a = a;
data[].b = b;
data[].deviceID = ;
data[].size = SIZE / ;
data[].a = a + SIZE / ;
data[].b = b + SIZE / ; HANDLE thread = CreateThread(NULL, , (PTHREAD_START_ROUTINE)routine, &(data[]), , NULL);
routine(&(data[]));
WaitForSingleObject(thread, INFINITE);
CloseHandle(thread); cudaFreeHost(a);
cudaFreeHost(b); printf("\n\tValue calculated: %f\n",data[].returnValue + data[].returnValue);
getchar();
return ;
}

● 可移动内存说明:“固定内存”是针对线程而言的,也就是说除了申请该固定内存的县城以外其他线程不能将其看作固定内存,而是可分页内存。这时需要添加固定内存的“可移动”属性,使得所有线程都将该段内存看做固定内存。

● 可移动内存的使用

 float *a;
cudaSetDeviceFlags(cudaDeviceMapHost);// 设置内存标志,表明希望映射主机内存
cudaHostAlloc((void**)&a, SIZE * sizeof(float), cudaHostAllocWriteCombined | cudaHostAllocPortable | cudaHostAllocMapped);
// 增加标记“可移动内存” cudaSetDeviceFlags(cudaDeviceMapHost);
// 在其他设备中使用这部分内存时需要再次声明内存标志 cudaFreeHost(a);

第十一章 多GPU系统的CUDA C的更多相关文章

  1. [问题解决]《GPU高性能编程CUDA实战》中第4章Julia实例“显示器驱动已停止响应,并且已恢复”问题的解决方法

    以下问题的出现及解决都基于"WIN7+CUDA7.5". 问题描述:当我编译运行<GPU高性能编程CUDA实战>中第4章所给Julia实例代码时,出现了显示器闪动的现象 ...

  2. 《GPU高性能编程CUDA实战》第五章 线程并行

    ▶ 本章介绍了线程并行,并给出四个例子.长向量加法.波纹效果.点积和显示位图. ● 长向量加法(线程块并行 + 线程并行) #include <stdio.h> #include &quo ...

  3. 《GPU高性能编程CUDA实战》第十一章 多GPU系统的CUDA C

    ▶ 本章介绍了多设备胸膛下的 CUDA 编程,以及一些特殊存储类型对计算速度的影响 ● 显存和零拷贝内存的拷贝与计算对比 #include <stdio.h> #include " ...

  4. 《GPU高性能编程CUDA实战》第四章 简单的线程块并行

    ▶ 本章介绍了线程块并行,并给出两个例子:长向量加法和绘制julia集. ● 长向量加法,中规中矩的GPU加法,包含申请内存和显存,赋值,显存传入,计算,显存传出,处理结果,清理内存和显存.用到了 t ...

  5. 《GPU高性能编程CUDA实战》第七章 纹理内存

    ▶ 本章介绍了纹理内存的使用,并给出了热传导的两个个例子.分别使用了一维和二维纹理单元. ● 热传导(使用一维纹理) #include <stdio.h> #include "c ...

  6. 《GPU高性能编程CUDA实战》第六章 常量内存

    ▶ 本章介绍了常量内存的使用,并给光线追踪的一个例子.介绍了结构cudaEvent_t及其在计时方面的使用. ● 章节代码,大意是有SPHERES个球分布在原点附近,其球心坐标在每个坐标轴方向上分量绝 ...

  7. 《GPU高性能编程CUDA实战》第三章 CUDA设备相关

    ▶ 这章介绍了与CUDA设备相关的参数,并给出了了若干用于查询参数的函数. ● 代码(已合并) #include <stdio.h> #include "cuda_runtime ...

  8. 《GPU高性能编程CUDA实战》第九章 原子性

    ▶ 本章介绍了原子操作,给出了基于原子操作的直方图计算的例子. ● 章节代码 #include <stdio.h> #include "cuda_runtime.h" ...

  9. 《GPU高性能编程CUDA实战中文》中第四章的julia实验

    在整个过程中出现了各种问题,我先将我调试好的真个项目打包,提供下载. /* * Copyright 1993-2010 NVIDIA Corporation. All rights reserved. ...

  10. 《GPU高性能编程CUDA实战》附录二 散列表

    ▶ 使用CPU和GPU分别实现散列表 ● CPU方法 #include <stdio.h> #include <time.h> #include "cuda_runt ...

随机推荐

  1. Opengl4.5 中文手册—F

    索引 A      B    C      D     E     F     G H      I     J      K     L     M     N O      P    Q      ...

  2. Highway Networks

    一 .Highway Networks 与 Deep Networks 的关系 深层神经网络相比于浅层神经网络具有更好的效果,在很多方面都已经取得了很好的效果,特别是在图像处理方面已经取得了很大的突破 ...

  3. 第一次安装jshint,jshint新手使用记录

    刚刚出来工作的渣渣,第一次进入这样比较正规的公司,各个开发流程都比较严格,代码也是要经过jshint的检测才能上传到svn才能成功打包项目.所以我这种技术都半桶水的职场开发小白,也是第一次用jshin ...

  4. MySQL+Keepalived配置高可用

    服务器环境: 主mysql:192.168.1.163 从mysql:192.168.1.126 VIP:192.168.1.50 一.mysql配置主从同步 1.配置主mysql服务器 vim /e ...

  5. 开博近一年的感想 by 程序员小白

    /* 好吧,这里的写博客应该理解为更宏观的写文章. */   在去年的这个时候,我所知道的平台只有 CSDN 和博客园..然而 CSDN 的广告实在是不想吐槽了,选择博客园是一件非常自然的事情.要说开 ...

  6. 实例讲解webpack的基本使用第二篇

    这一篇来讲解一下如何设置webpack的配置文件webpack.config.js 我们新建一个webpack-demo的项目文件夹,然后安装webpack 执行如下命令 在项目文件夹下,建一个dis ...

  7. hive自定义UDF

    udf udaf udtf 使用方式 hiverc文件 1.jar包放到安装日录下或者指定目录下 2.${HIVE_HOME}/bin目录下有个.hiverc文件,它是隐藏文件. 3.把初始化语句加载 ...

  8. 从头编写 asp.net core 2.0 web api 基础框架 (2)

    上一篇是: http://www.cnblogs.com/cgzl/p/7637250.html Github源码地址是: https://github.com/solenovex/Building- ...

  9. window开启remote desktop服务

    确定自己的PC支持远程桌面   1 先确定被遥控的电脑的系统必须是Professional或Enterprise以上版本,家庭版不支持远程桌面.以Win8.1(7和8同理)为例,依次打开控制面板→系统 ...

  10. 解决Jqyery的Trigger事件中两个按钮相互触发至死循环问题

    今天做项目,其中有个功能需要两个图表的联动,用到两个按钮,这两个按钮分别控制两个图表,第一次直接在btn1的单击事件中使用了$("btn2").trigger("clic ...