并行计算简介

1. 并行计算的发展历程

  • 1970s:超算(Supercomputers)用于科学计算
    • CMU C.mmp(1971):16 个 PDP-11 处理器
    • Cray XMP(1984):4 个向量处理器
    • Thinking Machines CM-2(1987):65536 个 1-bit 处理器 + 2048 个浮点协处理器
    • Blacklight 超算(今天):4096 核心
  • 1990s:数据库应用推动并行计算
    • Sun Enterprise 10000(1997):16 个 UltraSPARC-II
    • Oracle Supercluster M6-32(今天):32 个 SPARC M2
  • 2004 年:Intel 达到功率密度墙(Power Density Wall)
    • 单核性能提升受限
    • 并行处理成为性能提升的必然选择

2. 单处理器性能历史

  • 更宽的数据通路(4-bit → 64-bit)
  • 更高效的流水线(CPI 从 3.5 → 1.1)
  • 指令级并行(ILP,Superscalar,每周期最多发射 4 条指令)
  • 更快的时钟频率(10 MHz → 3 GHz)
  • 80-90 年代性能指数增长,但 2004 后趋缓

3. 并行计算机类型

  • 桌面/移动设备
    • Mac Pro:12 核 Xeon E5
    • MacBook Pro:4 核 i7
    • iPhone 6s:2 核 CPU + GPU
  • 超级计算机
    • Titan 超算:18688 个 16 核 CPU + NVIDIA K20X GPU
  • 专用加速器
    • Intel Xeon Phi:61 个 x86 核(1.3 GHz)
    • NVIDIA GTX 980:多块 GPU 处理单元

并行编程概念

1. 什么是并行计算机?

  • 定义:多个处理单元合作快速解决问题
  • 关注 性能效率
  • 与“并发编程”不同,后者更多是程序逻辑的正确性

2. 并行计算动机

  • 加速(Speedup)
    Speedup=单处理器时间多处理器时间 \text{Speedup} = \frac{\text{单处理器时间}}{\text{多处理器时间}} Speedup=多处理器时间单处理器时间
  • 影响加速的因素
    • 通信成本:通信越多,速度提升受限
    • 工作分配不均:部分处理器空闲影响效率

3. 并行思维(Parallel Thinking)

  1. 将任务分解成可并行执行的部分
  2. 分配任务到处理器
  3. 管理通信和同步,避免限制加速

4. 硬件视角

  • 了解硬件特性很重要(通信速度、功耗、性能/成本权衡)
  • 效率 ≠ 速度:程序快不代表硬件利用率高
  • CPU 设计变化:
    • 2004 前:单核性能为主
    • 2004 后:性能/面积/功耗为主,核心数增加,注重每瓦性能

例子概述

目标:计算数组中每个浮点数 x[i] 的 sin 值。
方法:用 Taylor 展开式近似:
sin⁡(x)=x−x33!+x55!−x77!+… \sin(x) = x - \frac{x^3}{3!} + \frac{x^5}{5!} - \frac{x^7}{7!} + \dots sin(x)=x3!x3+5!x57!x7+

C++ 程序逻辑

void sinx(int N, int terms, float* x, float* result) {
    for (int i = 0; i < N; i++) {               // 遍历数组每个元素
        float value = x[i];                      // 初始值 x
        float numer = x[i] * x[i] * x[i];       // 分子初始值 x^3
        int denom = 6;                           // 分母初始值 3! = 6
        int sign = -1;                           // 符号轮换
        for (int j = 1; j <= terms; j++) {      // Taylor 展开迭代 terms 次
            value += sign * numer / denom;      // 加上当前项
            numer *= x[i] * x[i];               // 更新分子 x^(2j+3)
            denom *= (2*j+2)*(2*j+3);           // 更新分母 (2j+2)*(2j+3)
            sign *= -1;                          // 更新符号
        }
        result[i] = value;                        // 存储结果
    }
}

要点

  1. 外层循环 for (i) → 遍历数组每个元素
  2. 内层循环 for (j) → 计算 Taylor 展开的每一项
  3. 每次迭代更新:
    • 分子:numer *= x[i]^2
    • 分母:阶乘累乘
    • 符号:正负交替

汇编对应(伪示意)

x[i]          ; 加载 x[i] 到寄存器
ld r0, addr[r1] ; r1 存放 x[i] 地址,加载到 r0
mul r1, r0, r0  ; r1 = x[i]^2
mul r1, r1, r0  ; r1 = x[i]^3  -> 对应 numer 初值
...
st addr[r2], r0 ; 计算完成后,存入 result[i]

理解

  • ld:加载数组元素到寄存器
  • mul:计算幂次(如 x^3)
  • st:把最终结果存回 result[i]

并行执行的潜在优化

这个程序是 “数据并行” 的典型例子:

  • 每个数组元素的计算互不依赖 → 可以用多核 CPU/GPU 并行处理
  • 外层 for (i) 循环完全可以分给多个线程或 SIMD 指令
  • 内层 for (j) 由于迭代依赖前一项,暂时无法并行
    总结
  1. C++ 程序 → Taylor 展开计算 sin(x)
  2. 汇编 → 展示了加载、乘法、存储流程
  3. 并行优化 → 数据级并行可显著提升性能
#include <iostream>
#include <cmath>    // 仅用于验证结果
// 使用 Taylor 展开计算 sin(x) 对数组每个元素进行计算
void sinx(int N, int terms, float* x, float* result) {
    for (int i = 0; i < N; i++) {               // 遍历数组每个元素
        float value = x[i];                      // 初始值 sin(x) 的第 0 项
        float numer = x[i] * x[i] * x[i];       // 分子初始值 x^3
        int denom = 6;                           // 分母初始值 3! = 6
        int sign = -1;                           // 符号轮换,从负号开始
        for (int j = 1; j <= terms; j++) {      // Taylor 展开迭代 terms 次
            value += sign * numer / denom;      // 加上当前项
            numer *= x[i] * x[i];               // 更新分子 x^(2j+3)
            denom *= (2*j+2)*(2*j+3);           // 更新分母 (2j+2)*(2j+3)
            sign *= -1;                          // 符号翻转
        }
        result[i] = value;                        // 存储结果
    }
}
int main() {
    const int N = 5;             // 数组长度
    const int terms = 5;         // Taylor 展开项数
    float x[N] = {0.0f, 0.5f, 1.0f, 1.5f, 3.14f}; // 输入数组
    float result[N];             // 输出结果数组
    // 调用函数
    sinx(N, terms, x, result);
    // 打印结果,并与标准库 sin 函数比较
    for (int i = 0; i < N; i++) {
        std::cout << "x = " << x[i]
                  << ", sinx = " << result[i]
                  << ", std::sin = " << std::sin(x[i])
                  << std::endl;
    }
    return 0;
}

程序解析

  1. 外层循环 i
    遍历输入数组的每个元素,每个元素可以独立计算 → 数据级并行可行。
  2. 内层循环 j
    计算 Taylor 展开式的每一项:
    sin⁡(x)≈x−x33!+x55!−… \sin(x) \approx x - \frac{x^3}{3!} + \frac{x^5}{5!} - \dots sin(x)x3!x3+5!x5
  3. 符号控制
    sign *= -1 保证交替加减。
  4. 分子和分母更新
    • 分子:乘以 x[i]*x[i]
    • 分母:累乘 (2*j+2)*(2*j+3) → 阶乘增长
  5. 存储结果
    最终的 value 存入 result[i]

可优化点

  1. 并行化:外层循环可用 OpenMP、CUDA、ISPC 等并行执行。
  2. SIMD:对数组做向量化计算,提高 CPU 浮点吞吐量。
  3. 减少浮点操作:复用分子和分母,避免重复计算幂和阶乘。
┌─────────────────────────────────┐                                                                                       
│                                 │  ┌─────────────────────────┐  ┌─────────────────────────┐  ┌─────────────────────────┐
│    ┌─────────────────────┐      │  │PC ->  ld r0, addr[r1]   │  │       ld r0, addr[r1]   │  │       ld r0, addr[r1]   │
│    │       Fetch/        │      │  │                         │  │                         │  │                         │
│    │       Decode        │      │  │       mul r1, r0, r0    │  │PC ->  mul r1, r0, r0    │  │       mul r1, r0, r0    │
│    └─────────────────────┘      │  │                         │  │                         │  │                         │
│    ┌─────────────────────┐      │  │       mul r1, r1, r0    │  │       mul r1, r1, r0    │  │PC ->  mul r1, r1, r0    │
│    │         ALU         │      │  │                         │  │                         │  │                         │
│    │      (Execute)      │      │  │       ...               │  │       ...               │  │       ...               │
│    └─────────────────────┘      │  │                         │  │                         │  │                         │
│                                 │  │       ...               │  │       ...               │  │       ...               │
│    ┌─────────────────────┐      │  │                         │  │                         │  │                         │
│    │      Execution      │      │  │       ...               │  │       ...               │  │       ...               │
│    │       Context       │      │  │                         │  │                         │  │                         │
│    │ ┌───────┐┌───────┐  │      │  │       ...               │  │       ...               │  │       ...               │
│    │ ├───────┤├───────┤  │      │  │                         │  │                         │  │                         │
│    │ ├───────┤├───────┤  │      │  │       ...               │  │       ...               │  │       ...               │
│    │ ├───────┤├───────┤  │      │  │                         │  │                         │  │                         │
│    │ └───────┘└───────┘  │      │  │       ...               │  │       ...               │  │       ...               │
│    │                     │      │  │                         │  │                         │  │                         │
│    └─────────────────────┘      │  │       st addr[r2], r0   │  │       st addr[r2], r0   │  │       st addr[r2], r0   │
│                                 │  │                         │  │                         │  │                         │
└─────────────────────────────────┘  └─────────────────────────┘  └─────────────────────────┘  └─────────────────────────┘
┌─────────────────────────────────┐                                
│                                 │         ┌─────────────────────┐
│  ┌───────────┐ ┌───────────┐    │         │   ld r0, addr[r1]   │
│  │  Fetch/   │ │  Fetch/   │    │         │                     │
│  │  Decode   │ │  Decode   │    │         │   mul r1, r0, r0    │
│  └───────────┘ └───────────┘    │         │                     │
│  ┌───────────┐ ┌───────────┐    │         │   mul r1, r1, r0    │
│  │    ALU    │ │    ALU    │    │         │                     │
│  │ (Execute) │ │ (Execute) │    │         │   ...               │
│  └───────────┘ └───────────┘    │         │                     │
│                                 │         │   ...               │
│    ┌─────────────────────┐      │         │                     │
│    │      Execution      │      │         │   ...               │
│    │       Context       │      │         │                     │
│    │ ┌───────┐┌───────┐  │      │         │   ...               │
│    │ ├───────┤├───────┤  │      │         │                     │
│    │ ├───────┤├───────┤  │      │         │   ...               │
│    │ ├───────┤├───────┤  │      │         │                     │
│    │ └───────┘└───────┘  │      │         │   ...               │
│    │                     │      │         │                     │
│    └─────────────────────┘      │         │   st addr[r2], r0   │
│                                 │         │                     │
└─────────────────────────────────┘         └─────────────────────┘
 ld r0, addr[r1]
 mul r1, r0, r0 
 mul r1, r1, r0 
Note: No ILP exists in this region of the program

1. 简单处理器(单发射)

你的示意图展示了一个 非常简单的处理器

  • 特点:每个时钟周期执行一条指令(单发射)。
  • 执行流程
    1. Fetch/Decode:取指令并解码。
    2. ALU (Execute):执行指令。
    3. Execution Context:更新寄存器/内存状态。
  • 示例指令流
    ld r0, addr[r1]      // 从内存加载 x[i] 到 r0
    mul r1, r0, r0       // r1 = r0 * r0
    mul r1, r1, r0       // r1 = r1 * r0
    st addr[r2], r0      // 将结果存回内存
    
  • 特点分析
    • 每条指令必须等待上一条完成才能执行。
    • 流水线深度浅,吞吐量低
    • 没有利用 指令级并行性(ILP)

