跳转至

计算机组成概述

约 3022 个字 79 行代码 7 张图片 预计阅读时间 11 分钟

本章主要介绍计算机的大致体系结构

冯-诺伊曼架构

alt text

冯·诺伊曼架构是一种由冯·诺伊曼提出的电子计算机通用架构。冯·诺依曼架构将通用计算机定义为以下 3 个基本原则:

  1. 采用二进制: 指令和数据均采用二进制格式;
  2. 存储程序: 一个计算机程序是由成千上万条指令组成的。指令和数据均存储在存储器中,而不是早期的插线板中,计算机按需从存储器中取指令和取数据;
  3. 计算机由 5 个硬件组成:运算器、控制器、存储器、输入设备和输出设备。在最开始的计算机中,五个部件是围绕着运算器运转的,这使得存储器和 I/O 设备之间的数据传送也需要经过运算器。 而现代计算机中,五个部件是围绕着存储器运转的,这使得存储器和 I/O 设备可以直接完成数据传送,而不需要经过 CPU。

DMA 与 RDMA

DMA(Direct Memory Access,直接内存访问)是一种允许外设(如硬盘、网卡、显卡等)直接与计算机内存进行数据传输的技术,无需 CPU 全程参与控制。

RDMA 是一种跨网络的直接内存访问技术,允许一台计算机直接访问另一台计算机的内存,无需经过对方 CPU 的处理。其具有以下特点:

  • 低延迟:绕开远程主机的 CPU 和操作系统内核,直接在内存层面传输数据,减少协议栈处理开销。
  • 低 CPU 占用:远程主机无需参与数据处理,本地 CPU 也仅需发起传输请求,适合高并发、大数据量场景。

尝试查找资料或询问 AI 回答下述问题:

  1. 在 DMA 出现前 CPU 是如何控制外设与计算机内存之间进行数据传输的?
  2. (选做)RDMA 中绕开远程主机的 CPU 和操作系统内核的目的是什么?如果不进行绕过会有什么开销?

计算机常见硬件1

CPU

简介:

CPU 是整个计算机的核心,承担着运算和控制的功能。CPU 从存储设备中读取代码和数据,尽管现代处理器对代码和数据会有不同的处理,但是其本质上并没有严格的区分。代码由一条一条的指令组成,CPU 按照顺序一条一条执行从存储设备中读取的指令(至少从软件和程序员等使用者的视角看是这样),指令可以是修改 CPU 的状态,进行运算,或者是从其他硬件读取信息或者输出信息。

指令集(ISA):

ISA 定义了程序员如何使用 CPU,如处理器有哪些指令、指令编码方式和功能、寄存器的大小数量和功能、怎么寻找和读取数据、能暂存多少数据、每个暂存的数据有多大、数据存放的顺序如何等等各种约定,并不是指令的简单集合。 下面是一些常见的 ISA:

  • x86: 最经典最常见的 ISA,同学案头计算机的处理器基本都基于此,历史兼容性好(低情商:历史包袱重)
  • ARM: 移动端(包括手机)常见的 ISA,苹果、华为等厂商的处理器都使用该 ISA。
  • RiscV: 新兴的开源 ISA,嵌入式设备中有使用,由于其设计比较简单,易于阅读和学习,学校也喜欢用于教学

我们将在下一节较为详细介绍有关指令集与 CPU 运行的知识

内存

内存用于暂时存放 CPU 中的运算数据,以及与硬盘等外部存储器交换的数据。它是外存与 CPU 进行沟通的桥梁,计算机中所有程序的运行都在内存中进行。

alt text 内存条

硬盘

硬盘有时候也被称作“闪存”或者“外存”

目前市面上主要有两种硬盘,一种是机械硬盘 HDD,另一种是固态硬盘 SSD。SSD 里面没有高速运转的机械结构,取而代之的是闪存芯片和控制电路,在闪存芯片内部有大量的单元通过捕获电荷来存储信息。固态硬盘具有较为明确的寿命特点,其写入次数有限(个人日常使用的实际寿命并不一定比机械硬盘差,各有优缺),且由于电荷流失,长时间不通电时不能保证数据稳定。

alt text 机械硬盘

GPU

GPU(Graphic Process Unit)是专门用于处理图像和图形相关运算的处理器。与 CPU 不同,GPU 采用并行编程模型,能够在处理大量数据时显著提高速度,因此在深度学习等计算密度较大的任务中有十分重要的作用,也是高性能计算的核心硬件。

