zoukankan      html  css  js  c++  java
  • cuda c和c++的简单介绍

    原文地址,纯翻译

    https://developer.nvidia.com/blog/easy-introduction-cuda-c-and-c/

      这是cuda并行计算平台 c和c++接口系列的第一篇文章。学习前要求熟练掌握c,针对cuda fortran编程的帖子也会同步更新。这两个系列将涵盖cuda平台上并行计算基本概念。从这里开始,除非有特别说明,否则我将使用属于"cuda c"作为cuda c和c++的简写。cuda c本质上是带有一些拓展的c/c++,允许使用多个并行线程在gpu上执行的函数。

    CUDA编程模型基础

      在接触cuda c代码之前,那些刚接触cuda的人最好先了解cuda编程模型基本描述和其中的一些术语。

      cuda编程模型是一种同时使用cpu和gpu的异构模型。在cuda中,host指cpu及其内存,device指gpu及其内存,host上运行的代码可以管理host和device上的内存、启动kernel(核函数),这些kernel是device上运行的函数,他们由gpu上的许多线程并发运行。

      鉴于cuda编程模型的异构性质,cuda c程序的普遍操作顺序是:

      1.声明和分配host端,device端内存

      2.初始化host端数据

      3.将数据从host端传送到device端

      4.执行一个或多个核函数

      5.将结果从device端传回host端

      记住这个操作流程,让我们来看一个cuda c的例子

    第一个cuda c程序

      上一篇文章中,我介绍了六种SAXPY(Scalar Alpha X Plus Y)的方法,其中就包括了cuda c版本,SAXPY表示单精度A*X+Y,对于并行计算来说是一个很好的hello world程序。在这篇文章中,我将展示一个cuda SAXPY的更完整版本,详细说明做了什么以及为什么这样做,完整的SAXPY代码如下:

    #include <stdio.h>
    
    __global__ void saxpy(int n,float a,float *x,float *y)
    {
        int i=blockIdx.x*blockDim.x+threadIdx.x;
        if(i<n)
        {
            y[i]=a*x[i]+y[i];
        }
    }
    
    int main(void)
    {
        int N=1<<20;    // 1左移20
        float *x,*y,*d_x,*d_y;
        x=(float*)malloc(N*sizeof(float));
        y=(float*)malloc(N*sizeof(float));
    
        cudaMalloc(&d_x,N*sizeof(float));
        cudaMalloc(&d_y,N*sizeof(float));
    
        for(int i=0;i<N;++i)
        {
            x[i]=1.0f;
            y[i]=2.0f;
        }
    
        cudaMemcpy(d_x,x,N*sizeof(float),cudaMemcpyHostToDevice);
        cudaMemcpy(d_y,y,N*sizeof(float),cudaMemcpyHostToDevice);
    
        // perform SAXPY in 1M elements
        int threads=256;
        int blocks=(N+threads-1)/threads;   //  上取整
        saxpy<<<blocks,threads>>>(N,2.0f,d_x,d_y);
        
        cudaMemcpy(y,d_y,N*sizeof(float),cudaMemcpyDeviceToHost);
    
        float maxError=0.0f;
        for(int i=0;i<N;++i)
        {
            maxError=max(maxError,abs(y[i]-4.0f));
        }
        printf("Max error: %f\n",maxError);
    
        cudaFree(d_x);
        cudaFree(d_y);
        free(x);
        free(y);
    
        return 0;
    }

      函数saxpy就是在gpu上运行的核函数,main函数是host端代码,让我们先从host端代码开始讨论这个程序

    Host端代码

      主函数声明了两对数组

    float *x,*y,*d_x,*d_y;
    x=(float*)malloc(N*sizeof(float));
    y=(float*)malloc(N*sizeof(float));
    
    cudaMalloc(&d_x,N*sizeof(float));
    cudaMalloc(&d_y,N*sizeof(float));

      指针x和y指向host端数组,使用malloc函数申请内存空间,d_x,d_y数组指向device端数组,使用cuda runtime api中的cudaMalloc函数申请空间。cuda编程中host端和device端具有独立的内存空间,两者都可以通过host端代码进行管理(cuda c内核还可以在支持他的设备上分配设备内存)。

      host端代码随后初始化host端数组。在此,我们将x设为元素全部为1的数组,y设为元素全为2的数组

    for(int i=0;i<N;++i)
    {
        x[i]=1.0f;
        y[i]=2.0f;
    }

      要初始化device端数组,我们只需使用cudaMemcpy函数将数据从x,y拷贝到device端对应数组d_x,d_y上,这个过程就像c语言中的memcpy函数,唯一的区别就是cudaMemcpy需要地四个参数来指定数据拷贝到方向(host端到device端还是device端到host端),在此例中,我们使用cudaMemcpyHostToDevice表示数据从host拷贝到device端。

    cudaMemcpy(d_x,x,N*sizeof(float),cudaMemcpyHostToDevice);
    cudaMemcpy(d_y,y,N*sizeof(float),cudaMemcpyHostToDevice);

      在运行完核函数后,为了将数据d_y从device端拷贝回host端的y,我们使用cudaMemcpy函数,第四个参数指定为cudaMemcpyDeviceToHost

    cudaMemcpy(y,d_y,N*sizeof(float),cudaMemcpyDeviceToHost);

    启动核函数

      saxpy核函数由以下代码启动

    saxpy<<<blocks,threads>>>(N,2.0f,d_x,d_y);

      <<<>>>中间的信息是核函数的执行配置,具体指有多少设备线程并行执行内核。在cuda中,软件有一个线程层次结构,它模仿线程处理器在gpu上的分组方式。在cuda编程模型中,我们通常说通过grid来启动线程block。执行配置中的第一个参数指定grid中的block数目,第二个参数指定一个block中的线程数。

      block和grid可以通过给dim3传值构造成1维,2维或3维度数据结构(由cuda定义的具有x,y,z策划那个圆的简单结构),但是对于这个简单的例子,我们只需要一个维度,所以我们传递一个整数。在这个例子中,我们设置一个block中包含256个线程,使用取整算法来计算得到处理数组N个元素所需要的block数目((N+256-1)/256)。

      对于数组中元素的数量不能被线程块大小整除的情况,内核代码必须检查越界内存访问。(我认为就是检测取整多出来的那部分)

    释放内存

      程序结束时,我们需要释放掉所有申请的内存空间。对于用cudaMalloc()申请的device端内存,使用cudaFree()来释放。对于host端申请单内存,使用free()来释放。

    cudaFree(d_x);
    cudaFree(d_y);
    free(x);
    free(y);

    device端代码

      现在我们来看核函数代码

    __global__ void saxpy(int n,float a,float *x,float *y)
    {
        int i=blockIdx.x*blockDim.x+threadIdx.x;
        if(i<n)
        {
            y[i]=a*x[i]+y[i];
        }
    }

      在cuda中,我们使用__global__声明符来声明核函数。device代码中定义的变量不需要指定为device变量,因为他们被指定驻留在device上。(我的理解是核函数里声明的变量,他的生命周期就是该线程上执行的核函数生命周期)。在本例中,n,a和i变量将由每个线程存储在一个寄存器中,并且指针x和y必须是指向device内存地址空间的指针。因为当我们从host端启动核函数时,我们要将d_x,d_y传递给kernel。前两个变量n和a,我们没有明确的代码将它们从host端传到device端,由于函数参数在c/c++中默认按值传递,因此cuda运行时可以自动处理这些值到device的传输。cuda runtime api这个特性使得gpu上启动核函数非常自然和容易,几乎与调用c函数一样。

      核函数saxpy的代码只有两行,前面我们提到过,核函数是由多线程并发执行的。如果我们希望每个线程处理数组的一个元素,那么我们需要一种区分和识别每个线程的方法。cuda定义了变量blockDim,blockIdx和threadIdx。这些预定义变量的类型为dim3,类似于主机代码中的执行配置参数。预定义变量blockDim包含在核函数启动的第二个执行配置参数中,指定block中的线程个数(维度)。预定义变量threadIdx和blockIdx分别表示block内的线程索引和grid内地block索引。表达方式:

    int i=blockIdx.x*blockDim.x+threadIdx.x;

      生成用于访问数组元素的全局索引。我们在这个例子中没有使用gridDim,gridDim表示核函数启动的第一个执行配置参数中指定的grid尺寸(一个grid里面有多少个block)

      在使用索引访问数组元素之前,会根据元素个数n检查索引是否合法,以确保没有内存越界访问。如果数组中的元素不能被block中的线程数整除,(由于向上取整)核函数启动的线程数会大于数组大小,此时需要进行检查。核函数的第二行逐个元素执行SAXPY操作,除了边界检查外,他的结果等于host端通过循环实现的SAXPY

    if(i<n) y[i]=a*x[i]+y[i];

    编译和运行代码

      cuda c编译器,nvcc是NVIDIA CUDA Toolkit(http://www.nvidia.com/content/cuda/cuda-toolkit.html)的一部分。为了编译我们的SAXPY例子,我们将代码保存为.cu格式,文件名是saxpy.cu。我们可以使用nvcc编译它

    nvcc -o saxpy saxpy.cu

      我们可以运行代码

    ./saxpy

    总结与结论

      通过使用cuda c对saxpy简单实现,我们现在了解了cuda编程的基础知识。将c代码移植到cuda c上只需要几个c扩展:核函数的__global__声明符;核函数启动时的执行配置(<<<blocks,threads>>>);用于识别和区分并行执行核函数的gpu线程的内置设备变量blockDim,blockIdx和threadIdx。

      异构cuda编程模型的一个优点是,可以增量的将现有代码从c移植到cuda c,一次一个内核。

      在本系列的下一篇文章中,我么将了解一些性能测量和指标。

     
    无情的摸鱼机器
  • 相关阅读:
    PHP保留小数的相关方法
    ASP.NET Core MVC 之过滤器(Filter)
    ASP.NET Core MVC 之控制器(Controller)
    ASP.NET Core MVC 之视图组件(View Component)
    ASP.NET Core MVC 之局部视图(Partial Views)
    标签助手(TagHelper)
    ASP.NET Core MVC 之布局(Layout)
    ASP.NET Core MVC 之视图(Views)
    ASP.NET Core MVC 之模型(Model)
    九卷读书:淘宝从小到大的发展 -重读《淘宝技术这十年》
  • 原文地址:https://www.cnblogs.com/wangtianning1223/p/15703150.html
Copyright © 2011-2022 走看看