CUDA:Supercomputing for the Masses (用于大量数据的超级计算)-第三节

原文链接

恭喜!通过对CUDA(Compute Unified DeviceArchitecture,即计算统一设备架构的首字母缩写)系列文章第一节和第二节,您现在已经是能够使用CUDA的程序员了,您可以创建和运行在支持CUDA的设备上使用成百上千同步线程的程序。在第二节的incrementArrays.cu中,我提供了一个常见的CUDA应用程序模式的工作样例——将数据移动到设备,运行一个或多个内核以进行计算并获得结果。本质上,只需使用您自己的内核并加载自己的数据(我在本篇专栏文章的示例中就是这样做的)就可以将incrementArrays.cu变形到任何您需要的应用程序中。以后的专栏文章将介绍CUDA异步I/O和流。

“你知道的越多,就越危险!“是对上一段内容的幽默而又准确的总结。CUDA的好消息就是,它提供了一种自然的方式将您作为程序员的思路转换到大量平行的程序。不过坏消息是, 要让这些程序更“结实”,更为有效,需要更高的理解力。

不要小心翼翼。开始试验吧,动手起来!CUDA提供了创建优秀软件的编程工具和结构。想要真正学会它,就要动手试验。实际上,这些专栏文章通过简短的样例重点介绍CUDA功能,并为您提供网路上的优秀信息资源,以补充您的经验和学习过程。记住,CUDAZone是CUDA资源的集散地,而论坛上会提供很多你提出的问题的答案。另外,它们的互动优势能让你提出问题,得到答案。

本专栏文章和以下几个专栏文章将利用一个简单的数组反向应用程序来扩充您的知识,将重点介绍共享内存的性能影响。我将和CUDA剖析工具一起介绍错误检查和性能行为。此外,还包含了下一专栏文章的资源列表,这样您可以看到如何通过共享内存实现数组反向。程序reverseArray_multiblock.cu采用明显但性能较低的方式实现了在CUDA设备上反向全局内存中的数组。不要将它用作应用程序的模型,因为对于此类应用程序来说全局内存并不是最佳的内存类型——而且此版本还要进行不结合的内存访问,这会对全局内存性能产生不利影响。只有当同时的内存访问能够结合成单个的内存事务时,我们才能获得最佳的全局内存带宽。在后续的专栏文章中,我将介绍全局内存和共享内存之间的不同,以及根据设备的计算能力对要结合的内存访问的各种要求。

CUDA 错误处理

创建健壮和实用的软件,发现和处理错误非常重要。当用户的应用程序出现故障,或产生错误的结果时,他们通常会非常恼怒。对于程序开发人员来说,添加错误处理代码是个令人恼火和繁琐的工作。它会使原本整洁的代码变得散乱,为了处理每个能想到的错误,开发的进程被延缓。是的,错误处理是个不讨好的工作,但是记住,你不是为自己做这个工作(尽管良好的错误检查机制已经挽救了我无数次)――您是为了将来使用这些程序的人做这项工作。如果出现故障,用户需要了解为什么会这样,更重要的是,他们能够做些什么来解决问题。有效的错误处理和回复的确可以让你的应用程序受到用户的欢迎。商业开发人员尤其需要注意到这一点。

CUDA设计人员明了有效错误处理的重要性。为了更好的处理错误,每个CUDA调用(包括内核启动异常)都会返回一个类型为cudaError_t.的错误代码。一旦成功完成,返回cudaSuccess。否则,返回错误代码。

char *cudaGetErrorString(cudaError_t code);

C语言程序员会发现此方法和C库之间的相似处,C库使用变量errno来表示错误,使用perror和strerror来获得适合阅读的错误消息报告。C库样例已经很好地为几百万行C代码服务了,无疑它将来也会很好地为CUDA软件服务。

