并发与并行:概念及对应关系

1. 并行(Parallelism)

GPU

概念: 并行是指物理意义上的同时执行,即多个任务在硬件上真正同时运行。在 CUDA 中,这通常依赖于 GPU 的多核架构,允许大量线程或任务并行执行。

在 CUDA 编程模型中的体现

  • 线程级并行: CUDA 内核函数中的每个线程是独立执行的,且同一线程块内的线程可通过共享内存协作。GPU 的 流式多处理器(SM) 可以同时调度多个线程块或线程束(warp)并行执行。
    • 线程块(Block):每个线程块被分配到一个 SM 上执行,块内的线程并行运行。
    • 线程束(Warp):32 个线程组成一个 warp,SM 按 warp 粒度调度执行(SIMD 模式)。
  • 任务级并行: 不同的 CUDA 操作(如内核启动、内存拷贝)在硬件上可并行执行。例如,GPU 支持同时运行多个内核(kernel<<<...>>>)或同时进行内存传输(cudaMemcpyAsync)。

在硬件模型中的体现

  • GPU 架构: NVIDIA GPU 包含数百至数千个 CUDA 核心,每个 SM 可同时处理多个线程块。例如,NVIDIA Ampere 架构的 SM 支持最多 32 个线程块(通过 动态并行硬件资源分配)。
  • 内存层次: 全局内存、共享内存和寄存器的协同设计,使得线程间数据访问高效,支持大规模并行计算。

CPU

概念: 并行是指多个任务在同一时刻真正同时执行,依赖硬件的多核或多处理器架构。每个任务在独立的硬件单元(如 CPU 核心)上运行,物理上完全独立,无需依赖时间片切换。

在硬件中的体现

  • 多核 CPU:每个核心独立执行指令,拥有独立的寄存器、缓存和执行单元。
  • 分布式系统:多台计算机通过网络协作,任务分解到不同节点并行执行。

在软件中的体现

  • 操作系统调度器:将任务分配到不同的 CPU 核心,利用多核并行执行。
  • 并行编程模型:如 OpenMP(共享内存并行)、MPI(分布式内存并行),显式划分任务到不同核心/节点。

示例

  • 科学计算:矩阵乘法分解到多个核心并行计算。
  • 多线程程序:在多核 CPU 上,多个线程分别运行在不同核心上。

2. 并发(Concurrency)

GPU

概念: 并发是指逻辑意义上的交替执行,即多个任务通过时间片轮转资源复用的方式看似同时运行。在 CUDA 中,这通常通过 CUDA Stream 实现,允许不同任务在逻辑上交错执行,但物理上可能共享硬件资源。

在 CUDA 编程模型中的体现

  • CUDA Stream: CUDA Stream 是任务队列,用于管理 GPU 上异步操作(如内核启动、内存拷贝)。

    • 默认流(Stream 0):所有未显式指定流的操作会按顺序执行(隐式同步)。
    • 非默认流:通过创建多个流(cudaStreamCreate),不同流中的操作可以并发执行(需满足硬件条件)。

    示例

    cudaStream_t stream1, stream2;
    cudaMemcpyAsync(d_data1, h_data1, size, cudaMemcpyHostToDevice, stream1); // 流1中的内存拷贝
    kernel1<<<grid, block, 0, stream1>>>(d_data1); // 流1中的内核
    cudaMemcpyAsync(d_data2, h_data2, size, cudaMemcpyHostToDevice, stream2); // 流2中的内存拷贝
    kernel2<<<grid, block, 0, stream2>>>(d_data2); // 流2中的内核

    在硬件支持下,kernel1kernel2 可能并行执行,或与内存拷贝操作重叠。

  • 并发与并行的结合

    • 计算与内存传输的重叠: 通过不同流的异步操作(如 cudaMemcpyAsync + kernel<<<...>>>),计算与内存传输可并发执行,从而隐藏内存延迟。
    • 多内核并发执行: 若硬件资源(如 SM 数量)充足,多个内核可在不同流中并发运行。

在硬件模型中的体现

  • GPU 资源调度: CUDA 运行时通过 调度器(Scheduler) 动态分配 SM 和内存带宽给不同流的任务。例如,NVIDIA Fermi 架构起支持最多 16 个并发内核。
  • 内存带宽复用: 不同流的内存拷贝操作(如 cudaMemcpyAsync)可利用独立的 DMA 引擎并发执行。

CPU

概念: 并发是指多个任务在逻辑上同时执行,但物理上可能通过时间片轮转或资源复用的方式交替执行。即使单核 CPU 也能通过快速切换任务上下文实现并发。