2. 超标量处理器(Superscalar)

  • 特点:每个时钟周期可以尝试执行多条指令(2条或更多),前提是这些指令没有数据依赖
  • 执行流程
    • Fetch/Decode 两条指令并行
    • ALU 执行两条指令并行
    • 寄存器更新/存储也可以并行。
  • 示意图
    时钟1: ld r0, addr[r1]   | mul r1, r0, r0
    时钟2: mul r1, r1, r0    | ...
    
  • 优势
    • 能利用 指令级并行性(ILP) 提高吞吐量。
    • 处理器面积/功耗相同情况下,性能提升显著。
  • 限制
    • 如果指令有数据依赖(例如 mul r1, r0, r0 必须等 ld r0, addr[r1] 完成),那么 ILP 无法发挥作用
    • 如你的示例中:
      ld r0, addr[r1]
      mul r1, r0, r0
      mul r1, r1, r0
      
      这里三条指令都存在依赖,没有 ILP 可利用,即使是超标量处理器,也无法并行执行这些指令。

3. 总结

类型每周期指令数ILP 利用情况特点
单发射处理器1简单,吞吐量低
超标量处理器2 或更多数据无依赖时有效提高吞吐量,硬件复杂
  • 关键概念
    • ILP(Instruction-Level Parallelism):利用指令之间的独立性,同时执行多条指令。
    • 数据依赖会限制 ILP。
    • 超标量处理器并不是对所有代码都加速,顺序依赖强的代码无法并行化
          System Bus (External)             ┌─────────────┐                                                                                   
     ◀─────────────────◀─┬─────────────▶    │  L2 Cache   │                                                                                   
                         │                  └─────▲───────┘                                                                                   
                         │                        │                                                                                           
                         │                        │ Cache Bus                                                                                 
                         │                        │                                                                                           
                   ┌─────▼────────────────────────▼─────────────────┐◀─────────────────────────────────────────────────────────────┐          
                   │         Bus Interface Unit                     │◀──────────────────────────────────────────┐                  │          
                   └──────────▲─────────────────────────▲───────────┘                                           │                  │          
                              │                         │                                                       │                  │          
                              │                         │                                                       │                  │          
                    ┌─────────▼─────────────┬───────────▼───────────┐    ┌────────────────────────┐             │                  │          
                    │Instruction Fetch Unit │Instruction Cache (L1) ◀────▶        Next IP         │             │                  │          
                    └──────────────┬────────┴───────────────────────┘    │        Unit            │             │                  │          
                                   │                                     │                        │             │                  │          
                                   │                                     ├ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─              │                  │          
                    ┌──────────────▼────────────────────────────────┐    │         Branch         │             │                  │          
                    │           Instruction Decoder                 ├────▶         Target         ◀─────┐       │                  │          
                    │ ┌───────────┐   ┌───────────┐  ┌───────────┐  │    │         Buffer         │     │       │                  │          
                    │ │Simple     │   │Simple     │  │Complex    │  │    │                        │     │       │                  │          
                    │ │Instuction │   │Instuction │  │Instuction │  │    └────────────────────────┘   From      │                  │          
                    │ │Decoder    │   │Decoder    │  │Decoder    │  │    ┌────────────────────┐       Integer   │                  │          
                    │ └─────┬─────┘   └────┬──────┘  └┬──┬──┬──┬─┘  ◀────▶    Microcode       │       Unit      │                  │          
                    └───────┼──────────────┼──────────┼──┼──┼──┼────┘    │    Instruction     │                 │            ┌─────▼────────┐ 
                            │              │          │  │  │  │         │    Sequencer       │                 │            │              │ 
                            │              │          │  │  │  │         └────────────────────┘                 │            │   Memory     │ 
                     ┌──────▼──────────────▼──────────▼──▼──▼──▼───┐                                            │            │   Reorder    │ 
                     │           Register Alias Table              │                                            │            │   Buffer     │ 
                     └──────┬──────────────────────────────────────┘                                            │            │              │ 
                            │                                                                                   │            └───────▲──────┘ 
                            │                                                                                   │                    │        
                            │       ┌──────────────────────────────────┐                                        │                    │        
                            ├───────▶        Retirement Unit           │                               ┌────────▼───────┐            │        
                            │       ├──────────────────────────────────┤                               │                │            │        
       ┌────────────────────┼───────▶Reorder Buffer (Instruction Pool) │                               │  Data Cache    │            │        
       │                    │       └───────────────▲──────────────────┘                               │  Unit (L1)     │            │        
       │                    │                       │                                                  │                │            │        
       │                    ▼                       │                                                  └─────────┬──────┘            │        
       │     ┌──────────────────────────────────────▼───────────────────────────────────────────────┐            │                   │        
       │     │                           Reservation Station                                        │            │                   │        
       │     └──▲──────────────────────────────┬────────────────────────────────────────────────────┘            │                   │        
       │        │                              │                                                                 │                   │        
       │        │                              │                                                                 │                   │        
       │        │    ┌─────────────────────────▼────────────────────────────────────────────┐                    │                   │        
       │        │    │                     Execution Unit                                   │                    │                   │        
       │        │    │   ┌─────────┐   ┌──────────┐  ┌──────────┐ ┌──────────┐  ┌──────────┐│                    │                   │        
       │        │    │   │ SIMD FP │   │Floating- │  │  Integer │ │  Integer │  │ Memory   │◀────────────────────│                   │        
       │        └─┐  │   │ Unit    │   │Point Unit│  │  Unit    │ │  Unit    │  │ Interface││                    │                   │        
       │          │  │   │ (FPU)   │   │(FPU)     │  │          │ │          │  │ Unit     ││                    │                   │        
       │          │  │   └─────────┘   └──────────┘  └──────────┘ └──────────┘  └──────────┘│                    │                   │        
       │          │  │                                                                      │                    │                   │        
       │          │  └─────────────────────────┬────────────────────────────────────────────┘                    │                   │        
       │          │                            │                                                                 │                   │        
       │          │                            │                                                                 │                   │        
       │     ┌────┴────────────────────────────▼─────────────────────────────────────────────────────────────────▼───────────────────▼───────┐
       └─────┤                          Internal Data-Results Buses                                                                          │
             └───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

现代超标量 CPU 内部结构示意图,我帮你用梳理理解一下。这个图主要展示了指令从 取指到执行再到写回 的全过程,以及 CPU 各个模块之间的关系。

1. 系统总线与缓存

  • System Bus (外部总线)
    • 连接 CPU 与主存或外设。
    • 外部访问速度慢,需要缓存提高效率。
  • L2 Cache(二级缓存)
    • 容量大于 L1,速度慢于 L1。
    • 缓存数据和指令,减少对主存的访问。
  • Cache Bus(缓存总线)
    • CPU 内部访问 L2 Cache 的通路。

2. 指令获取与缓存

  • Instruction Fetch Unit(取指单元)
    • 从 L1 指令缓存或 L2 Cache 取指令。
  • Instruction Cache (L1)
    • 一级缓存,存储最近使用的指令,速度最快。
  • Next IP Unit / Branch Target Buffer
    • 预测下一条要执行的指令地址(IP = Instruction Pointer),处理分支预测。

3. 指令解码

  • Instruction Decoder(指令解码器)
    • 将取到的机器指令为 CPU 可以执行的微操作(Micro-ops)。
    • Simple Instruction Decoder:处理简单指令
    • Complex Instruction Decoder:处理复杂指令
  • Microcode Instruction Sequencer(微码指令序列器)
    • 对复杂指令生成一系列微操作,供执行单元使用。

4. 重命名与乱序执行

  • Register Alias Table(寄存器别名表)
    • 用于寄存器重命名,避免写后读(WAR)或写后写(WAW)依赖。
  • Reorder Buffer / Retirement Unit(重排缓冲/提交单元)
    • 指令可以乱序执行,但保证最终 程序语义顺序提交
    • 执行完的指令先放入 Reorder Buffer,按程序顺序写回寄存器和内存。
  • Reservation Station(保留站)
    • 存放等待执行的微操作,解决数据依赖问题。
    • 当所需操作数就绪时,分派到执行单元。

5. 执行单元

  • Execution Unit(执行单元)
    • SIMD FP Unit(向量浮点单元)
    • Floating Point Unit(标量浮点单元)
    • Integer Unit(整数单元)
    • Memory Interface Unit(内存接口单元)
    • 功能:真正执行指令操作,包括算术、逻辑、加载/存储等。
  • Internal Data-Results Buses(内部数据结果总线)
    • 执行单元的计算结果通过总线传回寄存器或 Reorder Buffer。

6. 内存层次

  • Data Cache L1(一级数据缓存)
    • 存储最近使用的数据,提高访问速度。
  • Memory Reorder Buffer(内存乱序缓冲)
    • 协调乱序执行与内存访问顺序,保证程序语义正确。

7. 工作流程总结(指令流水)

  1. 取指:Instruction Fetch → L1 Instruction Cache → Branch Prediction
  2. 解码:Instruction Decoder → 微操作序列
  3. 寄存器重命名:Register Alias Table
  4. 调度执行:Reservation Station → Execution Units
  5. 结果写回:Reorder Buffer → 寄存器/内存
  6. 缓存层次:L1/L2 Cache → 主存

特点

  • 支持乱序执行、超标量、分支预测、寄存器重命名。
  • 提高指令级并行性(ILP)和 CPU 吞吐量。
  • 保证程序语义顺序与最终结果一致。
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
│    ┌─────────────────────────────────┐ ┌─────────────────────────────────────────────────────┐ │
│    │                                 │ │                                                     │ │
│    │  ┌───────────┐ ┌───────────┐    │ │                                                     │ │
│    │  │  Fetch/   │ │  Fetch/   │    │ │                                                     │ │
│    │  │  Decode   │ │  Decode   │    │ │                   Data cache                        │ │
│    │  └───────────┘ └───────────┘    │ │                                                     │ │
│    │  ┌───────────┐ ┌───────────┐    │ │                  (a big one)                        │ │
│    │  │    ALU    │ │    ALU    │    │ │                                                     │ │
│    │  │ (Execute) │ │ (Execute) │    │ │                                                     │ │
│    │  └───────────┘ └───────────┘    │ └─────────────────────────────────────────────────────┘ │
│    │                                 │                                                         │
│    │    ┌─────────────────────┐      │ ┌─────────────────────────────────────────────────────┐ │
│    │    │      Execution      │      │ │             Out-of-order control logic              │ │
│    │    │       Context       │      │ └─────────────────────────────────────────────────────┘ │
│    │    │ ┌───────┐┌───────┐  │      │ ┌─────────────────────────────────────────────────────┐ │
│    │    │ ├───────┤├───────┤  │      │ │               Fancy branch predictor                │ │
│    │    │ ├───────┤├───────┤  │      │ └─────────────────────────────────────────────────────┘ │
│    │    │ ├───────┤├───────┤  │      │ ┌─────────────────────────────────────────────────────┐ │
│    │    │ └───────┘└───────┘  │      │ │                  Memory pre-fetcher                 │ │
│    │    │                     │      │ └─────────────────────────────────────────────────────┘ │
│    │    └─────────────────────┘      │                                                         │
│    │                                 │                                                         │
│    └─────────────────────────────────┘                                                         │
│                                                                                                │
└────────────────────────────────────────────────────────────────────────────────────────────────┘

好的,这张图展示的是 多核时代之前的单核处理器(pre multi-core era)架构,我帮你理解一下:

1. 核心思想

  • 当时处理器主要 用大量晶体管优化单条指令流(single instruction stream)执行速度
  • 目标:让单线程程序跑得尽可能快,而不是增加多核并行。
  • 使用的主要策略:
    1. 增大缓存(Data Cache)
    2. 更智能的乱序执行控制(Out-of-order control logic)
    3. 高级分支预测(Fancy branch predictor)
    4. 内存预取(Memory pre-fetcher)
    5. 更高的时钟频率(通过更小的晶体管)

2. 图中模块理解

核心执行单元

  • Fetch/Decode(取指/解码)
    • 读取指令并为微操作。
  • ALU (Execute)(算术逻辑单元/执行单元)
    • 执行算术运算、逻辑运算等。
  • Execution Context(执行上下文)
    • 保存寄存器状态、指令状态、执行结果等。

性能优化模块

  • Data Cache(数据缓存)
    • 存储最近使用的数据,加快访问速度。
    • 越大越好,但占用晶体管面积。
  • Out-of-order Control Logic(乱序执行控制逻辑)
    • 支持乱序执行,提高指令级并行(ILP)。
  • Fancy Branch Predictor(高级分支预测器)
    • 提前预测分支跳转,减少流水线停顿。
  • Memory Pre-fetcher(内存预取器)
    • 预测未来访问的数据,提前加载到缓存中,减少内存等待。

3. 晶体管使用策略

  • 增加晶体管 → 可以:
    • 扩大 L1/L2 缓存容量
    • 做更复杂的乱序控制逻辑
    • 做更智能的分支预测
    • 支持更多内存预取功能
  • 同时 → 晶体管越小 → CPU 时钟频率越高 → 单核速度提升

4. 总结

  • 单核时代:追求 单线程性能
  • 重点:用更多晶体管做 缓存、乱序执行、预测和预取
  • 多核出现之前,性能提升依赖 ILP + 高频率 + 大缓存 + 智能硬件机制
┌─────────────────────────────────┐     ┌─────────────────────────────────┐
│                                 │     │                                 │
│         ┌───────────┐           │     │         ┌───────────┐           │
│         │  Fetch/   │           │     │         │  Fetch/   │           │
│         │  Decode   │           │     │         │  Decode   │           │
│         └───────────┘           │     │         └───────────┘           │
│         ┌───────────┐           │     │         ┌───────────┐           │
│         │    ALU    │           │     │         │    ALU    │           │
│         │ (Execute) │           │     │         │ (Execute) │           │
│         └───────────┘           │     │         └───────────┘           │
│                                 │     │                                 │
│    ┌─────────────────────┐      │     │    ┌─────────────────────┐      │
│    │      Execution      │      │     │    │      Execution      │      │
│    │       Context       │      │     │    │       Context       │      │
│    │ ┌───────┐┌───────┐  │      │     │    │ ┌───────┐┌───────┐  │      │
│    │ ├───────┤├───────┤  │      │     │    │ ├───────┤├───────┤  │      │
│    │ ├───────┤├───────┤  │      │     │    │ ├───────┤├───────┤  │      │
│    │ ├───────┤├───────┤  │      │     │    │ ├───────┤├───────┤  │      │
│    │ └───────┘└───────┘  │      │     │    │ └───────┘└───────┘  │      │
│    │                     │      │     │    │                     │      │
│    └─────────────────────┘      │     │    └─────────────────────┘      │
│                                 │     │                                 │
└─────────────────────────────────┘     └─────────────────────────────────┘

多核时代(multi-core era)的处理器架构变化,我帮你做理解:

1. 核心思想

  • 多核时代的理念:
    • 不再用增加晶体管去做更复杂的单线程加速(如更智能的乱序执行、分支预测、投机执行)。
    • 而是用晶体管增加 更多的核心(core)
  • 每个核心可以独立执行一条指令流,多个核心 并行计算不同的数据或任务
  • 举例:处理一个数组中的元素:
    • 单核时代:一个核心依次计算所有元素。
    • 多核时代:两个核心可以同时计算两个不同的元素。

2. 图示解读

  • 图中显示两个核心,每个核心有:
    • Fetch/Decode(取指/解码)
    • ALU/Execute(算术逻辑执行单元)
    • Execution Context(执行上下文)
  • 每个核心相比单核“fancy core”更简单
    • 每个核心执行单线程的速度慢一些(例如 0.75 倍)。
  • 但因为有两个核心:
    • 总性能 ≈ 核心数 × 单核性能 = 2 × 0.75 = 1.5(潜在加速比)。

3. 指令流示意

假设计算 sinx 函数或类似逐元素运算:

  1. 核心 1 计算 x[i]:
ld   r0, addr[r1] 
mul  r1, r0, r0 
mul  r1, r1, r0 
... 
st   addr[r2], r0
result[i]
  1. 核心 2 计算 x[j]:
ld   r0, addr[r1] 
mul  r1, r0, r0 
mul  r1, r1, r0 
... 
st   addr[r2], r0
result[j]
  • 两条指令流 同时执行,相互独立。
  • 数据并行(Data Parallelism):每个核心处理不同数组元素。

4. 总结

  1. 单核时代
    • 晶体管主要用于提升单线程速度(缓存、乱序执行、预测)。
    • 单核速度高,但无法利用多核并行。
  2. 多核时代
    • 晶体管用于增加核心数量。
    • 每个核心简单,但可以并行计算。
    • 通过增加核心数实现整体性能提升,即 线程级并行(TLP, Thread-Level Parallelism)
  3. 关键公式
    总体性能≈核心数×每核心性能 \text{总体性能} \approx \text{核心数} \times \text{每核心性能} 总体性能核心数×每核心性能

1⃣ 原始串行程序

void sinx(int N, int terms, float* x, float* result) {
    for (int i = 0; i < N; i++) {
        float value = x[i];
        float numer = x[i] * x[i] * x[i];
        int denom = 6;  // 3!
        int sign = -1;
        for (int j = 1; j <= terms; j++) {
            value += sign * numer / denom;
            numer *= x[i] * x[i];
            denom *= (2*j+2)*(2*j+3);
            sign *= -1;
        }
        result[i] = value;
    }
}

理解

  • 这是一个纯串行程序,每次只处理数组 x[i] 的一个元素
  • 编译器用 GCC 编译后,只会在 一个 CPU 核心上运行
  • 如果我们换成多核的简化处理器(每核速度 0.75 倍),程序仍然是单线程,总体速度反而更慢(0.75x)。

2⃣ 使用 pthreads 表示并行

typedef struct {
    int N;
    int terms;
    float* x;
    float* result;
} my_args;
void* my_thread_start(void* thread_arg) {
    my_args* args = (my_args*)thread_arg;
    sinx(args->N, args->terms, args->x, args->result);
    return NULL;
}
void parallel_sinx(int N, int terms, float* x, float* result) {
    pthread_t thread_id;
    my_args args;
    args.N = N/2;
    args.terms = terms;
    args.x = x;
    args.result = result;
    pthread_create(&thread_id, NULL, my_thread_start, &args); // 启动线程
    sinx(N - args.N, terms, x + args.N, result + args.N);     // 主线程处理剩余数据
    pthread_join(thread_id, NULL);                             // 等待子线程完成
}

理解

  • 程序手动把数组分成两半,创建一个线程去处理前半部分,主线程处理后半部分。
  • 通过多线程,两个核心可以同时工作,实现 显式的并行
  • 好处:可以利用多核 CPU 提升速度。

3⃣ 数据并行(Data-Parallel)表示

void sinx(int N, int terms, float* x, float* result) {
    // 程序员声明循环迭代相互独立
    forall (int i from 0 to N-1) {
        float value = x[i];
        float numer = x[i] * x[i] * x[i];
        int denom = 6;
        int sign = -1;
        for (int j = 1; j <= terms; j++) {
            value += sign * numer / denom;
            numer *= x[i] * x[i];
            denom *= (2*j+2)*(2*j+3);
            sign *= -1;
        }
        result[i] = value;
    }
}

理解

  • forall 表示 循环迭代相互独立,可以安全地同时执行。
  • 编译器可以利用这个信息,自动生成多线程或者 GPU 并行代码
  • 不需要程序员手动创建线程,更简洁、易维护

4⃣ 总结对比

实现方式并行性程序员负担硬件利用
串行程序只用一个核心,低效率
pthreads 手动并行多核利用,但代码复杂
数据并行声明 (forall)多核或 SIMD 自动利用,易扩展
核心概念
  1. 串行程序:没有并行性,硬件多核无法利用。
  2. 显式线程并行:程序员手动分块,利用多核。
  3. 数据并行:程序员声明独立迭代,编译器自动生成并行代码,代码更清晰。
    Four cores: compute four elements in parallel
┌─────────────────────────────────┐     ┌─────────────────────────────────┐ 
│                                 │     │                                 │ 
│         ┌───────────┐           │     │         ┌───────────┐           │ 
│         │  Fetch/   │           │     │         │  Fetch/   │           │ 
│         │  Decode   │           │     │         │  Decode   │           │ 
│         └───────────┘           │     │         └───────────┘           │ 
│         ┌───────────┐           │     │         ┌───────────┐           │ 
│         │    ALU    │           │     │         │    ALU    │           │ 
│         │ (Execute) │           │     │         │ (Execute) │           │ 
│         └───────────┘           │     │         └───────────┘           │ 
│                                 │     │                                 │ 
│    ┌─────────────────────┐      │     │    ┌─────────────────────┐      │ 
│    │      Execution      │      │     │    │      Execution      │      │ 
│    │       Context       │      │     │    │       Context       │      │ 
│    │ ┌───────┐┌───────┐  │      │     │    │ ┌───────┐┌───────┐  │      │ 
│    │ ├───────┤├───────┤  │      │     │    │ ├───────┤├───────┤  │      │ 
│    │ ├───────┤├───────┤  │      │     │    │ ├───────┤├───────┤  │      │ 
│    │ ├───────┤├───────┤  │      │     │    │ ├───────┤├───────┤  │      │ 
│    │ └───────┘└───────┘  │      │     │    │ └───────┘└───────┘  │      │ 
│    │                     │      │     │    │                     │      │ 
│    └─────────────────────┘      │     │    └─────────────────────┘      │ 
│                                 │     │                                 │ 
└─────────────────────────────────┘     └─────────────────────────────────┘ 
 ┌─────────────────────────────────┐     ┌─────────────────────────────────┐
 │                                 │     │                                 │
 │         ┌───────────┐           │     │         ┌───────────┐           │
 │         │  Fetch/   │           │     │         │  Fetch/   │           │
 │         │  Decode   │           │     │         │  Decode   │           │
 │         └───────────┘           │     │         └───────────┘           │
 │         ┌───────────┐           │     │         ┌───────────┐           │
 │         │    ALU    │           │     │         │    ALU    │           │
 │         │ (Execute) │           │     │         │ (Execute) │           │
 │         └───────────┘           │     │         └───────────┘           │
 │                                 │     │                                 │
 │    ┌─────────────────────┐      │     │    ┌─────────────────────┐      │
 │    │      Execution      │      │     │    │      Execution      │      │
 │    │       Context       │      │     │    │       Context       │      │
 │    │ ┌───────┐┌───────┐  │      │     │    │ ┌───────┐┌───────┐  │      │
 │    │ ├───────┤├───────┤  │      │     │    │ ├───────┤├───────┤  │      │
 │    │ ├───────┤├───────┤  │      │     │    │ ├───────┤├───────┤  │      │
 │    │ ├───────┤├───────┤  │      │     │    │ ├───────┤├───────┤  │      │
 │    │ └───────┘└───────┘  │      │     │    │ └───────┘└───────┘  │      │
 │    │                     │      │     │    │                     │      │
 │    └─────────────────────┘      │     │    └─────────────────────┘      │
 │                                 │     │                                 │
 └─────────────────────────────────┘     └─────────────────────────────────┘