CUDA还提供了一个方法,cudaGetLastError, 它报告主机线程里的任何之前的运行调用的最后一次错误。它有几个作用:
内核启动的不同步本质排除了通过cudaGetLastError显示检查错误的可能。相反,使用cudaThreadSynchronize会阻止错误检查直到设备完成所有之前的调用,包括内核调用,并且如果前面的某个任务失败它会返回错误。多个内核启动排队则意味着只有在所有内核都完成以后才能进行错误检查——除非程序员在内核内进行了明显的错误检查并向主机报告。

错误被报告给正确的主机线程。如果主机正在运行多线程,很有可能当应用程序正在使用多个CUDA设备时,错误被报告给正确的主机线程。
当有多个错误在对cudaGetLastError的调用之间发生时,仅最后一个错误会被报告。这意味着程序员必须注意将错误与生成该错误的运行时调用相连或冒险给用户发送一个不正确的错误报告。

查看源代码

查看reverseArray_multiblock.cu的源代码,你会注意到该程序的结构非常类似于第二节的moveArrays.cu的结构。提供了一个错误例程checkCUDAError,这样主机可打印出可阅读的消息,并当错误被报告时(通过cudaGetLastError),退出。您看到了,在整个程序中,我们巧妙地利用了checkCUDAError来检查错误。

程序reverseArray_multiblock.cu实质上创建了一个1D整数数组,h_a, 包含整数值 [0 ..dimA-1]。数组h_a通过cudaMemcpy移动到数组d_a,后者位于设备的全局内存里。主机然后启动reverseArrayBlock内核,以反向顺序从d_a到 d_b拷贝数组内容(这是另外一个全局内存数组)。使用cudaMemcpy 来传输数据-这次是从d_b到主机。然后进行主机检查,以确认设备给出了正确的结果(比如,[dimA-1 .. 0])。

 1 #include <stdio.h>
 2 #include <assert.h>
 3 #include "cuda.h"
 4 #include "cuda_runtime.h"
 5 #include "device_launch_parameters.h"
 6 // Simple utility function to check for CUDA runtime errors
 7 void checkCUDAError(const char* msg);
 8 // Part3: implement the kernel
 9 __global__ void reverseArrayBlock(int *d_out, int *d_in)
10 {
11     int inOffset = blockDim.x * blockIdx.x;
12     int outOffset = blockDim.x * (gridDim.x - 1 - blockIdx.x);
13     int in = inOffset + threadIdx.x;
14     int out = outOffset + (blockDim.x - 1 - threadIdx.x);
15     d_out[out] = d_in[in];
16 }
17 /////////////////////////////////////////////////////////////////////
18 // Program main
19 /////////////////////////////////////////////////////////////////////
20 int main(int argc, char** argv)
21 {
22     // pointer for host memory and size
23     int *h_a;
24     int dimA = 256 * 1024; // 256K elements (1MB total)
25     // pointer for device memory
26     int *d_b, *d_a;
27     // define grid and block size
28     int numThreadsPerBlock = 256;
29     // Part 1: compute number of blocks needed based on
30     // array size and desired block size
31     int numBlocks = dimA / numThreadsPerBlock;
32     // allocate host and device memory
33     size_t memSize = numBlocks * numThreadsPerBlock * sizeof(int);
34     h_a = (int *)malloc(memSize);
35     cudaMalloc((void **)&d_a, memSize);
36     cudaMalloc((void **)&d_b, memSize);
37     // Initialize input array on host
38     for (int i = 0; i < dimA; ++i)
39     {
40         h_a[i] = i;
41     }
42     // Copy host array to device array
43     cudaMemcpy(d_a, h_a, memSize, cudaMemcpyHostToDevice);
44     // launch kernel
45     dim3 dimGrid(numBlocks);
46     dim3 dimBlock(numThreadsPerBlock);
47     reverseArrayBlock << < dimGrid,
48         dimBlock >> >(d_b, d_a);
49     // block until the device has completed
50     cudaThreadSynchronize();
51     // check if kernel execution generated an error
52     // Check for any CUDA errors
53     checkCUDAError("kernel invocation");
54     // device to host copy
55     cudaMemcpy(h_a, d_b, memSize, cudaMemcpyDeviceToHost);
56     // Check for any CUDA errors
57     checkCUDAError("memcpy");
58     // verify the data returned to the host is correct
59     for (int i = 0; i < dimA; i++)
60     {
61         assert(h_a[i] == dimA - 1 - i);
62     }
63     // free device memory
64     cudaFree(d_a);
65     cudaFree(d_b);
66     // free host memory
67     free(h_a);
68     // If the program makes it this far, then the results are
69     // correct and there are no run-time errors.  Good work!
70     printf("Correct!\n");
71
72     return 0;
73 }
74 void checkCUDAError(const char *msg)
75 {
76     cudaError_t err = cudaGetLastError();
77     if (cudaSuccess != err)
78     {
79         fprintf(stderr, "Cuda error: %s: %s.\n", msg,
80             cudaGetErrorString(err));
81         exit(EXIT_FAILURE);
82     }
83 }