关键概念:

  1. 线程块: 线程块是一组线程的集合,它是 GPU 并行计算的基本调度单元。一个线程块内的所有线程在 同一个流处理器中执行。线程块之间是相互独立的,也就是说,它们不能直接通信,但块内的线程可以共享数据并进行同步。 每个线程块有一个独立的 共享内存(Shared Memory),这是 GPU 内存中速度最快的部分。线程块内的线程可以通过共享内存进行高效的通信和数据交换。
  2. 流式多处理器(SM): 流式多处理器是 GPU 中进行并行计算的核心单元。一个 GPU 通常包含多个 SM,每个 SM 内部有多个计算核心(CUDA Cores),这些核心负责实际的计算工作。 每个 SM 管理多个线程块。线程块被分配到不同的 SM 上执行,这样 GPU 可以实现高度的并行计算。

GPU 的本质

GPU 的本质就是用高度并行化提升核心数量来掩盖高延迟的问题,与降低延迟为导向的 CPU 相比,GPU 的设计是以增大吞吐为目的。

alt text

网卡

网卡,即网络接口卡(network interface card),也叫 NIC 卡,是一种允许网络连接的计算机硬件设备。网卡是工作在第二层链路层的网络组件,是局域网中连接计算机和传输介质的接口,不仅能实现与局域网传输介质之间的物理连接和电信号匹配,还涉及帧的发送与接收、帧的封装与拆封、介质访问控制、数据的编码与解码以及数据缓存的功能等

网络是通过模拟信号将信息转化为电流传播的,网卡在这里面就充当了一个解码器的作用,将电信号重新转换文文字图像等就是网卡的责任。网卡的其他功能还有监控上传及下载流量,控制网速稳定的作用。

alt text 网卡

存储的层次结构2

Tip

由于不同存储介质的性能、价格有较大的差别,因此为了在维持计算机价格的合理程度同时维持较高的存储性能,一种层次化的存储结构是当下最主要使用的内存结构

alt text

寄存器 (Register)

寄存器是存放当前正在执行的指令和使用的数据的存储,位于 CPU 内部,是访问最快,同时存储量最小的存储。

缓存 (Cache)

一种较小、较快的存储设备,作为较大、较慢设备中数据子集的暂存区

为什么缓存策略是有效的?

  • 基于局部性原理:程序访问第 \(k\) 层数据的频率,通常高于它访问第 \(k+1\) 层数据的频率。
  • 因此,第 \(k+1\) 层的存储设备可以更慢,从而可以做得容量更大,并且每比特的成本也更低。

Tip

时间局部性:最近用到的数据可能会被再次用到

空间局部性:相邻的数据可能会被近期用到

缓存的相关概念:

  • 请求的数据块在缓存中:命中 (Hit)!
  • 请求的数据块不在缓存中:未命中 (Miss)!
  • 缓存未命中的类型:

    • 冷未命中 (Cold / Compulsory Miss):冷未命中(或称强制性未命中)的发生,是因为缓存初始为空。
    • 冲突未命中 (Conflict Miss):大多数缓存会将第 \(k+1\) 层的块限制映射到第 \(k\) 层中一个很小的块位置子集。(例如:第 \(k+1\) 层的块 \(i\) 必须被放置到第 \(k\) 层的第\(i \mod 4\)个块的位置)。当第 \(k\) 层缓存的容量足够大,但多个数据对象恰好都被映射到第 \(k\) 层的同一个缓存块时,就会发生冲突未命中。(例如:若映射函数为 \(i \pmod 4\),那么交替访问块 0, 8, 0, 8, ... 会因为它们都映射到位置 0 而不断产生冲突,导致每次都未命中)。
    • 容量未命中 (Capacity Miss):当当前活跃的缓存块集合(即工作集, working set)的大小超过了缓存本身的容量时,就会发生容量未命中。

缓存命中率对程序性能十分重要!

已知 cache miss 消耗约 100 个时钟周期,cache hit 消耗 1 个时钟周期,试证明:

99% hits is twice as good as 97% hits

内存 (Memory)

分类:

内存(RAM)分为两种 DRAM 和 SRAM。静态内存(SRAM)访问速度更快,同时价格更高昂,反之,动态内存(DRAM)的访问速度相对较慢,但是更为实惠。

在实际的计算机结构中,缓存一般会使用 SRAM 而正常的内存一般会使用 DRAM

alt text

易失性:

内存是一种易失性存储,随着断电,内存存储的内容也会丢失。

DDR:

DDR(Double Data Rate 双倍数据速率)是一种内存传输技术,简单来说,这项技术可以让内存在一个时钟周期内传输两次数据,从而使得数据传输的带宽(速度)翻倍,而无需提高时钟频率本身。