Sixteen cores: compute sixteen elements in parallel

┌─────────────────────────────────┐   ┌─────────────────────────────────┐     ┌─────────────────────────────────┐      ┌─────────────────────────────────┐ 
│                                 │   │                                 │     │                                 │      │                                 │ 
│         ┌───────────┐           │   │         ┌───────────┐           │     │         ┌───────────┐           │      │         ┌───────────┐           │ 
│         │  Fetch/   │           │   │         │  Fetch/   │           │     │         │  Fetch/   │           │      │         │  Fetch/   │           │ 
│         │  Decode   │           │   │         │  Decode   │           │     │         │  Decode   │           │      │         │  Decode   │           │ 
│         └───────────┘           │   │         └───────────┘           │     │         └───────────┘           │      │         └───────────┘           │ 
│         ┌───────────┐           │   │         ┌───────────┐           │     │         ┌───────────┐           │      │         ┌───────────┐           │ 
│         │    ALU    │           │   │         │    ALU    │           │     │         │    ALU    │           │      │         │    ALU    │           │ 
│         │ (Execute) │           │   │         │ (Execute) │           │     │         │ (Execute) │           │      │         │ (Execute) │           │ 
│         └───────────┘           │   │         └───────────┘           │     │         └───────────┘           │      │         └───────────┘           │ 
│                                 │   │                                 │     │                                 │      │                                 │ 
│    ┌─────────────────────┐      │   │    ┌─────────────────────┐      │     │    ┌─────────────────────┐      │      │    ┌─────────────────────┐      │ 
│    │      Execution      │      │   │    │      Execution      │      │     │    │      Execution      │      │      │    │      Execution      │      │ 
│    │       Context       │      │   │    │       Context       │      │     │    │       Context       │      │      │    │       Context       │      │ 
│    │ ┌───────┐┌───────┐  │      │   │    │ ┌───────┐┌───────┐  │      │     │    │ ┌───────┐┌───────┐  │      │      │    │ ┌───────┐┌───────┐  │      │ 
│    │ ├───────┤├───────┤  │      │   │    │ ├───────┤├───────┤  │      │     │    │ ├───────┤├───────┤  │      │      │    │ ├───────┤├───────┤  │      │ 
│    │ ├───────┤├───────┤  │      │   │    │ ├───────┤├───────┤  │      │     │    │ ├───────┤├───────┤  │      │      │    │ ├───────┤├───────┤  │      │ 
│    │ ├───────┤├───────┤  │      │   │    │ ├───────┤├───────┤  │      │     │    │ ├───────┤├───────┤  │      │      │    │ ├───────┤├───────┤  │      │ 
│    │ └───────┘└───────┘  │      │   │    │ └───────┘└───────┘  │      │     │    │ └───────┘└───────┘  │      │      │    │ └───────┘└───────┘  │      │ 
│    │                     │      │   │    │                     │      │     │    │                     │      │      │    │                     │      │ 
│    └─────────────────────┘      │   │    └─────────────────────┘      │     │    └─────────────────────┘      │      │    └─────────────────────┘      │ 
│                                 │   │                                 │     │                                 │      │                                 │ 
└─────────────────────────────────┘   └─────────────────────────────────┘     └─────────────────────────────────┘      └─────────────────────────────────┘ 
 ┌─────────────────────────────────┐   ┌─────────────────────────────────┐     ┌─────────────────────────────────┐      ┌─────────────────────────────────┐
 │                                 │   │                                 │     │                                 │      │                                 │
 │         ┌───────────┐           │   │         ┌───────────┐           │     │         ┌───────────┐           │      │         ┌───────────┐           │
 │         │  Fetch/   │           │   │         │  Fetch/   │           │     │         │  Fetch/   │           │      │         │  Fetch/   │           │
 │         │  Decode   │           │   │         │  Decode   │           │     │         │  Decode   │           │      │         │  Decode   │           │
 │         └───────────┘           │   │         └───────────┘           │     │         └───────────┘           │      │         └───────────┘           │
 │         ┌───────────┐           │   │         ┌───────────┐           │     │         ┌───────────┐           │      │         ┌───────────┐           │
 │         │    ALU    │           │   │         │    ALU    │           │     │         │    ALU    │           │      │         │    ALU    │           │
 │         │ (Execute) │           │   │         │ (Execute) │           │     │         │ (Execute) │           │      │         │ (Execute) │           │
 │         └───────────┘           │   │         └───────────┘           │     │         └───────────┘           │      │         └───────────┘           │
 │                                 │   │                                 │     │                                 │      │                                 │
 │    ┌─────────────────────┐      │   │    ┌─────────────────────┐      │     │    ┌─────────────────────┐      │      │    ┌─────────────────────┐      │
 │    │      Execution      │      │   │    │      Execution      │      │     │    │      Execution      │      │      │    │      Execution      │      │
 │    │       Context       │      │   │    │       Context       │      │     │    │       Context       │      │      │    │       Context       │      │
 │    │ ┌───────┐┌───────┐  │      │   │    │ ┌───────┐┌───────┐  │      │     │    │ ┌───────┐┌───────┐  │      │      │    │ ┌───────┐┌───────┐  │      │
 │    │ ├───────┤├───────┤  │      │   │    │ ├───────┤├───────┤  │      │     │    │ ├───────┤├───────┤  │      │      │    │ ├───────┤├───────┤  │      │
 │    │ ├───────┤├───────┤  │      │   │    │ ├───────┤├───────┤  │      │     │    │ ├───────┤├───────┤  │      │      │    │ ├───────┤├───────┤  │      │
 │    │ ├───────┤├───────┤  │      │   │    │ ├───────┤├───────┤  │      │     │    │ ├───────┤├───────┤  │      │      │    │ ├───────┤├───────┤  │      │
 │    │ └───────┘└───────┘  │      │   │    │ └───────┘└───────┘  │      │     │    │ └───────┘└───────┘  │      │      │    │ └───────┘└───────┘  │      │
 │    │                     │      │   │    │                     │      │     │    │                     │      │      │    │                     │      │
 │    └─────────────────────┘      │   │    └─────────────────────┘      │     │    └─────────────────────┘      │      │    └─────────────────────┘      │
 │                                 │   │                                 │     │                                 │      │                                 │
 └─────────────────────────────────┘   └─────────────────────────────────┘     └─────────────────────────────────┘      └─────────────────────────────────┘
 ┌─────────────────────────────────┐   ┌─────────────────────────────────┐     ┌─────────────────────────────────┐      ┌─────────────────────────────────┐ 
│                                 │   │                                 │     │                                 │      │                                 │ 
│         ┌───────────┐           │   │         ┌───────────┐           │     │         ┌───────────┐           │      │         ┌───────────┐           │ 
│         │  Fetch/   │           │   │         │  Fetch/   │           │     │         │  Fetch/   │           │      │         │  Fetch/   │           │ 
│         │  Decode   │           │   │         │  Decode   │           │     │         │  Decode   │           │      │         │  Decode   │           │ 
│         └───────────┘           │   │         └───────────┘           │     │         └───────────┘           │      │         └───────────┘           │ 
│         ┌───────────┐           │   │         ┌───────────┐           │     │         ┌───────────┐           │      │         ┌───────────┐           │ 
│         │    ALU    │           │   │         │    ALU    │           │     │         │    ALU    │           │      │         │    ALU    │           │ 
│         │ (Execute) │           │   │         │ (Execute) │           │     │         │ (Execute) │           │      │         │ (Execute) │           │ 
│         └───────────┘           │   │         └───────────┘           │     │         └───────────┘           │      │         └───────────┘           │ 
│                                 │   │                                 │     │                                 │      │                                 │ 
│    ┌─────────────────────┐      │   │    ┌─────────────────────┐      │     │    ┌─────────────────────┐      │      │    ┌─────────────────────┐      │ 
│    │      Execution      │      │   │    │      Execution      │      │     │    │      Execution      │      │      │    │      Execution      │      │ 
│    │       Context       │      │   │    │       Context       │      │     │    │       Context       │      │      │    │       Context       │      │ 
│    │ ┌───────┐┌───────┐  │      │   │    │ ┌───────┐┌───────┐  │      │     │    │ ┌───────┐┌───────┐  │      │      │    │ ┌───────┐┌───────┐  │      │ 
│    │ ├───────┤├───────┤  │      │   │    │ ├───────┤├───────┤  │      │     │    │ ├───────┤├───────┤  │      │      │    │ ├───────┤├───────┤  │      │ 
│    │ ├───────┤├───────┤  │      │   │    │ ├───────┤├───────┤  │      │     │    │ ├───────┤├───────┤  │      │      │    │ ├───────┤├───────┤  │      │ 
│    │ ├───────┤├───────┤  │      │   │    │ ├───────┤├───────┤  │      │     │    │ ├───────┤├───────┤  │      │      │    │ ├───────┤├───────┤  │      │ 
│    │ └───────┘└───────┘  │      │   │    │ └───────┘└───────┘  │      │     │    │ └───────┘└───────┘  │      │      │    │ └───────┘└───────┘  │      │ 
│    │                     │      │   │    │                     │      │     │    │                     │      │      │    │                     │      │ 
│    └─────────────────────┘      │   │    └─────────────────────┘      │     │    └─────────────────────┘      │      │    └─────────────────────┘      │ 
│                                 │   │                                 │     │                                 │      │                                 │ 
└─────────────────────────────────┘   └─────────────────────────────────┘     └─────────────────────────────────┘      └─────────────────────────────────┘ 
 ┌─────────────────────────────────┐   ┌─────────────────────────────────┐     ┌─────────────────────────────────┐      ┌─────────────────────────────────┐
 │                                 │   │                                 │     │                                 │      │                                 │
 │         ┌───────────┐           │   │         ┌───────────┐           │     │         ┌───────────┐           │      │         ┌───────────┐           │
 │         │  Fetch/   │           │   │         │  Fetch/   │           │     │         │  Fetch/   │           │      │         │  Fetch/   │           │
 │         │  Decode   │           │   │         │  Decode   │           │     │         │  Decode   │           │      │         │  Decode   │           │
 │         └───────────┘           │   │         └───────────┘           │     │         └───────────┘           │      │         └───────────┘           │
 │         ┌───────────┐           │   │         ┌───────────┐           │     │         ┌───────────┐           │      │         ┌───────────┐           │
 │         │    ALU    │           │   │         │    ALU    │           │     │         │    ALU    │           │      │         │    ALU    │           │
 │         │ (Execute) │           │   │         │ (Execute) │           │     │         │ (Execute) │           │      │         │ (Execute) │           │
 │         └───────────┘           │   │         └───────────┘           │     │         └───────────┘           │      │         └───────────┘           │
 │                                 │   │                                 │     │                                 │      │                                 │
 │    ┌─────────────────────┐      │   │    ┌─────────────────────┐      │     │    ┌─────────────────────┐      │      │    ┌─────────────────────┐      │
 │    │      Execution      │      │   │    │      Execution      │      │     │    │      Execution      │      │      │    │      Execution      │      │
 │    │       Context       │      │   │    │       Context       │      │     │    │       Context       │      │      │    │       Context       │      │
 │    │ ┌───────┐┌───────┐  │      │   │    │ ┌───────┐┌───────┐  │      │     │    │ ┌───────┐┌───────┐  │      │      │    │ ┌───────┐┌───────┐  │      │
 │    │ ├───────┤├───────┤  │      │   │    │ ├───────┤├───────┤  │      │     │    │ ├───────┤├───────┤  │      │      │    │ ├───────┤├───────┤  │      │
 │    │ ├───────┤├───────┤  │      │   │    │ ├───────┤├───────┤  │      │     │    │ ├───────┤├───────┤  │      │      │    │ ├───────┤├───────┤  │      │
 │    │ ├───────┤├───────┤  │      │   │    │ ├───────┤├───────┤  │      │     │    │ ├───────┤├───────┤  │      │      │    │ ├───────┤├───────┤  │      │
 │    │ └───────┘└───────┘  │      │   │    │ └───────┘└───────┘  │      │     │    │ └───────┘└───────┘  │      │      │    │ └───────┘└───────┘  │      │
 │    │                     │      │   │    │                     │      │     │    │                     │      │      │    │                     │      │
 │    └─────────────────────┘      │   │    └─────────────────────┘      │     │    └─────────────────────┘      │      │    └─────────────────────┘      │
 │                                 │   │                                 │     │                                 │      │                                 │
 └─────────────────────────────────┘   └─────────────────────────────────┘     └─────────────────────────────────┘      └─────────────────────────────────┘