在硬件中的体现

  • 单核 CPU:通过时间片轮转(Time-Slicing)快速切换任务,模拟“同时执行”。
  • 多核 CPU:单核处理并发任务的同时,其他核心可并行执行其他任务。

在软件中的体现

  • 操作系统调度器:管理任务的切换和资源分配(如 CPU、内存),通过上下文切换实现并发。
  • 并发编程模型:如多线程、异步 I/O(如 Java 的 CompletableFuture),通过协作式或抢占式调度实现任务交错执行。

示例

  • Web 服务器:单核 CPU 通过并发处理多个 HTTP 请求,快速切换上下文以响应客户端。
  • 图形用户界面(GUI):主线程处理用户交互,后台线程执行耗时任务(如文件读取)。

3. 并发与并行的区别与联系

GPU

特性并行(Parallelism)并发(Concurrency)
物理性真正同时执行(硬件支持)逻辑交替执行(通过时间片或资源复用)
硬件依赖需要多核/多处理单元(如 GPU 的 SM)通过流调度实现,硬件支持有限(如流数限制)
CUDA 实现方式多线程块、多 warp、多内核CUDA Stream 管理异步操作
典型应用科学计算、深度学习训练(大规模数据并行)实时系统、任务流水线(计算与传输重叠)

CPU

特性并行(Parallelism)并发(Concurrency)
硬件依赖多核 CPU 或分布式系统单核或单核 + 多核 CPU(单核需时间片轮转)
执行方式物理上真正同时执行逻辑上交替执行(可能通过时间片或资源复用)
软件支持需要并行编程模型(如 OpenMP、MPI)需要并发编程模型(如多线程、异步 I/O)
目标缩短任务总耗时(加速计算)提高资源利用率(如响应性、吞吐量)
典型场景科学计算、大规模数据处理(如 Hadoop、Spark)I/O 密集型任务(如 Web 服务器、数据库)

4. 实际应用中的对应关系

GPU

  • 硬件模型

    • 并行:GPU 的多 SM 架构(如 NVIDIA A100 的 108 SM)直接支持线程级并行。
    • 并发:GPU 的流调度器(如 ECU,Execution Context Unit)管理多个流的并发执行。
  • 编程模型

    • 并行:通过线程块(Block)和网格(Grid)设计实现大规模数据并行。
    • 并发:通过 CUDA Stream 和异步 API(如 cudaMemcpyAsync)实现任务级并发。

CPU

单核 CPU

  • 只能实现并发:通过时间片轮转切换任务(如早期 PC 的 Windows 9x 系统)。
  • 示例
    • 一个进程运行 10ms 后被强制中断,切换到另一个进程。
    • 用户感觉多个程序“同时运行”,但实际是快速切换的结果。

多核 CPU

  • 并发 + 并行
    • 操作系统调度器将任务分配到多个核心(并行),同时单个核心内仍通过时间片轮转处理并发任务。
    • 示例
      • 4 核 CPU 上运行 8 个线程:4 个线程并行执行,另外 4 个线程在单核上并发执行。

5. 优化建议

GPU

  • 最大化并行

    • 合理配置线程块大小(如 256~512 线程/块),确保 SM 资源利用率。
    • 使用 __global__ 内核的 <<<grid, block>>> 配置充分填充 GPU。
  • 利用并发

    • 为计算与内存传输分配不同流,通过 cudaMemcpyAsync + kernel<<<...>>> 实现重叠。
    • 使用 CUDA Graph 固化高频重复操作(如推理阶段的 Token 生成),减少 CPU 开销。

CPU

  • 硬件设计

    • 多核 CPU 的核心数量决定并行能力上限(如 8 核 CPU 最大支持 8 个并行任务)。
    • 缓存层级(L1/L2/L3)影响并行任务的数据访问效率。
  • 软件设计

    • 并发编程:需处理线程安全(如锁、信号量)和资源竞争问题。
    • 并行编程:需关注任务划分(负载均衡)和通信开销(如 MPI 中的点对点通信)。

总结

GPU

  • 并行:是 GPU 硬件的核心能力,通过多线程/多内核实现物理并行。
  • 并发:是 CUDA 编程模型的高级特性,通过流调度实现逻辑上的任务重叠。
  • 结合使用:两者结合可显著提升 GPU 利用率,例如在深度学习中通过并发流重叠数据加载与模型计算。

CPU

  • 传统 CPU:单核仅支持并发,多核支持并发 + 并行。
  • 操作系统:通过调度器管理任务的并发执行,并在多核环境中实现并行加速。
  • 开发实践
    • I/O 密集型任务(如 Web 服务):优先利用并发(多线程/异步)。
    • CPU 密集型任务(如科学计算):优先利用并行(多核/分布式)。