该程序的一个关键设计特征是,两个数组d_a 和d_b 位于设备上的全局内存里。CUDA SDK提供样例程序bandwidthTest,它提供了一些关于设备特点的信息。在我的系统中,全局内存带宽刚刚超过60GB/s。如果要为128个硬件线程提供服务,这将是很有用的—-每个线程都能提供大量的浮点操作。因为一个32位浮点值占据四个字节,此设备上的全局内存带宽受限应用程序只能提供大约1515 GF/s–或可用性能很小一部分的百分比。(假定应用程序仅从全局内存读取,并且不向全局内存写入东西)。显然,性能较高的应用程序必须重新以某种方式使用数据。这是共享和寄存器内存的功能。我们程序员的工作就是获得这些内存类型的最大效益。要想更好的了解浮点能力与内存带宽之间的机器平衡法则(和其他的机器特征),请阅读我的文章HPC Balance and Common Sense。

共享内存版

以下资源列表是关于arrayReversal_multiblock_fast.cu, 我会在下一部分里介绍。
我现在提供它是为了方便您了解如何在这个问题上使用共享内存。

1 // includes, system
 2 #include <stdio.h>
 3 #include <assert.h>
 4 // Simple utility function to check for CUDA runtime errors
 5 void checkCUDAError(const char* msg);
 6 // Part 2 of 2: implement the fast kernel using shared memory
 7 __global__ void reverseArrayBlock(int *d_out, int *d_in)
 8 {
 9     extern __shared__ int s_data[];
10     int inOffset  = blockDim.x * blockIdx.x;
11     int in  = inOffset + threadIdx.x;
12     // Load one element per thread from device memory and store it
13     // *in reversed order* into temporary shared memory
14     s_data[blockDim.x - 1 - threadIdx.x] = d_in[in];
15     // Block until all threads in the block have
16     // written their data to shared mem
17     __syncthreads();
18     // write the data from shared memory in forward order,
19     // but to the reversed block offset as before
20     int outOffset = blockDim.x * (gridDim.x - 1 - blockIdx.x);
21     int out = outOffset + threadIdx.x;
22     d_out[out] = s_data[threadIdx.x];
23 }
24 /////////////////////////////////////////////////////////////////////
25 // Program main
26 /////////////////////////////////////////////////////////////////////
27 int main( int argc, char** argv)
28 {
29     // pointer for host memory and size
30     int *h_a;
31     int dimA = 256 * 1024; // 256K elements (1MB total)
32     // pointer for device memory
33     int *d_b, *d_a;
34     // define grid and block size
35     int numThreadsPerBlock = 256;
36     // Compute number of blocks needed based on array size
37     // and desired block size
38     int numBlocks = dimA / numThreadsPerBlock;
39     // Part 1 of 2: Compute number of bytes of shared memory needed
40     // This is used in the kernel invocation below
41     int sharedMemSize = numThreadsPerBlock * sizeof(int);
42     // allocate host and device memory
43     size_t memSize = numBlocks * numThreadsPerBlock * sizeof(int);
44     h_a = (int *) malloc(memSize);
45     cudaMalloc( (void **) &d_a, memSize );
46     cudaMalloc( (void **) &d_b, memSize );
47     // Initialize input array on host
48     for (int i = 0; i < dimA; ++i)
49     {
50         h_a[i] = i;
51     }
52     // Copy host array to device array
53     cudaMemcpy( d_a, h_a, memSize, cudaMemcpyHostToDevice );
54     // launch kernel
55     dim3 dimGrid(numBlocks);
56     dim3 dimBlock(numThreadsPerBlock);
57     reverseArrayBlock<<< dimGrid, dimBlock,
58              sharedMemSize >>>( d_b, d_a );
59     // block until the device has completed
60     cudaThreadSynchronize();
61     // check if kernel execution generated an error
62     // Check for any CUDA errors
63     checkCUDAError("kernel invocation");
64     // device to host copy
65     cudaMemcpy( h_a, d_b, memSize, cudaMemcpyDeviceToHost );
66     // Check for any CUDA errors
67     checkCUDAError("memcpy");
68     // verify the data returned to the host is correct
69     for (int i = 0; i < dimA; i++)
70     {
71         assert(h_a[i] == dimA - 1 - i );
72     }
73     // free device memory
74     cudaFree(d_a);
75     cudaFree(d_b);
76     // free host memory
77     free(h_a);
78     // If the program makes it this far, then results are correct and
79     // there are no run-time errors.  Good work!
80     printf("Correct!\n");
81
82     return 0;
83 }
84
85
86 void checkCUDAError(const char *msg)
87 {
88     cudaError_t err = cudaGetLastError();
89     if( cudaSuccess != err)
90     {
91         fprintf(stderr, "Cuda error: %s: %s.\n", msg,
92                              cudaGetErrorString( err) );
93         exit(EXIT_FAILURE);
94     }
95 }  