1⃣ 多核处理器:四核与十六核

你画的图展示了多核 CPU 的核心利用情况:

  • 四核 CPU:一次可以计算四个数组元素的结果,每个核心独立执行自己的指令流。
  • 十六核 CPU:一次可以计算十六个数组元素,每个核心都有独立的 Fetch/Decode/ALU/Execution 单元。
  • 原理
    • 核心数增加 → 可以同时处理更多的独立任务。
    • 每个核心可能比老式单核复杂核心慢一点(简化设计),但通过并行计算,总体吞吐量提高。
      速度分析(假设每核是 0.75x 单核速度):
  • 单核:1 × 1 = 1(基准)
  • 四核:4 × 0.75 = 3 → 总体速度 3 倍
  • 十六核:16 × 0.75 = 12 → 总体速度 12 倍

2⃣ 多核示例:Intel Coffee Lake Core i7 (2017)

  • CPU 类型:桌面处理器
  • 核心数:6 个核心(hexa-core)
  • 应用:多线程程序可以同时利用 6 个核心计算,提高性能
  • 特点:每个核心相对独立,具有自己的指令流水线和执行单元

3⃣ GPU 示例:NVIDIA GTX 1080 (2016)

  • GPU 核心数:20 个“SM”(Streaming Multiprocessor,流式多处理器)
  • 特点
    • 每个 SM 内部又有很多 ALU,可以执行大量线程
    • 适合 大规模数据并行计算(如矩阵运算、图像处理、科学计算)
  • 对比 CPU:GPU 核心数量多得多,但单个核心较简单,单线程性能低;优势在于高并行吞吐量。

4⃣ 总结多核思路

  1. 单核时代:所有性能都靠复杂核心提升(Out-of-order、分支预测、缓存优化)
  2. 多核时代
    • 利用更多简单核心执行独立任务
    • 每个核心负责一个或几个独立的数据元素
    • 总体吞吐量大幅提升
  3. CPU vs GPU
    • CPU 核心少,但单核强
    • GPU 核心多,但单核弱,适合海量并行
      核心理解

增加核心数 → 可以同时处理更多任务,程序必须表达并行性,才能利用硬件。

┌───────────────────────────────────────────────────┐
│          ┌───────────────────────────┐            │
│          │         Fetch/            │            │
│          │         Decode            │            │
│          └───────────────────────────┘            │
│          ┌─────┐ ┌─────┐ ┌─────┐┌─────┐           │
│          │ALU 0│ │ALU 1│ │ALU 2││ALU 3│           │
│          └─────┘ └─────┘ └─────┘└─────┘           │
│          ┌─────┐ ┌─────┐ ┌─────┐┌─────┐           │
│          │ALU 4│ │ALU 5│ │ALU 6││ALU 7│           │
│          └─────┘ └─────┘ └─────┘└─────┘           │
│                                                   │
│                   Execution                       │
│                    Context                        │
│    ┌─────────────────────────────────────────┐    │
│    │   ┌─┬─┬─┬─┬─┬─┬─┬─┐┌─┬─┬─┬─┬─┬─┬─┬─┐    │    │
│    │   │ │ │ │ │ │ │ │ ││ │ │ │ │ │ │ │ │    │    │
│    │   │ │ │ │ │ │ │ │ ││ │ │ │ │ │ │ │ │    │    │
│    │   └─┴─┴─┴─┴─┴─┴─┴─┘└─┴─┴─┴─┴─┴─┴─┴─┘    │    │
│    │   ┌─┬─┬─┬─┬─┬─┬─┬─┐┌─┬─┬─┬─┬─┬─┬─┬─┐    │    │
│    │   │ │ │ │ │ │ │ │ ││ │ │ │ │ │ │ │ │    │    │
│    │   │ │ │ │ │ │ │ │ ││ │ │ │ │ │ │ │ │    │    │
│    │   └─┴─┴─┴─┴─┴─┴─┴─┘└─┴─┴─┴─┴─┴─┴─┴─┘    │    │
│    │   ┌─┬─┬─┬─┬─┬─┬─┬─┐┌─┬─┬─┬─┬─┬─┬─┬─┐    │    │
│    │   │ │ │ │ │ │ │ │ ││ │ │ │ │ │ │ │ │    │    │
│    │   │ │ │ │ │ │ │ │ ││ │ │ │ │ │ │ │ │    │    │
│    │   └─┴─┴─┴─┴─┴─┴─┴─┘└─┴─┴─┴─┴─┴─┴─┴─┘    │    │
│    │   ┌─┬─┬─┬─┬─┬─┬─┬─┐┌─┬─┬─┬─┬─┬─┬─┬─┐    │    │
│    │   │ │ │ │ │ │ │ │ ││ │ │ │ │ │ │ │ │    │    │
│    │   │ │ │ │ │ │ │ │ ││ │ │ │ │ │ │ │ │    │    │
│    │   └─┴─┴─┴─┴─┴─┴─┴─┘└─┴─┴─┴─┴─┴─┴─┴─┘    │    │
│    └─────────────────────────────────────────┘    │
│                                                   │
└───────────────────────────────────────────────────┘

非常好 ,你现在看到的这部分内容实际上是并行计算从“多核并行”进一步发展到“数据并行(SIMD)”的核心思想。下面我帮你完整、清晰地用解释每个要点。

一、Data-parallel(数据并行)表达式

代码:

void sinx(int N, int terms, float* x, float* result) {
    // 声明:每次循环迭代都是独立的
    forall (int i from 0 to N-1) {
        float value = x[i];
        float numer = x[i] * x[i] * x[i];
        int denom = 6;      // 3!
        int sign = -1;
        for (int j = 1; j <= terms; j++) {
            value += sign * numer / denom;
            numer *= x[i] * x[i];
            denom *= (2*j+2) * (2*j+3);
            sign *= -1;
        }
        result[i] = value;
    }
}

含义:

  • forall 表示这个循环的 每个迭代是独立的,可以 并行执行
  • 也就是说:
    • i=0i=1i=2 … 之间 互不依赖
    • 每个循环体计算 sin(x[i])
    • 所以可以同时在多个核心(CPU 或 GPU)上执行。
      关键点:

程序员显式告诉编译器:这些循环迭代没有数据依赖,可以并行。

二、并行粒度(Parallelism granularity)

这段代码的并行性是 “跨循环迭代的”(across iterations)。
也就是说:

  • 每次迭代做的事情完全相同(计算一个输入值的正弦函数)。
  • 所以这类问题属于 数据并行(Data Parallelism)
  • 编译器可以自动把这个循环转化成多线程,甚至 SIMD 指令。

三、进一步优化的思路:SIMD(单指令多数据)

背景:

即使在单个核心内部,我们也可以通过“同时计算多个数据元素”来加速。

关键思想:

一条指令,多个数据(Single Instruction, Multiple Data)
例如:

result[i..i+3] = sin(x[i..i+3])

CPU 不再逐个执行,而是一次执行 4 个(或 8 个、16 个)元素。

硬件原理图解释:

┌───────────────────────────────────────────────────┐
│          ┌───────────────────────────┐            │
│          │       Fetch/Decode         │ ← 一条指令 │
│          └───────────────────────────┘            │
│          ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐          │
│          │ALU 0│ │ALU 1│ │ALU 2│ │ALU 3│ ← 同时执行 │
│          └─────┘ └─────┘ └─────┘ └─────┘          │
│                   (Execution Context)            │
└───────────────────────────────────────────────────┘
  • 只有一个指令流(fetch/decode 一次)
  • 但同时有多个算术逻辑单元(ALU)
  • 每个 ALU 对不同数据执行相同操作

四、举个具体例子(SIMD 实际等价)

假设我们有:

for (int i = 0; i < 8; i++) 
    y[i] = a[i] + b[i];

在 SIMD 硬件下,CPU 可能一次执行:

// 载入 8 个浮点数
ymm0 = load(a[0..7]);
ymm1 = load(b[0..7]);
// SIMD 加法
ymm2 = add(ymm0, ymm1);
// 存回结果
store(y[0..7], ymm2);

等价于执行 8 次加法,但只需 1 条指令。

五、SIMD vs 多核并行

对比点多核并行 (Multi-core)SIMD 并行
并行单位不同线程(不同指令流)不同数据(相同指令流)
指令流数量多条一条
典型实现pthreads, OpenMP, std::threadSSE, AVX, NEON
适用场景任务级并行(不同工作)数据级并行(相同操作)
例子同时计算 sin(x[0]) 和 sin(x[1]) 在不同核上在同一个核上同时计算 sin(x[0…7])

总结一句话

“forall” 表示循环迭代相互独立,可并行。
多核并行是“多个指令流同时工作”;
SIMD 是“一个指令流同时处理多个数据”。
两者结合,就是现代高性能 CPU/GPU 的基础。

CMU 15-418/618 并行计算课程(Fall 2018),讲的是如何把一个标量(scalar)程序转换为使用 AVX SIMD 指令的向量化(vectorized)程序。我们来详细解释每一部分的含义。

一、标量版本(Scalar program)

void sinx(int N, int terms, float* x, float* result) {
    for (int i = 0; i < N; i++) {
        float value = x[i];
        float numer = x[i] * x[i] * x[i];
        int denom = 6;       // 3!
        int sign = -1;
        for (int j = 1; j <= terms; j++) {
            value += sign * numer / denom;
            numer *= x[i] * x[i];
            denom *= (2*j+2)*(2*j+3);
            sign *= -1;
        }
        result[i] = value;
    }
}

含义:

  • 这个函数用 Taylor 级数展开 来近似计算 sin(x)
    sin⁡(x)≈x−x33!+x55!−x77!+… \sin(x) \approx x - \frac{x^3}{3!} + \frac{x^5}{5!} - \frac{x^7}{7!} + \dots sin(x)x3!x3+5!x57!x7+
  • terms 控制展开的阶数(越多越精确)。
  • 循环逐个计算数组 x[] 中每个元素的 sin(x)

特点:

  • 标量执行(scalar execution):每次只处理一个 float
  • 所有计算都是通过普通的浮点寄存器完成。
  • 伪汇编指令:
    ld     r0, addr[r1]
    mul    r1, r0, r0
    mul    r1, r1, r0
    ...
    st     addr[r2], r0
    
    说明:每次加载一个 x[i],计算,再存入结果。

二、向量化版本(Vector program using AVX intrinsics)

代码