当下的 DDR 技术已经从 DDR 发展到了 DDR5 代,每次迭代主要是在双倍数据速率的基础上降低功耗、提升带宽、升高频率

硬盘 (Disk)

磁盘比 SRAM 慢约 40000 倍,比 DRAM 慢 2500 倍

SSD 与 HDD 的比较如下:

  • 优势 (Advantages)

    • 无机械移动部件:因此速度更快、功耗更低、更坚固耐用。
  • 劣势 (Disadvantages)

    • 存在磨损和写入寿命限制。
    • 价格更昂贵,单位容量成本更高。

总结

Summary

  1. 寄存器(Register): 这种存储器封装在 CPU 内,用于存放当前正在执行的指令和使用的数据。用触发器实现,速度极快,容量很小(几个字节到几十字节)
  2. 缓存(Cache): 位于 CPU 内部或附近,用来存放当前要执行的局部程序段和数据。用 SRAM 实现,速度可与 CPU 匹配,容量小(几十 KB 到几 MB)
  3. 内存(Memory): 位于 CPU 之外,用来存放已被启动的程序及所用的数据。用 DRAM 实现,速度较快,容量较大(目前来说通常是 2GB 到 16GB)
  4. 硬盘(Disk): 位于主机之外,用来存放暂不运行的程序、数据或存档文件。用磁表面或光存储器或固态硬盘(SSD Solid State Disk)实现,容量大而速度慢。SSD 中的重要部件是 NAND Flash。
Question

比较下面的两个 GEMM(General Matrix Multiplication 通用矩阵乘法)写法以及运行时间差异,分析缓存的局部性在其中起到了怎样的作用:

#include <iostream>
#include <vector>
#include <random>
#include <chrono>
#include <iomanip>
#include <cstring>

using namespace std;


void gemm_v0(int M, int N, int K, const float* A, const float* B, float* C) {
    for (int i = 0; i < M; ++i) {
        for (int j = 0; j < N; ++j) {
            C[i * N + j] = 0.0f;
            for (int k = 0; k < K; ++k) {
                C[i * N + j] += A[i * K + k] * B[k * N + j];
            }
        }
    }
}

void gemm_v1(int M, int N, int K, const float* A, const float* B, float* C) {
    memset(C, 0, sizeof(float) * M * N);
    // main loop
    for (int i = 0; i < M; ++i) {
        for (int j = 0; j < K; ++j) {
            for (int k = 0; k < N; ++k) {
                C[i * N + k] += A[i * K + j] * B[j * N + k];
            }
        }
    }
}


template<typename F>
double bench(F&& f) {
    auto t0 = chrono::high_resolution_clock::now();
    f();
    auto t1 = chrono::high_resolution_clock::now();
    using ms = chrono::duration<double, std::milli>;
    return chrono::duration_cast<ms>(t1 - t0).count();
}

int main(int argc, char* argv[]) {
    int M = 1024, N = 1024, K = 1024, repeats = 10;
    if (argc >= 4) {
        M = atoi(argv[1]);
        N = atoi(argv[2]);
        K = atoi(argv[3]);
    }
    cout << "Benchmark GEMM (" << M << "×" << N << "×" << K
         << "), repeats = " << repeats << "\n";

    // 分配并填充数据
    vector<float> A(M * K), B(K * N), C(M * N);
    mt19937 rng(123);
    uniform_real_distribution<float> dist(-1.f, 1.f);
    for (auto& x : A) x = dist(rng);
    for (auto& x : B) x = dist(rng);

    double t0_ms = 0;
    for (int r = 0; r < repeats; ++r)
        t0_ms += bench([&]{ gemm_v0(M, N, K, A.data(), B.data(), C.data()); });
    t0_ms /= repeats;

    double t1_ms = 0;
    for (int r = 0; r < repeats; ++r)
        t1_ms += bench([&]{ gemm_v1(M, N, K, A.data(), B.data(), C.data()); });
    t1_ms /= repeats;

    cout << fixed << setprecision(3);
    cout << "gemm_v0: " << t0_ms << " ms\n";
    cout << "gemm_v1: " << t1_ms << " ms\n";
    cout << "speed ratio (v0/v1): " << (t0_ms / t1_ms) << "\n";
    return 0;
}

运行方法:

g++ -O3 -march=native -std=c++17 benchmark.cpp -o benchmark

./benchmark 512 512 512 #自定义矩阵的大小

Reference


  1. LCPU - 计算机基本知识概览 I  

  2. Bryant, Randal E., and David R. O'Hallaron. "The Memory Hierarchy." Computer Systems: A Programmer's Perspective, 3rd ed., Pearson, 2016, pp. 577-676.