在下一专栏文章中,我将介绍共享内存的使用以提高性能。那时,我会深入介绍CUDA内存类型——特别是 __shared__、__constant__和register memory。

时间: 2024-11-15 10:26:48

CUDA:Supercomputing for the Masses (用于大量数据的超级计算)-第三节的相关文章

CUDA:Supercomputing for the Masses (用于大量数据的超级计算)-第一节

原文链接 第一节 CUDA 让你可以一边使用熟悉的编程概念,一边开发可在GPU上运行的软件. Rob Farber 是西北太平洋国家实验室(Pacific Northwest National Laboratory)的高级科研人员.他在多个国家级的实验室进行大型并行运算的研究,并且是几个新创企业的合伙人.大家可以发邮件到[email protected]与他沟通和交流. 您是否有兴趣在使用高级语言(比如C编程语言)编程时,通过标准多核处理器将性能提升几个数量级?您是否期待拥有跨多个设备的伸缩能力

CUDA:Supercomputing for the Masses (用于大量数据的超级计算)-第七节

第七节:使用下一代CUDA硬件,快乐加速度 原文链接 Rob Farber 是西北太平洋国家实验室(Pacific Northwest National Laboratory)的高级科研人员.他在多个国家级的实验室进行大型并行运算的研究,并且是几个新创企业的合伙人.大家可以发邮件到[email protected]与他沟通和交流. 在关于CUDA(Compute Unified Device Architecture,即计算统一设备架构的简称)的系列文章的第六节,我探讨了使用CUDA PROFI

CUDA:Supercomputing for the Masses (用于大量数据的超级计算)-第六节