#include <immintrin.h>
void sinx(int N, int terms, float* x, float* result) {
    float three_fact = 6; // 3!
    for (int i = 0; i < N; i += 8) {
        __m256 origx = _mm256_load_ps(&x[i]);
        __m256 value = origx;
        __m256 numer = _mm256_mul_ps(origx, _mm256_mul_ps(origx, origx));
        __m256 denom = _mm256_broadcast_ss(&three_fact);
        int sign = -1;
        for (int j = 1; j <= terms; j++) {
            __m256 tmp = _mm256_div_ps(
                _mm256_mul_ps(_mm256_set1_ps((float)sign), numer),
                denom
            );
            value = _mm256_add_ps(value, tmp);
            numer = _mm256_mul_ps(numer, _mm256_mul_ps(origx, origx));
            denom = _mm256_mul_ps(denom, _mm256_set1_ps((2*j+2)*(2*j+3)));
            sign *= -1;
        }
        _mm256_store_ps(&result[i], value);
    }
}

含义:

  • 使用 AVX 256-bit 向量寄存器(__m256,一次可以处理 8 个 float
  • _mm256_load_ps() 从内存加载 8 个连续的 float
  • _mm256_mul_ps()_mm256_add_ps() 等操作都是 8 路并行执行
  • _mm256_set1_ps() 用来广播一个标量到所有 8 个通道中。
  • _mm256_store_ps() 把结果一次性写回内存。

特点:

对比项标量版本向量版本
每次处理数据量1 个浮点数8 个浮点数
使用寄存器标量寄存器(32 位)向量寄存器(256 位)
指令执行方式顺序执行SIMD 并行执行
汇编示例mul r1, r0, r0vmulps xmm1, xmm0, xmm0
性能理论上可快 8 倍

对应汇编层面:

AVX 版本编译后类似:

vloadps      xmm0, addr[r1]
vmulps       xmm1, xmm0, xmm0
vmulps       xmm1, xmm1, xmm0
...
vstoreps     addr[r2], xmm0
  • vloadps:向量加载 8 个 float
  • vmulps:向量乘法(8 个元素同时相乘)
  • vstoreps:向量存储结果

三、总结与理解

概念说明
Scalar program(标量程序)一次只处理一个浮点数,使用普通浮点寄存器。
Vector program(向量化程序)利用 SIMD(单指令多数据)并行计算,一次处理 8 个 float。
AVX (Advanced Vector Extensions)Intel 的 SIMD 指令集扩展,支持 256 位寄存器。
性能收益理论上可提升最多 8 倍性能(取决于内存带宽、分支预测等)。
关键思想把独立的数据操作批量并行化,而不改变算法逻辑。
如果你希望我帮你:
改成 AVX2 + FMA(融合乘加) 优化版本,
或者生成一个 标量 vs SIMD 运行时间对比 的完整 C++ 测试程序,

CMU 15-418/618(Parallel Computer Architecture and Programming) 课程讲义中关于 数据并行(data parallelism)与条件分支(conditional execution) 的经典讲解。

下面我们分阶段来详细解释,帮你彻底理解这些图表背后的思想。

第一部分:Data-parallel expression —— 数据并行表达式

原文:

void sinx(int N, int terms, float* x, float* result) {
    // declare independent loop iterations
    forall (int i from 0 to N-1) {
        float value = x[i];
        float numer = x[i] * x[i] * x[i];
        int denom = 6;    // 3!
        int sign = -1;
        for (int j = 1; j <= terms; j++) {
            value += sign * numer / denom;
            numer *= x[i] * x[i];
            denom *= (2*j+2) * (2*j+3);
            sign *= -1;
        }
        result[i] = value;
    }
}

理解:

  • forall 是一个虚构的并行语言关键字(不是 C++ 的一部分)。
  • 意思是:

    “对每个 i 的循环迭代都是独立的(independent iterations)”,
    所以编译器可以自动并行化这些循环。

要点:

概念含义
forall明确告诉编译器:循环中每次迭代彼此独立,没有依赖关系。
独立循环每个 i 的结果只依赖于自己的 x[i],不会影响其他元素。
编译器优化编译器可以自动生成多核(multi-core)并行代码,以及在每个核内使用 SIMD(Single Instruction, Multiple Data)指令。
效果一次计算很多数据点,比如用 AVX2/AVX-512 一次处理 8/16/32 个 x[i]

第二部分:What about conditional execution? —— 条件执行怎么办?

原文大意:

如果循环体里有条件语句(if / else),SIMD 并行执行时会发生什么?

示例代码:

float x = A[i];
if (x > 0) {
    float tmp = exp(x, 5.f);
    tmp *= kMyConst1;
    x = tmp + kMyConst2;
} else {
    float tmp = kMyConst1;
    x = 2.f * tmp;
}
result[i] = x;

理解:

  • 每个 x = A[i] 可能不同,有的满足 x > 0,有的 x <= 0
  • 当这些数据同时放入 8 个 ALU(AVX 8 路并行)中执行时:
    • 有的通道需要执行 if 部分;
    • 有的通道需要执行 else 部分;
    • 它们不能同时执行不同路径!

第三部分:Mask (discard) output of ALU —— 掩码执行机制

原文中提到的 “Mask (discard) output” 是 掩码执行(masked execution) 或称 谓词执行(predicated execution)

图示理解:

ALU 通道元素条件结果
ALU 1A[0]True
ALU 2A[1]True
ALU 3A[2]False
ALU 4A[3]True
ALU 5A[4]False
ALU 6A[5]False
ALU 7A[6]False
ALU 8A[7]False
→ 只有部分通道执行有效操作,其他通道被“屏蔽(masked out)”。

解释:

  • SIMD 掩码执行 的机制是:

    对于不满足条件的元素,仍然在执行,但结果被丢弃(masked out)。

  • 也就是说所有 8 个 ALU 仍然在跑,只是其中一些的计算结果不保留。
  • 这就导致了性能浪费:

最坏情况下:8 个 ALU 里只有 1 个在干活,性能只有 1/8。

第四部分:After branch —— 分支执行完成后恢复全速

原文:“After branch: continue at full performance”
意思是:

  • 一旦 if/else 的两条路径都执行完,
  • 程序重新回到没有条件的统一路径,
  • 所有 ALU 又可以同时工作,
  • 性能恢复到峰值(full throughput)。

总结对照表

阶段现象解释
Data-parallel expression使用 forall 并行循环每个迭代独立,编译器可并行化
Conditional executionif 判断引入控制流分歧不同数据走不同分支,破坏并行
Mask (discard) output使用掩码执行仍然执行全部 ALU,但屏蔽部分结果
After branch恢复统一路径所有 ALU 重新并行,恢复满速执行

举个直观例子

假设我们有 8 个输入:

A = [-1, 2, -3, 4, -5, 6, -7, 8]

执行逻辑:

if (x > 0) {
    x = x * 10;
} else {
    x = 0;
}

在 SIMD 下:

  • 第 2、4、6、8 个元素满足条件;
  • 其它为假;
  • 所以 GPU/CPU SIMD 单元需要:
    • 先执行所有 if 路径(mask 掩码控制谁生效);
    • 再执行所有 else 路径;
    • 最后合并结果。
      性能取决于条件分支的分布。
      如果 8 个数据都同一方向(全 True 或全 False),性能最好;
      如果 True/False 混杂,就浪费计算能力。

总体理解

这节课想让你理解:

数据并行编程(Data Parallelism)最怕的就是控制流分歧(Control Flow Divergence)。
在 GPU、SIMD、向量指令中尤其明显:

  • 分支越多,掩码越稀疏;
  • 实际有效的 ALU 越少;
  • 性能下降。
    因此,优秀的 SIMD / GPU 程序员会尽量避免分支,通过数学形式(如混合、blend、select)重写代码:
x = (x > 0) ? f1(x) : f2(x);
// 可以改写成:
__m256 mask = _mm256_cmp_ps(x, _mm256_set1_ps(0.0f), _CMP_GT_OQ);
x = _mm256_blendv_ps(f2(x), f1(x), mask);

这样就没有真正的分支,所有通道都在工作,性能最高。

一、Terminology — 基本术语

Instruction stream coherence(指令流一致性 / 指令流相干性)

  • 定义:
    在 SIMD(单指令多数据)执行中,所有数据元素同时执行相同的指令序列
    换句话说,所有 ALU(算术逻辑单元)都在执行同一条指令,只是作用在不同的数据上。
  • 要点:
    • “指令流一致” 意味着不同的数据路径上执行完全相同的操作。
    • 这是 高效利用 SIMD 资源的必要条件
      否则某些通道(lane)会空闲(即我们在之前学的“mask out”现象)。
  • 反例:
    如果不同元素因为条件判断(if/else)而执行不同的指令路径 → 指令流不一致(divergent execution) → SIMD 效率下降。

Divergent execution(发散执行)

  • 定义:
    也叫“指令流发散”,指在 SIMD 组中,不同元素需要执行不同指令的情况。
  • 影响:
    • 破坏 SIMD 执行的统一性;
    • 部分 ALU 必须停下来等待其他路径执行;
    • 实际并行效率可能只有理论峰值的 1/4、1/8、甚至 1/32。

注意区别:

不要把 “instruction stream coherence(指令流一致性)” 和 “cache coherence(缓存一致性)” 搞混。

  • 前者讨论的是 “是否执行同一条指令”;
  • 后者讨论的是 “多核间共享内存数据是否保持一致”。

二、SIMD execution on modern CPUs — 现代 CPU 上的 SIMD 执行

SSE 与 AVX 指令集

指令集向量宽度能并行处理的数据
SSE128 位4×32-bit float 或 2×64-bit double
AVX256 位8×32-bit float 或 4×64-bit double
AVX-512512 位(部分 CPU)16×32-bit float 或 8×64-bit double
所以:一个 AVX 指令可以同时处理 8 个 float 运算。

指令来源

SIMD 指令由编译器生成,有三种方式:

  1. 显式请求(explicit SIMD)
    • 程序员直接写 SIMD 内在函数(intrinsics),如 _mm256_add_ps
    • 或者在编译器里启用自动向量化(例如 -O3 -march=native)。
  2. 语言语义传达(parallel semantics)
    • 用并行语言结构(如 forall#pragma omp simd)告诉编译器:循环可并行。
  3. 依赖分析自动推断(dependency analysis)
    • 编译器自动分析循环依赖关系;
    • 如果确认各迭代独立,则自动生成 SIMD 指令;
    • 但这是困难问题:即使最好的编译器,对复杂 C/C++ 代码也常常分析失败。

术语:“explicit SIMD” 显式 SIMD

意味着 SIMD 并行化在编译期完成,编译后你能在汇编中看到对应的 SIMD 指令。
例如:

vaddps  ymm0, ymm1, ymm2   // AVX 加法
vmulps  ymm3, ymm4, ymm5   // AVX 乘法
vstoreps [addr], ymm0      // 存储结果

三、SIMD execution on GPUs — GPU 上的 SIMD 执行

“Implicit SIMD” (隐式 SIMD)

GPU 和 CPU 不同:

  • 编译器只生成标量代码(scalar instructions)
  • 但是 硬件执行时自动同时运行多个实例
    即:
execute(my_function, N);

表示执行 my_function N 次(每个实例对应不同的数据元素)。
硬件负责:

  • 让多个实例在同一时间执行相同指令;
  • 在不同数据上同时计算(SIMD 化)。
    也就是说,GPU 的硬件架构本身是数据并行的(data-parallel)

GPU 的 SIMD 宽度

  • 大多数现代 GPU 的 SIMD 宽度为 8 到 32
  • 这意味着:
    • 一个“warp”(在 NVIDIA 中)包含 32 个线程;
    • 每次执行一条相同指令;
    • 如果分支发散,只执行部分线程;
    • 最差情况下,性能可能仅为峰值的 1/32

四、具体硬件例子

Intel Core i7 (示例配置)

  • 4 个物理核心;
  • 每个核心有 8 个 SIMD ALU;
  • 使用 AVX 指令集;
  • 所以总共可以同时并行计算约 4 × 8 = 32 个浮点元素。

NVIDIA GTX 480 (GPU)

  • 15 个计算核心;
  • 每个核心有 32 个 SIMD ALU;
  • 理论峰值性能:1.3 TFLOPS(每秒 1.3 万亿次浮点运算)
  • 这就是 GPU 能爆炸性提升数据并行任务性能的原因。

五、Summary — 并行执行总结

并行形式描述控制方式
Multi-core 多核同时运行多个不同的线程(不同的指令流)程序员显式创建线程(如 pthread、OpenMP)
SIMD 向量化同一条指令同时作用于多个数据通常由编译器(或硬件)实现
Superscalar 超标量同一条指令流中的多条指令在硬件中动态并行执行由 CPU 自动实现,程序员不可见

重点区别表

类型并行层级谁控制?是否执行相同指令流?
多核(Multi-core)线程级程序员不同核心可执行不同程序
SIMD数据级编译器/硬件所有 ALU 执行同一指令
超标量(Superscalar)指令级CPU 硬件同一线程内部并行执行多条指令

总结一句话

SIMD 并行要求“指令流一致”,多核并行不要求。

GPU 采用隐式 SIMD,CPU 采用显式 SIMD。

发散(divergence)导致 SIMD 执行效率下降。

而多核并行因为每个核能独立执行不同程序,不受此限制。

               ┌─────────────────────────────────────────────────────┐                                                  
               │    ┌───────────────────────────┐     ┌───────────┐  │                                                  
               │    │         Fetch/            │     │           │  │                                                  
               │    │         Decode            │     │ L1 cache  │  │                                                  
               │    └───────────────────────────┘     │           │  │                                                  
               │    ┌─────┐ ┌─────┐ ┌─────┐┌─────┐    │ (32 KB)   │  │                                                  
               │    │ALU 0│ │ALU 1│ │ALU 2││ALU 3│    │           │  │                                                  
               │    └─────┘ └─────┘ └─────┘└─────┘    └───────────┘  │                                                  
               │    ┌─────┐ ┌─────┐ ┌─────┐┌─────┐                   │   ┌────────────┐                                 
               │    │ALU 4│ │ALU 5│ │ALU 6││ALU 7│    ┌───────────┐  │   │            │                                 
               │    └─────┘ └─────┘ └─────┘└─────┘    │           │  │   │            │                                 
               │ ┌───────────────────────────────────┐│           │  │   │            │                                 
               │ │           Execution               ││  L2 cache │  │   │            │                                 
 Core 1        │ │            Context                ││           │  │   │            │                                 
               │ │                                   ││ (256 KB)  │  │   │            │               ┌────────────────┐
               │ │┌─┬─┬─┬─┬─┬─┬─┬─┐ ┌─┬─┬─┬─┬─┬─┬─┬─┐││           │  │   │            │               │                │
               │ │├─┼─┼─┼─┼─┼─┼─┼─┤ ├─┼─┼─┼─┼─┼─┼─┼─┤││           │  │   │            │               │                │
               │ │├─┼─┼─┼─┼─┼─┼─┼─┤ ├─┼─┼─┼─┼─┼─┼─┼─┤││           │  │   │            │               │                │
               │ │├─┼─┼─┼─┼─┼─┼─┼─┤ ├─┼─┼─┼─┼─┼─┼─┼─┤│└───────────┘  │   │            │               │                │
               │ │└─┴─┴─┴─┴─┴─┴─┴─┘ └─┴─┴─┴─┴─┴─┴─┴─┘│               │   │            │               │                │
               │ └───────────────────────────────────┘               │   │            │               │                │
               └─────────────────────────────────────────────────────┘   │            │               │                │
                                  ●                                      │            │               │                │
                                                                         │            │   25 GB/sec   │    Memory      │
                                                                         │            │               │                │
                                  ●                                      │            ◀══════════════▶│   DDR3 DRAM    │
                                                                         │   L3 cache │               │                │
                                                                         │            │               │   (Gigabytes)  │
                                  ●                                      │  (8 MB)    │               │                │
                                                                         │            │               │                │
                ┌─────────────────────────────────────────────────────┐  │            │               │                │
                │    ┌───────────────────────────┐     ┌───────────┐  │  │            │               │                │
                │    │         Fetch/            │     │           │  │  │            │               │                │
                │    │         Decode            │     │ L1 cache  │  │  │            │               └────────────────┘
  Core N        │    └───────────────────────────┘     │           │  │  │            │                                 
                │    ┌─────┐ ┌─────┐ ┌─────┐┌─────┐    │ (32 KB)   │  │  │            │                                 
                │    │ALU 0│ │ALU 1  │ALU 2││ALU 3│    │           │  │  │            │                                 
                │    └─────┘ └─────┘ └─────┘└─────┘    └───────────┘  │  │            │                                 
                │    ┌─────┐ ┌─────┐ ┌─────┐┌─────┐                   │  │            │                                 
                │    │ALU 4│ │ALU 5│ │ALU 6││ALU 7│    ┌───────────┐  │  │            │                                 
                │    └─────┘ └─────┘ └─────┘└─────┘    │           │  │  │            │                                 
                │ ┌───────────────────────────────────┐│           │  │  │            │                                 
                │ │           Execution               ││  L2 cache │  │  └────────────┘                                 
                │ │            Context                ││           │  │                                                 
                │ │                                   ││ (256 KB)  │  │                                                 
                │ │┌─┬─┬─┬─┬─┬─┬─┬─┐ ┌─┬─┬─┬─┬─┬─┬─┬─┐││           │  │                                                 
                │ │├─┼─┼─┼─┼─┼─┼─┼─┤ ├─┼─┼─┼─┼─┼─┼─┼─┤││           │  │                                                 
                │ │├─┼─┼─┼─┼─┼─┼─┼─┤ ├─┼─┼─┼─┼─┼─┼─┼─┤││           │  │                                                 
                │ │├─┼─┼─┼─┼─┼─┼─┼─┤ ├─┼─┼─┼─┼─┼─┼─┼─┤│└───────────┘  │                                                 
                │ │└─┴─┴─┴─┴─┴─┴─┴─┘ └─┴─┴─┴─┴─┴─┴─┴─┘│               │                                                 
                │ └───────────────────────────────────┘               │                                                 
                └─────────────────────────────────────────────────────┘                                                 

这段内容来自 CMU 15-418/618(并行计算导论)课程,主要讲的是 内存访问(Part 2: accessing memory),重点是“延迟(latency)”、“带宽(bandwidth)”、“缓存(cache)”、“预取(prefetching)”和“多线程隐藏延迟(multithreading to hide latency)”。
我来帮你分段详细解释成理解:

一、基本术语:延迟与带宽

Memory latency(内存延迟)
  • 一次内存请求被服务的时间(从发出加载请求到数据到达处理器)。
  • 举例:访问一次主内存可能需要 100 个 CPU 周期(约 100 纳秒)。
  • 所以延迟 = 时间。
Memory bandwidth(内存带宽)
  • 单位时间内 内存系统能向处理器传输数据的速率。
  • 举例:20 GB/s 表示每秒钟可传输 20 GB 的数据。
  • 所以带宽 = 速率。
    类比:

延迟好比「取快递的路程时间」;带宽好比「一次能搬多少快递」。

二、Stalls(停顿)

  • 当处理器 无法继续执行下一条指令,因为它依赖于尚未完成的指令结果,就会“stall”(停顿)。
  • 访问内存是最常见的停顿原因。
    示例:
ld  r0, mem[r2]   // 从内存加载 mem[r2]
ld  r1, mem[r3]   // 从内存加载 mem[r3]
add r0, r0, r1    // 依赖前两条指令结果
  • add 不能执行,直到前两条加载完成。
  • 加载内存可能花费上百周期,CPU 就必须空等。
    所以:

内存访问延迟数据依赖CPU 停顿

三、为什么需要缓存(Cache)

现代处理器层级:

L1 cache: 32 KB   (每个核心)
L2 cache: 256 KB  (每个核心)
L3 cache: 8 MB    (共享)
主内存:  几 GB
  • 缓存是小而快的内存,存放“最近访问过的数据”。
  • L1/L2 缓存 是每个核心独有的(速度快,容量小)。
  • L3 缓存 通常是所有核心共享的(速度较慢,容量大)。
  • 主内存(DRAM) 容量大但速度慢。
    作用:

1⃣ 降低延迟(latency)
2⃣ 提高带宽(bandwidth)
3⃣ 减少 stall 的发生

四、预取(Prefetching)

Prefetching = 预先加载数据到缓存中,让 CPU 用的时候已经在缓存里。
CPU 的预取逻辑:

  1. 分析程序的访问模式(如连续访问数组)
  2. 预测下一个要访问的地址
  3. 提前发出内存加载请求
  4. 当指令真正执行时,数据已经在 cache 中了 → cache hit!
    示例:
predict value of r2, initiate load
predict value of r3, initiate load
...
ld  r0, mem[r2]
ld  r1, mem[r3]
add r0, r0, r1

优点:

  • 隐藏内存访问延迟(数据提前到达)
  • 提高缓存命中率
    缺点:
  • 如果预测错了,会:
    • 浪费带宽
    • 污染缓存(把有用数据挤掉)

五、多线程隐藏延迟(Multithreading hides stalls)

即使有预取,有时仍然有延迟 → 解决方法是:

在一个核心上交错执行多个线程(interleaving threads)。
原理:

  • 当一个线程在等数据(stalled),核心就切换去执行另一个线程。
  • 这样 ALU 永远有活干,不用空等。
    注意:
  • 多线程并不会让单个内存访问更快(延迟不变),
  • 但它能隐藏延迟(让 CPU 不闲着)。

总结一览表

概念含义目标类比
Latency(延迟)一次访问耗时减少等待跑一趟取快递的时间
Bandwidth(带宽)每秒传输多少数据提高吞吐一次能搬多少快递
Stall(停顿)CPU 因等待数据而空转尽量避免人在等快递到手
Cache(缓存)临时保存常用数据加快访问在家门口放快递柜
Prefetching(预取)提前加载数据隐藏延迟快递提前送到
Multithreading(多线程)多线程交替执行隐藏延迟一人等快递时,另一个干活
TIME                                                                                                             ┌─────────────────────────────────────────────────────────────────────────────────────────┐
                                                                                                                 │                                                                                         │
          Thread 1              Thread 2                   Thread 3                   Thread 4                   │                            ┌───────────────────────────┐                                │
   ┃     Elements 0 … 7        Elements 8 … 15            Elements 16 … 23           Elements 24 … 31            │                            │         Fetch/            │                                │
   ┃   ┌─┬─┬─┬─┬─┬─┬─┬─┐      ┌─┬─┬─┬─┬─┬─┬─┬─┐         ┌─┬─┬─┬─┬─┬─┬─┬─┐           ┌─┬─┬─┬─┬─┬─┬─┬─┐            │                            │         Decode            │                                │
   ┃   └─┴─┴─┴─┴─┴─┴─┴─┘      └─┴─┴─┴─┴─┴─┴─┴─┘         └─┴─┴─┴─┴─┴─┴─┴─┘           └─┴─┴─┴─┴─┴─┴─┴─┘            │                            └───────────────────────────┘                                │
   ┃   ┌─────────────────┐                                                                                       │                            ┌─────┐ ┌─────┐ ┌─────┐┌─────┐                               │
   ┃   │┌─┬─┬─┬─┬─┬─┬─┬─┐│                                                                                       │                            │ALU 0│ │ALU 1│ │ALU 2││ALU 3│                               │
   ┃   ││ │ │ │ │ │ │ │ ││                                                                                       │                            └─────┘ └─────┘ └─────┘└─────┘                               │
   ┃   ││ │ │ │ │ │ │ │ ││                                                                                       │                            ┌─────┐ ┌─────┐ ┌─────┐┌─────┐                               │
   ┃   │└─┴─┴─┴─┴─┴─┴─┴─┘│                                                                                       │                            │ALU 4│ │ALU 5│ │ALU 6││ALU 7│                               │
   ┃   └──────Stall──────┘   ┌─────────────────┐                                                                 │                            └─────┘ └─────┘ └─────┘└─────┘                               │
   ┃           ┃             │┌─┬─┬─┬─┬─┬─┬─┬─┐│                                                                 │     ┌───────────────────────────────────┐       ┌───────────────────────────────────┐   │
   ┃           ┃             ││ │ │ │ │ │ │ │ ││                                                                 │     │           Execution               │       │           Execution               │   │
   ┃           ┃             ││ │ │ │ │ │ │ │ ││                                                                 │     │            Context     1          │       │            Context      2         │   │
   ┃           ┃             │└─┴─┴─┴─┴─┴─┴─┴─┘│                                                                 │     │                                   │       │                                   │   │
   ┃           ▼             └──────Stall──────┘       ┌─────────────────┐                                       │     │┌─┬─┬─┬─┬─┬─┬─┬─┐ ┌─┬─┬─┬─┬─┬─┬─┬─┐│       │┌─┬─┬─┬─┬─┬─┬─┬─┐ ┌─┬─┬─┬─┬─┬─┬─┬─┐│   │
   ┃        Runnable                 ┃                 │┌─┬─┬─┬─┬─┬─┬─┬─┐│                                       │     │├─┼─┼─┼─┼─┼─┼─┼─┤ ├─┼─┼─┼─┼─┼─┼─┼─┤│       │├─┼─┼─┼─┼─┼─┼─┼─┤ ├─┼─┼─┼─┼─┼─┼─┼─┤│   │
   ┃                                 ┃                 ││ │ │ │ │ │ │ │ ││                                       │     │├─┼─┼─┼─┼─┼─┼─┼─┤ ├─┼─┼─┼─┼─┼─┼─┼─┤│       │├─┼─┼─┼─┼─┼─┼─┼─┤ ├─┼─┼─┼─┼─┼─┼─┼─┤│   │
   ┃                                 ┃                 ││ │ │ │ │ │ │ │ ││                                       │     │├─┼─┼─┼─┼─┼─┼─┼─┤ ├─┼─┼─┼─┼─┼─┼─┼─┤│       │├─┼─┼─┼─┼─┼─┼─┼─┤ ├─┼─┼─┼─┼─┼─┼─┼─┤│   │
   ┃    ┌─────────────────┐          ┃                 │└─┴─┴─┴─┴─┴─┴─┴─┘│                                       │     │└─┴─┴─┴─┴─┴─┴─┴─┘ └─┴─┴─┴─┴─┴─┴─┴─┘│       │└─┴─┴─┴─┴─┴─┴─┴─┘ └─┴─┴─┴─┴─┴─┴─┴─┘│   │
   ┃    │┌─┬─┬─┬─┬─┬─┬─┬─┐│          ▼                 └──────Stall──────┘        ┌─────────────────┐            │     └───────────────────────────────────┘       └───────────────────────────────────┘   │
   ┃    ││ │ │ │ │ │ │ │ ││       Runnable                     ┃                  │┌─┬─┬─┬─┬─┬─┬─┬─┐│            │                                                                                         │
   ┃    ││ │ │ │ │ │ │ │ ││                                    ┃                  ││ │ │ │ │ │ │ │ ││            │     ┌───────────────────────────────────┐       ┌───────────────────────────────────┐   │
   ┃    │└─┴─┴─┴─┴─┴─┴─┴─┘│                                    ┃                  ││ │ │ │ │ │ │ │ ││            │     │           Execution               │       │           Execution               │   │
   ┃    └─────────────────┘   ┌─────────────────┐              ┃                  │└─┴─┴─┴─┴─┴─┴─┴─┘│            │     │            Context     3          │       │            Context      4         │   │
   ┃          Done!           │┌─┬─┬─┬─┬─┬─┬─┬─┐│              ▼                  └──────Stall──────┘            │     │                                   │       │                                   │   │
   ┃                          ││ │ │ │ │ │ │ │ ││           Runnable                      ┃                      │     │┌─┬─┬─┬─┬─┬─┬─┬─┐ ┌─┬─┬─┬─┬─┬─┬─┬─┐│       │┌─┬─┬─┬─┬─┬─┬─┬─┐ ┌─┬─┬─┬─┬─┬─┬─┬─┐│   │
   ┃                          ││ │ │ │ │ │ │ │ ││                                         ┃                      │     │├─┼─┼─┼─┼─┼─┼─┼─┤ ├─┼─┼─┼─┼─┼─┼─┼─┤│       │├─┼─┼─┼─┼─┼─┼─┼─┤ ├─┼─┼─┼─┼─┼─┼─┼─┤│   │
   ┃                          │└─┴─┴─┴─┴─┴─┴─┴─┘│                                         ┃                      │     │├─┼─┼─┼─┼─┼─┼─┼─┤ ├─┼─┼─┼─┼─┼─┼─┼─┤│       │├─┼─┼─┼─┼─┼─┼─┼─┤ ├─┼─┼─┼─┼─┼─┼─┼─┤│   │
   ┃                          └─────────────────┘       ┌─────────────────┐               ┃                      │     │├─┼─┼─┼─┼─┼─┼─┼─┤ ├─┼─┼─┼─┼─┼─┼─┼─┤│       │├─┼─┼─┼─┼─┼─┼─┼─┤ ├─┼─┼─┼─┼─┼─┼─┼─┤│   │
   ┃                                Done!               │┌─┬─┬─┬─┬─┬─┬─┬─┐│               ▼                      │     │└─┴─┴─┴─┴─┴─┴─┴─┘ └─┴─┴─┴─┴─┴─┴─┴─┘│       │└─┴─┴─┴─┴─┴─┴─┴─┘ └─┴─┴─┴─┴─┴─┴─┴─┘│   │
   ┃                                                    ││ │ │ │ │ │ │ │ ││            Runnable                  │     └───────────────────────────────────┘       └───────────────────────────────────┘   │
   ▼                                                    ││ │ │ │ │ │ │ │ ││                                      │                                                                                         │
                                                        │└─┴─┴─┴─┴─┴─┴─┴─┘│                                      └─────────────────────────────────────────────────────────────────────────────────────────┘
                                                        └─────────────────┘        ┌─────────────────┐                                                                                                      
                                                              Done!                │┌─┬─┬─┬─┬─┬─┬─┬─┐│                                                                                                      
                                                                                   ││ │ │ │ │ │ │ │ ││                                                                                                      
                                                                                   ││ │ │ │ │ │ │ │ ││                                                                                                      
                                                                                   │└─┴─┴─┴─┴─┴─┴─┴─┘│                                                                                                      
                                                                                   └─────────────────┘                                                                                                      
                                                                                         Done!                                                                                                              

这整段内容讲的是多线程与吞吐量导向(throughput-oriented)处理器设计的核心思想,尤其是 GPU 相对于 CPU 的结构差异。下面是理解与结构化总结

一、核心思想:多线程的目的——隐藏延迟(Latency Hiding)

多线程(Multi-threading)不是为了让单个线程更快,而是为了让核心(core)在等待时不闲着

  • 当一个线程遇到高延迟操作(比如内存访问)而“停滞(stall)”时,核心可以立刻切换到其他线程继续执行
  • 这样做不能减少延迟(latency),但能隐藏延迟(让 ALU 不空闲),提高整体吞吐量(throughput)
    类比:
    你有 4 个顾客(线程),如果一个顾客在等咖啡(内存),你就去帮下一个顾客(线程)。这样咖啡机(ALU)就不会空转。

二、延迟隐藏 vs 延迟减少

技术目标示例
Prefetching(预取)减少等待时间提前加载数据
Multi-threading(多线程)隐藏等待时间在等待时切换到别的线程
多线程 ≠ 提高单线程性能
多线程 = 提高整体吞吐率

三、执行上下文(Execution Context)存储权衡

CPU 核心的硬件资源有限(寄存器、上下文存储空间等)。
多线程意味着要为每个线程保存寄存器状态(execution context)

情况每线程资源可同时运行的线程数特性
小上下文(轻量线程)多(如 16)高延迟隐藏能力
大上下文(重线程)少(如 4)低延迟隐藏能力
这就是 GPU “小上下文 + 海量线程” 的根本设计理念。

四、硬件多线程(Hardware Multi-threading)类型

1⃣ 交错多线程(Interleaved Multi-threading)

  • 每个时钟周期选择一个线程执行指令。
  • 类似“时间片轮转”。
  • 用于隐藏高延迟操作(例如内存访问)。
  • 例如:GPU 的 warp 轮换调度机制

2⃣ 同时多线程(Simultaneous Multi-threading, SMT)

  • 每个时钟周期从多个线程中取指令并行执行。
  • 扩展自超标量(superscalar)架构。
  • 示例:Intel 超线程技术(Hyper-Threading)(每核两个线程)。

五、多线程的收益与代价

优点缺点
更高的 ALU 利用率需要额外硬件存储上下文
隐藏内存延迟单个线程执行变慢
充分利用超标量单元需要足够的独立工作量
更好吞吐率更大工作集 → 缓存命中率下降 → 内存带宽压力增加
多线程的目标是:

“让所有 ALU 都一直有事干。”

六、GPU 是极端的吞吐量导向处理器

NVIDIA GTX 480 为例:

  • 每个核心(SM):
    • 32 个 ALU(SIMD 方式执行)
    • 128 KB execution contexts(用于保存大量线程状态)
    • 同时支持 48 个 warp(每 warp 32 线程)→ 1536 个线程并发
  • 整个 GPU:
    • 15 个核心 × 1536 线程 ≈ 23,000 并行线程
      GPU 的哲学是:

“多线程到极致,就能完全隐藏延迟。”

七、带宽与算力的不匹配 —— GPU 的瓶颈

例子:向量乘法(A[i] × B[i])

  • 每次运算要加载 A[i]、B[i] 并存储 C[i] → 12 bytes per multiply
  • GTX 480:
    • 480 个乘法器 × 1.2 GHz = 5760 亿次乘法/秒
    • 如果全速运行,需要 6.4 TB/s 内存带宽
    • 实际只有 177 GB/s → 只能达到理论性能的约 3%
      虽然只有 3% 效率,但仍比四核 CPU 快 7 倍。
      说明 GPU 胜在“并行数大”,而非“单线程快”。

八、带宽瓶颈与算术密度(Arithmetic Intensity)

当线程太多、访问内存频繁时,内存带宽成为瓶颈(即使延迟被隐藏)。
解决方向:

  1. 提高数据复用率
    • 在同一线程中多次使用已加载的数据。
    • 在不同线程间共享数据(如共享内存、cache)。
  2. 提高算术密度(Arithmetic Intensity)
    • 同样数量的数据访问,尽量执行更多计算。
    • “多算少取”:计算是“免费的”,内存是昂贵的。

九、CPU vs GPU 对比总结

特征CPUGPU
核心数量少(4~16)多(数千线程)
每线程性能
缓存大(L1/L2/L3)小(L1/shared memory)
内存带宽中等(25 GB/s)极高(177 GB/s)
延迟应对方式依赖缓存和预取依赖多线程切换
适用任务控制复杂、逻辑多、分支多数据并行、算术密集

十、总结:现代处理器的三大设计理念

  1. 多核心(Multi-core) —— 空间并行
  2. SIMD(矢量化) —— 数据并行
  3. 多线程(Thread-level parallelism) —— 时间并行、隐藏延迟
Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