CUDA学习记录第一篇--CPU及GPU基础


CPU和GPU架构

处理器结构主要要考虑的两个指标:延迟和吞吐量。

  • 延迟:一条指令从发布到返回结果所经历的时间。
  • 吞吐量:单位时间内处理的指令的条数。

CPU: 延迟到向内核
GPU: 吞吐导向内核

CPUs

内存大

  • 多级缓存结构提高访问速度

有复杂的控制单元

  • 分支预测机制(判断运算分支)
  • 流水线数据前送机制(判断经常用到的数据,并将其前送)

运算单元强大

  • 整型浮点型运算速度快

GPUs

缓存小

  • 提高内存吞吐

控制简单

  • 没有分支预测
  • 没有数据转发

精简运算单元

  • 多长延时流水线以实现高吞吐量
  • 需要大量线程来容忍延迟

GPU&CPU特点

CPUs:连续计算部分,延迟优先。CPU比GPU单条复杂指令延迟快10倍以上。
GPUs:并行计算部分,吞吐优先。GPU比CPU单位时间内执行指令数量10倍以上。

什么样的问题适合GPU

计算密集:数值计算的比例要远大于内存操作,因此内存访问的延时可以被计算掩盖。
数据并行:大任务可以被拆解为执行相同指令的小任务,因此对复杂流程控制的需求较低

  1. 内存访问次数少
  2. 控制简单
  3. 计算简单
  4. 并行度高

GPU编程与CUDA

CUDA (Compute Unified Device Architecture),初衷是为GPU增加一个易用的编程接口,让开发者无需学习复杂的着色语言或者图形处理原语。

OpenCL(Open Computing Languge)是2008年发布的异构平台并行编程的开发标准,也是一个编程框架。OpenCL相比于CUDA,支持的平台更多,除了GPU还支持CPU、DSP、FPGA等设备。

GPU编程并行计算整体流程

void GPUkernel(float* A, float* B, float* C, int n){
1.  // Allocate device memory for A, B and C
    // copy A and B from host memory (CPU) to device memory (GPU)

2.  // Kernel launch code - to have the device
    // to perform the actual vector addition

3.  // copy C from the device memory to host memory
    // free device vectors
}

CUDA编程术语

硬件侧

  • Device=GPU

  • Host=CPU

  • Kernel=GPU上运行的函数

  • CUDA中的内存模型分为以下几个层次:

    • 每个线程处理器(SP)都用自己的registers(寄存器)
    • 每个SP都有自己的local memory(局部内存), register和local memory只能被线程自己访问
    • 每个多核处理器(SM)内都有自己的shared memeory(共享内存),shared memory可以被线程块内所有线程访问,不过shared memory的大小较小
    • 一个GPU的所有SM共有一块global memory(全局内存), 不用线程块的线程都可使用

软件侧

  • CUDA中的内存模型分为以下几个层次:
    • 线程处理器(SP)对应线程(thread)
    • 多核处理器(SM)对应线程块(thread block)
    • 设备端(device)对应线程块组合体(grid)
  • 一个kernel其实由一个grid来执行
  • 一个kernel一次只能在一个GPU上执行

线程块:可扩展的集合体

线程是内存模型在软件侧最基本的执行单位,线程块就是线程的组合体。

将线程数组分成多个块

  • 块内的线程通过共享内存、原子操作和屏障同步进行协作(shared memory, atomic operations and barrier synvhronization)
  • 不同块中的线程不能协作

特点:

  • 线程块内的所有线程的计算和内存都是互相独立的
  • 线程块内的共享内存都可访问
  • 可以用公有时钟来同步
网格(grid):并行线程块组合体

CUDA核函数由线程网格(数组)执行

  • 每个线程都有一个索引,用于计算内存地址和做出控制决策

特点:

  • 网格内的线程块是互相独立的
  • 网格内的全局内存可由网格内的所有线程块访问
  • 可用公有时钟同步网格内所有线程块
线程块id&线程id:定位独立线程

也是通过索引来确定线程的地址, 但GPU不止需要线程的索引,还需要线程块的索引(可能有多维)。

dim3 dimGrid(M, N);
dim3 dimBlock(P, Q, S);

threadId.x = blockIdx.x * blockDim.x + threadIdx.x;
threadId.y = blockIdx.y * blockDim.y + threadIdx.y;

线程束(warp)
  • SM采用的SMIT(Single-Instruction, Multiple-Thread, 单指令多线程)架构,warp(线约束)是最基本的执行单元,一个warp包含32个并行thread,这些thread以不同数据资源执行相同的指令。warp本质上是线程在GPU上运行的最小单元。
  • 当一个kernel被执行时,grid中的线程块被分配到SM上,一个线程块的thread只能在一个SM上调度,SM一般可以调度多个线程块,大量的thread可能被分到不同的SM上。每个threade拥有它自己的程序计数器和状态寄存器,并且用该线程自己的数据执行指令,这就是所谓的Single Instruction Multiple Thread(SIMT)。
  • 由于warp的大小为32, 所以block包含的thread的大小一般设置为32的倍数。

并行计算实例:向量相加

CPU

// Compute vector sum C=A+B
void vecAdd(float* A, float* B, float *c, int n){
    for (i = 0, i < n, i++)
    c[i] = A[i] + B[i];
}

int main(){
    //Memory alocation for A_h, B_h and C_h
    // I/O to read A_h and B_h, N elements
    vecAdd(A_h, B_h, C_h, N);
}

GPU

void vecAdd(float* A, float* B, float* C, int n){
    int size = n * sizeof(float);
    float* A_d, *B_d, *C_d;
    // Transfer A and B to device memory
    cudaMalloc((void **) &A_d, size);
    CUDAMemcpy(A_d, A, size, cudaMemcpyHostToDevice);
    cudaMalloc((void **) &B_d, size);
    cudaMemcpy(B_d, B, size, cudaMemcpyHostToDevice);

    // Allocate device memory for
    cudaMalloc((void **) &C_d, size);

    // Kernel invocation code - to be shown later

    // Transfer C from device to host
    cudaMemcpy(C, C_d, size, cudaMemcpyDeviceToHost);

    // Free device memory for A, B, C
    cudaFree(A_d);
    cudaFree(B_d);
    cudaFree(C_d);
}


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM