▶ 本章介绍了多设备胸膛下的 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实战》第十一章 多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实战》第四章 简单的线程块并行

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

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

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

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

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

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

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

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

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

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

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

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

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

随机推荐

  1. LG3804 【模板】后缀自动机

    题意 给定一个只包含小写字母的字符串\(S\), 请你求出 \(S\) 的所有出现次数不为 \(1\) 的子串的出现次数乘上该子串长度的最大值. 对于\(100\%\) 的数据,\(|S| \leq ...

  2. stenciljs 学习十一 pwa 支持

    stenciljs 对于pwa 的支持是自动注入的,我们只需要简单的配置,stenciljs使用workbox 配置 默认配置 { skipWaiting: true, clientsClaim: t ...

  3. NoSQLUnit

    NoSQLUnit Core Overview Unit testing is a method by which the smallest testable part of an applicati ...

  4. 使用cglib动态创建javabean

    动态创建javabean对于我们进行开发,会有一定的帮助,一下是使用cglib动态创建javabean的一个例子: 先贴上code: package com.dalong.CreateCode; im ...

  5. css中display为none 和visibility为hidden的区别

    区别一: display:none Turns off the display of an element (it has no effect on layout);  all child eleme ...

  6. 实战:mysql检查物理磁盘中的二进制日志文件是否有丢失

    版权声明:日常札记,乐于分享 https://blog.csdn.net/yangzhawen/article/details/32103555 场景:有时候由于磁盘损坏或人为原因错误删除了磁盘中的二 ...

  7. 海思3518EV200 SDK中获取和保存H.264码流详解

    /****************************************** step 2: Start to get streams of each channel. ********** ...

  8. Anglarjs 工具方法

    Angularjs 常用方法 1. isArray使用 效果图如下,结果为true 2.uppercase使用 $scope.name = "zhangsan"; $scope.n ...

  9. BeanUtils Object 取值赋值

    /** * 将结果集导出为Excel * * @param response * @param fsc * @param columns * @param bizType * @throws Exce ...

  10. vmware :Ubuntu 12.04添加新硬盘

    http://blog.csdn.net/hanpengyu/article/details/7475645 一.VMware新增磁盘的设置步骤 (建议:在设置虚拟的时候,不要运行虚拟机的系统,不然添 ...