第八章:多核与众核技术
章节概述
本章介绍并行处理的基本概念、多核和众核处理器架构、GPU架构、并行编程模型以及缓存一致性机制。通过本章学习,理解现代计算机系统的并行处理技术和发展趋势。
学习目标:
- 理解并行性的概念和分类
- 掌握多核处理器的架构特点
- 了解众核处理器与多核处理器的区别
- 理解GPU架构和编程模型
- 掌握常见的并行编程模型
- 理解缓存一致性协议
本章重点:
- 多核处理器的架构特点
- Cache一致性协议
- GPU架构和CUDA编程
- 并行编程模型
本章难点:
- 众核处理器的架构
- 缓存一致性协议的实现
- 并行编程的理解和应用
8.1 并行处理概述
8.1.1 并行性的概念
并行性:在同一时刻或同一时间间隔内完成两种或两种以上性质相同或不同的工作。
并行性的等级:
| 并行级别 | 说明 | 示例 |
| ———- | —— | —— |
| 指令级并行(ILP) | 指令之间的并行执行 | 流水线、超标量、乱序执行 |
| 数据级并行(DLP) | 对多个数据同时进行相同操作 | SIMD向量指令 |
| 线程级并行(TLP) | 多个线程并行执行 | 多线程、多核处理器 |
| 任务级并行 | 多个独立任务并行执行 | 多进程、分布式计算 |
并行与并发的区别:
并行(Parallelism):真正同时执行
- 需要多处理器或多核
- 多车道高速公路,车辆同时行驶
并发(Concurrency):宏观上同时,微观上交替
- 单处理器也可实现
- 通过时间片轮转实现
- 单车道上车辆交替通过
时间 →
并行:任务A ──────────────────
任务B ────────────────── (真正同时,多核)
并发:任务A ────┬─────┬─────┬───
任务B └─────┘ └─── (交替执行,单核)
并行性的开发方式:
- 硬件并行:多核、多处理器、向量处理
- 软件并行:并行算法、多线程编程
8.1.2 并行计算机分类
Flynn分类法(根据指令流和数据流分类):
| 类型 | 名称 | 特点 | 示例 |
| —— | —— | —— | —— |
| SISD | 单指令流单数据流 | 传统的串行计算机 | 早期的单核处理器 |
| SIMD | 单指令流多数据流 | 一条指令操作多个数据 | 向量机、GPU、SIMD指令 |
| MISD | 多指令流单数据流 | 多个指令处理同一数据 | 很少见(如容错系统) |
| MIMD | 多指令流多数据流 | 多个处理器独立执行 | 多处理器、多核系统 |
Flynn分类法示意:
SISD: SIMD:
指令 → [PU] → 数据 指令 → [PU] → 数据1
[PU] → 数据2
[PU] → 数据3
MISD: MIMD:
指令1 → [PU] → 指令1 → [PU1] → 数据1
↓ 指令2 → [PU2] → 数据2
指令2 → [PU] → 指令3 → [PU3] → 数据3
PU = 处理单元
MIMD的细分:
- 共享内存多处理器(SMP):多个处理器共享统一内存地址空间
- 分布式内存多计算机:每个处理器有自己的内存,通过消息传递通信
8.2 多核处理器
8.2.1 多核处理器概述
多核处理器(Multi-core Processor):在一个处理器芯片上集成两个或多个独立的处理核心。
产生背景:
- 功耗墙限制:单核频率提升受散热限制
- 指令级并行度瓶颈:单核提取更多并行性困难
- 晶体管持续增加:需要有效利用增加的晶体管
优势:
- 提高性能:多个核心同时工作
- 降低功耗:多个低效核心比单个高效核心更节能
- 提高可靠性:一个核心故障不影响其他核心
- 改善响应性:多任务并行处理
单核 vs 多核性能趋势:
性能
│ ╱ 单核(功耗墙)
│ ╱
│ ╱
│ ╱
│ ╱ ╱ 多核
│ ╱ ╱
│ ╱ ╱
│ ╱ ╱
└──────────────→ 时间
2004年(多核转折点)
8.2.2 多核架构
同构多核:
- 所有核心结构相同,功能对等
- 负载均衡容易实现
- 软件兼容性好
- 代表:Intel Core系列、AMD Ryzen
异构多核:
- 核心结构不同,通常包含大核和小核
- 大核:高性能、高功耗,处理复杂任务
- 小核:低性能、低功耗,处理简单任务
- 根据负载动态调度任务
- 代表:ARM big.LITTLE、Intel Alder Lake
异构多核架构(ARM big.LITTLE): ┌─────────────────────────────────────────┐ │ 异构多核处理器 │ │ │ │ ┌─────────┐ ┌─────────┐ │ │ │ 大核 │ │ 大核 │ Cortex-A78 │ │ │高性能 │ │高性能 │ (处理重负载) │ │ │ 高功耗 │ │ 高功耗 │ │ │ └─────────┘ └─────────┘ │ │ │ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │ │ 小核 │ │ 小核 │ │ 小核 │ │ │ │低性能 │ │低性能 │ │低性能 │ │ │ │ 低功耗 │ │ 低功耗 │ │ 低功耗 │ │ │ └─────────┘ └─────────┘ └─────────┘ │ │ Cortex-A55 │ │ (处理轻负载/后台任务) │ └─────────────────────────────────────────┘
多核处理器架构:
典型多核处理器架构: ┌─────────────────────────────────────────────────┐ │ 多核处理器芯片 │ │ │ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │ │ 核心0 │ │ 核心1 │ │ 核心2 │ │ │ │ ┌───┐ │ │ ┌───┐ │ │ ┌───┐ │ │ │ │ │L1I│ │ │ │L1I│ │ │ │L1I│ │ │ │ │ ├───┤ │ │ ├───┤ │ │ ├───┤ │ │ │ │ │L1D│ │ │ │L1D│ │ │ │L1D│ │ │ │ │ └───┘ │ │ └───┘ │ │ └───┘ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ L2 │ │ L2 │ │ L2 │ │ │ └────┬────┘ └────┬────┘ └────┬────┘ │ │ └─────────────┼─────────────┘ │ │ │ │ │ ┌──────┴──────┐ │ │ │ 共享L3 │ │ │ └──────┬──────┘ │ │ │ │ │ ┌──────┴──────┐ │ │ │ 内存控制器 │ │ │ └─────────────┘ │ └─────────────────────────────────────────────────┘
8.2.3 多核的Cache组织
私有Cache:每个核心有自己的L1和L2 Cache
- 优点:访问速度快,无竞争
- 缺点:共享数据可能存储在多个Cache中
共享Cache:多个核心共享L3 Cache
- 优点:容量大,共享数据只需存储一份
- 缺点:访问速度较慢,可能存在竞争
Cache一致性:需要确保多个核心看到的内存数据一致
8.3 众核处理器
8.3.1 众核处理器概述
众核处理器(Many-core Processor):集成几十个到上百个简单核心的处理器。
与多核的区别:
- 核心数量更多(通常>32个)
- 单个核心更简单(顺序执行、无乱序)
- 更强调吞吐量而非单线程性能
- 通常用于高性能计算(HPC)
代表产品:
- Intel Xeon Phi(MIC架构,最多72核)
- 国产申威26010(260核)
- Tilera TILE-Gx(100核)
众核 vs 多核对比: 特性 多核处理器 众核处理器 ──────────────────────────────────────────────── 核心数 2-32个 32-数百个 核心复杂度 复杂(OoO) 简单(顺序) 单线程性能 高 低 总吞吐量 中 高 适用场景 通用计算 HPC、数据中心 编程难度 较低 较高 代表产品 Core i9 Xeon Phi
8.3.2 众核架构特点
特点:
- 简单核心设计:顺序执行、无分支预测、无乱序执行
- 高吞吐量的内存系统:高内存带宽
- 硬件支持多线程:每个核心支持多个硬件线程
- 适合数据并行应用:如科学计算、深度学习
适用场景:
- 科学计算(有限元分析、分子动力学)
- 深度学习训练和推理
- 大数据分析
- 视频编码/解码
众核处理器架构(Intel Xeon Phi): ┌─────────────────────────────────────────┐ │ 众核处理器(72核示例) │ │ │ │ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ │ │ │核心0│ │核心1│ │核心2│ ... │核心71│ │ │ │×4线程│ │×4线程│ │×4线程│ │×4线程│ │ │ └──┬──┘ └──┬──┘ └──┬──┘ └──┬──┘ │ │ └───────┴───────┴───────────┘ │ │ │ │ │ ┌────┴────┐ │ │ │ 环形总线 │ │ │ └────┬────┘ │ │ ┌────────┼────────┐ │ │ ┌──┴──┐ ┌─┴─┐ ┌──┴──┐ │ │ │内存控制器│ │I/O│ │内存控制器│ │ │ └─────┘ └───┘ └─────┘ │ └─────────────────────────────────────────┘ 每个核心支持4个硬件线程,总共288个逻辑处理器
8.4 GPU架构
8.4.1 GPU概述
GPU(Graphics Processing Unit,图形处理器):
- 最初用于图形渲染(顶点变换、像素着色)
- 现在广泛用于通用计算(GPGPU - General-Purpose GPU)
- 适合大规模数据并行任务
GPU vs CPU:
| 特性 | CPU | GPU |
| —— | —– | —– |
| 核心数 | 少(几个到几十个) | 多(几百到几千个) |
| 核心复杂度 | 复杂(乱序执行、分支预测、大Cache) | 简单(顺序执行、小Cache) |
| 时钟频率 | 高(3-5GHz) | 较低(1-2GHz) |
| 内存 | 容量小(数十GB)、速度快(高带宽) | 容量大(数十GB)、带宽高 |
| 控制逻辑 | 占芯片面积大 | 占芯片面积小 |
| 计算单元 | 占芯片面积小 | 占芯片面积大 |
| 适合任务 | 串行任务、复杂控制流 | 数据并行任务、规则计算 |
CPU vs GPU设计哲学: CPU:强大的单线程性能 ┌─────────────────────────────────────┐ │ 控制单元 │ 控制单元 │ 控制单元 │ │ ├─────────┼─────────┼─────────┤ │ │ ALU │ ALU │ ALU │ Cache │ └─────────┴─────────┴─────────┴───────┘ 少量核心,复杂控制,大Cache GPU:大量简单计算单元 ┌─────────────────────────────────────┐ │ ALU │ ALU │ ALU │ ALU │ ALU │ ALU │ │ │ ALU │ ALU │ ALU │ ALU │ ALU │ ALU │ │ │ ALU │ ALU │ ALU │ ALU │ ALU │ ALU │ │ │ ... (数百到数千个ALU) │ │ 共享控制单元,小Cache │ └─────────────────────────────────────┘
8.4.2 GPU架构
NVIDIA GPU架构:
GPU架构示意(NVIDIA): ┌─────────────────────────────────────────────────┐ │ GPU芯片 │ │ ┌─────────────────────────────────────────┐ │ │ │ 全局内存(显存) │ │ │ │ (GDDR6/HBM,大容量高带宽) │ │ │ └─────────────────────────────────────────┘ │ │ │ │ │ │ │ │ │ ┌────────┼────┼────┼────┼────┼────────┐ │ │ │ 流处理器阵列(SM阵列) │ │ │ │ ┌──┐ ┌──┐ ┌──┐ ┌──┐ ┌──┐ ┌──┐ │ │ │ │ │SM│ │SM│ │SM│ │SM│ │SM│ │SM│... │ │ │ │ └──┘ └──┘ └──┘ └──┘ └──┘ └──┘ │ │ │ │ (SM = Streaming Multiprocessor) │ │ │ └─────────────────────────────────────┘ │ └─────────────────────────────────────────────────┘
Streaming Multiprocessor(SM):
- GPU的基本计算单元
- 包含多个CUDA核心(流处理器)
- 共享内存和L1 Cache
- 可同时执行多个线程块
- 包含特殊功能单元(SFU)、加载/存储单元(LD/ST)
Streaming Multiprocessor(SM)内部结构: ┌─────────────────────────────────────┐ │ Streaming Multiprocessor │ │ │ │ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ │ │ │CUDA │ │CUDA │ │CUDA │ │CUDA │ │ │ │核心 │ │核心 │ │核心 │ │核心 │ │ │ └──┬──┘ └──┬──┘ └──┬──┘ └──┬──┘ │ │ └───────┴───────┴───────┘ │ │ │ │ │ ┌───────────┴───────────┐ │ │ │ 共享内存/L1 │ │ │ │ (可被线程块访问) │ │ │ └───────────────────────┘ │ │ │ │ ┌────────┐ ┌────────┐ │ │ │ LD/ST │ │ SFU │ │ │ │加载/存储│ │特殊功能│ │ │ └────────┘ └────────┘ │ │ │ │ ┌─────────────────────────────┐ │ │ │ 指令调度/分发单元 │ │ │ └─────────────────────────────┘ │ └─────────────────────────────────────┘
GPU内存层次:
| 内存类型 | 位置 | 速度 | 容量 | 访问范围 |
| ——— | —— | —— | —— | ——— |
| 寄存器 | SM内 | 最快 | 每个线程KB级 | 单个线程 |
| 共享内存/L1 | SM内 | 快 | 每个SM数十KB | 线程块内 |
| L2 Cache | 芯片级 | 中 | 数MB | 所有线程 |
| 全局内存 | 显存 | 慢 | 数GB到数十GB | 所有线程 |
| 常量/纹理内存 | 显存 | 有缓存 | 有限 | 只读 |
8.4.3 CUDA编程
CUDA(Compute Unified Device Architecture):NVIDIA的并行计算平台和编程模型。
执行模型:
- Grid:整个GPU执行任务,包含多个Block
- Block:一组线程,可协作、共享内存
- Thread:基本执行单元
- Warp:32个线程组成一个Warp,是调度单位
CUDA执行模型层次: Grid(整个GPU) ┌─────────────────────────────────────┐ │ Block │ Block │ Block │ Block │ │ (0,0) │ (0,1) │ (1,0) │ (1,1) │ │ ┌───┐ │ ┌───┐ │ ┌───┐ │ ┌───┐ │ │ │0 │ │ │0 │ │ │0 │ │ │0 │ │ │ │1 │ │ │1 │ │ │1 │ │ │1 │ │ │ │...│ │ │...│ │ │...│ │ │...│ │ │ │255│ │ │255│ │ │255│ │ │255│ │ │ └───┘ │ └───┘ │ └───┘ │ └───┘ │ │ 线程块 │ 线程块 │ 线程块 │ 线程块 │ └─────────────────────────────────────┘ 每个Block内的线程可以: - 同步(__syncthreads()) - 共享数据(通过共享内存) - 协作执行
// CUDA程序示例:向量加法 #include <cuda_runtime.h> // 核函数(在GPU上执行) __global__ void vectorAdd(int *a, int *b, int *c, int n) { // 计算全局线程ID int i = blockIdx.x * blockDim.x + threadIdx.x; // 确保不越界 if (i < n) { c[i] = a[i] + b[i]; } } int main() { int n = 1024; int *a, *b, *c; // 主机内存 int *d_a, *d_b, *d_c; // 设备(GPU)内存 // 分配主机内存 a = (int*)malloc(n * sizeof(int)); b = (int*)malloc(n * sizeof(int)); c = (int*)malloc(n * sizeof(int)); // 分配设备内存 cudaMalloc(&d_a, n * sizeof(int)); cudaMalloc(&d_b, n * sizeof(int)); cudaMalloc(&d_c, n * sizeof(int)); // 初始化数据... // 拷贝数据到设备 cudaMemcpy(d_a, a, n * sizeof(int), cudaMemcpyHostToDevice); cudaMemcpy(d_b, b, n * sizeof(int), cudaMemcpyHostToDevice); // 启动核函数 int threadsPerBlock = 256; int blocksPerGrid = (n + threadsPerBlock - 1) / threadsPerBlock; vectorAdd<<<blocksPerGrid, threadsPerBlock>>>(d_a, d_b, d_c, n); // 拷贝结果回主机 cudaMemcpy(c, d_c, n * sizeof(int), cudaMemcpyDeviceToHost); // 释放内存... return 0; }
CUDA编程要点:
- 主机(CPU)和设备(GPU)有独立的内存空间
- 需要显式地在主机和设备之间传输数据
- 核函数用`global`声明
- 线程组织为Grid和Block两级
8.5 并行编程模型
8.5.1 共享内存模型
特点:
- 多个处理器共享同一地址空间
- 通过读写共享变量进行通信
- 需要同步机制(锁、信号量)保护共享数据
- 编程相对简单,但容易出现竞争条件
代表:OpenMP、Pthreads
// OpenMP示例 #include <omp.h> #include <stdio.h> int main() { int sum = 0; int a[100]; // 初始化数组 for (int i = 0; i < 100; i++) a[i] = i; // 并行区域 #pragma omp parallel for reduction(+:sum) for (int i = 0; i < 100; i++) { sum += a[i]; } printf("Sum = %d\n", sum); // 输出 4950 return 0; } 编译:gcc -fopenmp program.c -o program 运行:OMP_NUM_THREADS=4 ./program
OpenMP常用指令:
- `#pragma omp parallel`:创建并行区域
- `#pragma omp parallel for`:并行化for循环
- `#pragma omp critical`:临界区(互斥访问)
- `#pragma omp barrier`:同步点
- `reduction`:归约操作(如求和)
8.5.2 消息传递模型
特点:
- 每个处理器有独立地址空间
- 通过发送/接收消息进行通信
- 显式通信,编程复杂度高
- 适合分布式内存系统
代表:MPI(Message Passing Interface)
// MPI示例 #include <mpi.h> #include <stdio.h> int main(int argc, char** argv) { int rank, size; int sendbuf, recvbuf; MPI_Init(&argc, &argv); MPI_Comm_rank(MPI_COMM_WORLD, &rank); MPI_Comm_size(MPI_COMM_WORLD, &size); sendbuf = rank * 10; if (rank == 0) { // 进程0接收来自其他进程的数据 for (int i = 1; i < size; i++) { MPI_Recv(&recvbuf, 1, MPI_INT, i, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE); printf("Process 0 received %d from process %d\n", recvbuf, i); } } else { // 其他进程发送数据给进程0 MPI_Send(&sendbuf, 1, MPI_INT, 0, 0, MPI_COMM_WORLD); } MPI_Finalize(); return 0; } 编译:mpicc program.c -o program 运行:mpirun -np 4 ./program
常用MPI函数:
- `MPI_Init/MPI_Finalize`:初始化/结束MPI
- `MPI_Comm_rank`:获取当前进程ID
- `MPI_Comm_size`:获取进程总数
- `MPI_Send/MPI_Recv`:点对点通信
- `MPI_Bcast`:广播
- `MPI_Reduce`:归约操作
8.5.3 数据并行模型
特点:
- 同一条指令操作多个数据
- 适合SIMD架构
- 编程模型相对简单
代表:CUDA、OpenCL
// OpenCL示例 __kernel void vectorAdd(__global int *a, __global int *b, __global int *c, int n) { int i = get_global_id(0); if (i < n) { c[i] = a[i] + b[i]; } }
三种编程模型对比:
| 特性 | 共享内存 | 消息传递 | 数据并行 |
| —— | ——— | ——— | ——— |
| 地址空间 | 统一 | 分离 | 统一/分离 |
| 通信方式 | 读写共享变量 | 显式消息传递 | 隐式数据分布 |
| 编程复杂度 | 中 | 高 | 低 |
| 可扩展性 | 有限 | 好 | 好 |
| 适用系统 | 多核、SMP | 集群、分布式 | GPU、向量机 |
| 代表 | OpenMP | MPI | CUDA |
8.6 缓存一致性与互联
8.6.1 Cache一致性问题
问题描述:
- 多个核心的Cache中可能存有同一内存地址的副本
- 一个核心修改数据后,其他核心的Cache数据失效
- 需要确保所有核心看到的数据一致
Cache一致性问题示例: 初始:内存中X = 5 核心0 核心1 │ │ ▼ ▼ ┌─────┐ ┌─────┐ │Cache│ │Cache│ │ X=5 │ │ X=5 │ └─────┘ └─────┘ │ │ ▼ │ 写 X=10 │ │ │ ▼ ▼ ┌─────┐ ┌─────┐ │Cache│ │Cache│ │ X=10│ │ X=5 │ <-- 过期数据! └─────┘ └─────┘ 问题:核心1的Cache中X仍然是5,不一致!
Cache一致性协议:确保多个Cache中同一数据副本一致的机制。
8.6.2 监听协议(Snooping)
原理:
- 每个Cache控制器监听总线上的内存访问
- 如果发现自己的Cache中有被访问数据的副本,采取行动
- 适合总线连接的少量处理器系统
MESI协议:最常用的监听协议,定义四种状态
| 状态 | 名称 | 含义 |
| —— | —— | —— |
| M | Modified | 已修改,独占,与内存不一致 |
| E | Exclusive | 独占,未修改,与内存一致 |
| S | Shared | 共享,只读,可能有多个副本 |
| I | Invalid | 无效,数据已过期 |
MESI状态转换示意:
┌─────────┐
│ I(无效) │
└────┬────┘
│ 读(不命中)
▼
┌─────────────────┐
│ │
▼ ▼
┌───────┐ ┌───────┐
│E(独占) │ │S(共享) │
│ │ │ │
│写命中 │ │其他核心│
│ ↓ │ │也读了 │
│M(修改) │ │ │
└───────┘ └───────┘
状态转换事件:
- PrRd:处理器读
- PrWr:处理器写
- BusRd:总线读
- BusRdX:总线读独占
- BusUpgr:总线升级
MESI协议操作示例:
核心0读X:I -> E(总线读,其他Cache无此数据) 核心0写X:E -> M(独占,直接写) 核心1读X:M -> S,核心1获得S(总线读,核心0写回内存) 核心0写X:S -> M,核心1的S -> I(总线读独占,使其他失效)
8.6.3 目录协议(Directory)
原理:
- 集中维护共享数据的状态信息(目录)
- 记录每个内存块在哪个Cache中,什么状态
- 适合大规模系统(如多处理器服务器)
目录项内容:
- 状态位(是否被缓存、是否被修改)
- 共享位向量(哪些处理器有此数据副本)
目录协议示意: 内存控制器中的目录: ┌─────────┬─────────┬─────────────────┐ │ 内存块 │ 状态 │ 共享位向量 │ ├─────────┼─────────┼─────────────────┤ │ 块0 │ Shared │ [1,0,1,0] │ │ 块1 │ Modified│ [0,1,0,0] │ │ 块2 │ Uncached│ [0,0,0,0] │ └─────────┴─────────┴─────────────────┘ 共享位向量:[核心0, 核心1, 核心2, 核心3]
目录协议 vs 监听协议:
| 特性 | 监听协议 | 目录协议 |
| —— | ——— | ——— |
| 可扩展性 | 差(总线带宽限制) | 好 |
| 延迟 | 低(直接广播) | 较高(查询目录) |
| 存储开销 | 小 | 大(需要目录存储) |
| 适用规模 | 少量处理器(<16) | 大规模系统(>16) |
| 互联网络 | 总线 | 任意网络 |
8.6.4 片上互联
多核处理器需要高效的片上互联网络连接核心、Cache和内存控制器。
互联拓扑:
常见片上互联拓扑:
1. 总线(Bus)
核心0 ──┐
核心1 ──┼── 总线 ── 内存
核心2 ──┘
优点:简单、低成本
缺点:可扩展性差、带宽受限
2. 环形(Ring)
┌─────────────────┐
↓ │
核心0 → 核心1 → 核心2
↑ │
└──── 核心3 ←────┘
优点:平衡复杂度和性能
缺点:延迟随核心数增加
3. 网状网络(Mesh)
核心00 ── 核心01 ── 核心02
│ │ │
核心10 ── 核心11 ── 核心12
│ │ │
核心20 ── 核心21 ── 核心22
优点:可扩展性好、带宽高
缺点:复杂度高、延迟不确定
片上网络(NoC - Network on Chip):
- 使用分组交换技术
- 路由器连接各处理单元
- 类似互联网的路由机制
- 适合众核和大型多核系统
8.7 练习题
一、选择题
1. 根据Flynn分类法,多核处理器属于( ) A. SISD B. SIMD C. MISD D. MIMD
2. GPU最适合处理( ) A. 复杂串行任务 B. 数据并行任务 C. 分支多的任务 D. 内存密集型任务
3. MESI协议中,S表示( ) A. 已修改 B. 独占 C. 共享 D. 无效
4. OpenMP使用的是( )编程模型 A. 消息传递 B. 共享内存 C. 数据并行 D. 任务并行
5. 众核处理器的主要特点是( ) A. 核心少而复杂 B. 核心多而简单 C. 单核性能强 D. 功耗高
6. CUDA中,线程组织的最小调度单位是( ) A. Thread B. Block C. Grid D. Warp
7. 以下哪种互联拓扑可扩展性最好( ) A. 总线 B. 环形 C. 网状网络 D. 星形
8. 异构多核通常指( ) A. 所有核心相同 B. 核心分大小核 C. 只有一个核心 D. 没有Cache
9. 目录协议相比监听协议的主要优势是( ) A. 延迟更低 B. 可扩展性更好 C. 实现更简单 D. 存储开销更小
10. MPI最适合( )系统 A. 共享内存 B. 分布式内存 C. 单核 D. GPU
二、填空题
1. Flynn分类法将计算机分为_______、_______、_______和_______四类。
2. 多核处理器解决Cache一致性问题的两种方法是_______和_______。
3. GPU最初用于_______,现在也广泛用于_______。
4. CUDA编程模型中,执行单元分为_______、_______和_______三个层次。
5. MESI协议的四种状态是_______、_______、_______和_______。
6. 常见的并行编程模型有_______、_______和_______三种。
7. GPU中,_______个线程组成一个Warp,是基本的调度单位。
8. 片上互联的常见拓扑包括_______、_______和_______。
9. 众核处理器通常有_______个以上的核心。
10. 共享内存模型中,保护共享数据需要_______机制。
三、简答题
1. 简述多核处理器的优势和挑战。
2. 比较CPU和GPU的架构特点。
3. 简述共享内存模型和消息传递模型的区别。
4. 什么是MESI协议?简述四种状态的含义。
5. 简述监听协议和目录协议的区别及各自适用场景。
6. 什么是众核处理器?它与多核处理器有什么区别?
四、编程题
1. 使用OpenMP编写程序,计算π值(使用莱布尼茨公式)。
2. 使用CUDA编写向量加法程序。
参考答案:
一、选择题:1.D 2.B 3.C 4.B 5.B 6.D 7.C 8.B 9.B 10.B
二、填空题:
1. SISD、SIMD、MISD、MIMD 2. 监听协议、目录协议 3. 图形渲染、通用计算(GPGPU) 4. Grid、Block、Thread 5. Modified、Exclusive、Shared、Invalid 6. 共享内存、消息传递、数据并行 7. 32 8. 总线、环形、网状网络 9. 32(或数十个) 10. 同步(或锁、互斥)
三、简答题:
1. 优势:提高性能、降低功耗、提高可靠性、改善响应性。挑战:编程复杂度增加、Cache一致性问题、负载均衡、内存带宽瓶颈。
2. CPU核心少而复杂,适合串行任务;GPU核心多而简单,适合数据并行任务。CPU有高频率、大Cache、复杂控制逻辑;GPU有大量ALU、小Cache、简单控制逻辑。
3. 共享内存模型有统一地址空间,通过读写共享变量通信;消息传递模型每个处理器有独立地址空间,通过发送/接收消息通信。共享内存编程简单但可扩展性有限,消息传递可扩展性好但编程复杂。
4. MESI是Cache一致性协议,四种状态:M(Modified,已修改,独占)、E(Exclusive,独占,未修改)、S(Shared,共享,只读)、I(Invalid,无效)。
5. 监听协议通过广播监听总线实现一致性,适合小规模系统;目录协议通过集中维护目录实现一致性,适合大规模系统。监听协议延迟低但可扩展性差,目录协议存储开销大但可扩展性好。
6. 众核处理器有数十到数百个简单核心,强调吞吐量而非单线程性能。多核处理器有2-32个复杂核心,强调单线程性能。众核适合HPC,多核适合通用计算。
四、编程题:
1.
#include <omp.h> #include <stdio.h> int main() { int n = 1000000; double pi = 0.0; #pragma omp parallel for reduction(+:pi) for (int i = 0; i < n; i++) { double x = (i + 0.5) / n; pi += 4.0 / (1.0 + x * x); } pi /= n; printf("Pi = %f\n", pi); return 0; }
2. 见8.4.3节CUDA示例