原文链接 第六节:全局内存和CUDA RPOFILER  Rob Farber 是西北太平洋国家实验室(Pacific Northwest National Laboratory)的高级科研人员.他在多个国家级的实验室进行大型并行运算的研究,并且是几个新创企业的合伙人.大家可以发邮件到[email protected]与他沟通和交流. 在关于CUDA(Compute Unified DeviceArchitecture,即计算统一设备架构的简称)的系列文章的第二节,我探讨了内存性能和在rever

CUDA:Supercomputing for the Masses (用于大量数据的超级计算)-第十节

原文链接 第十节:CUDPP, 强大的数据平行CUDA库Rob Farber 是西北太平洋国家实验室(Pacific Northwest National Laboratory)的高级科研人员.他在多个国家级的实验室进行大型并行运算的研究,并且是几个新创企业的合伙人.大家可以发邮件到[email protected]与他沟通和交流. 在关于CUDA(Compute Unified Device Architecture,即计算统一设备架构的简称)的系列文章的第9节,我探讨了如何使用CUDA拓展高

CUDA:Supercomputing for the Masses (用于大量数据的超级计算)-第九节

原文链接 第九节:使用CUDA拓展高等级语言 Rob Farber 是西北太平洋国家实验室(Pacific Northwest National Laboratory)的高级科研人员.他在多个国家级的实验室进行大型并行运算的研究,并且是几个新创企业的合伙人.大家可以发邮件到[email protected]与他沟通和交流. 在关于CUDA(Compute Unified Device Architecture,即计算统一设备架构的简称)的系列文章的第八节,我探讨了使用CUDA利用库.在本小节,我

为数据挖掘小组写的一个用于造数据的小程序

最近有个数据挖掘的项目,要求在文本里面写入随机字母并且要1000W个 于是就写了个程序用来造数据并记录一下 程序写的时候遇到的问题 1 未考虑内存溢出的情况,大批量的把数据写入导致内存溢出 以后需要谨慎对待 目前完整版 package test; import java.io.File; import java.io.FileWriter; import java.io.IOException; import org.apache.commons.io.FileUtils; import org

Memblaze 联手PMC推出用于超大规模数据中心的高性能PCIe SSD

Memblaze 联手PMC 推出用于超大规模数据中心的高性能PCIe SSD Memblaze 产品在容量.灵活度及延迟等方面均领先业界       引领大数据连接.传送以及存储,提供创新半导体及软件解决方案的PMC?公司(纳斯达克代码:PMCS)今日宣布,忆恒创源科技有限公司( Memblaze Technology Co. Ltd)在其下一代PCIe产品中采用了PMC的Flashtec? NVM Express (NVMe) NVMe控制器. 基于Flashtec的Memblaze的PBl

Druid:一个用于大数据实时处理的开源分布式系统

Druid是一个用于大数据实时查询和分析的高容错.高性能开源分布式系统,旨在快速处理大规模的数据,并能够实现快速查询和分析.尤其是当发生代码部署.机器故障以及其他产品系统遇到宕机等情况时,Druid仍能够保持100%正常运行.创建Druid的最初意图主要是为了解决查询延迟问题,当时试图使用Hadoop来实现交互式查询分析,但是很难满足实时分析的需要.而Druid提供了以交互方式访问数据的能力,并权衡了查询的灵活性和性能而采取了特殊的存储格式. Druid功能介于PowerDrill和Dremel

CUDA 实现JPEG图像解码为RGB数据

了解JPEG数据格式的人应该easy想到.其对图像以8*8像素块大小进行切割压缩的方法非常好用并行处理的思想来实现.而其实英伟达的CUDA自v5.5開始也提供了JPEG编解码的演示样例.该演示样例存储在CUDA的SDK中,即CUDA的默认安装路径"C:\ProgramData\NVDIA Corporation\CUDA Samples\v7.0\7_CUDALibraries\jpegNPP"(v后面的数字依据版本号的不同会变更)中. 该演示样例将图片数据进行了解码和再编码,因为解码