计算机组成概述¶
约 3022 个字 79 行代码 7 张图片 预计阅读时间 11 分钟
本章主要介绍计算机的大致体系结构
冯-诺伊曼架构¶
冯·诺伊曼架构是一种由冯·诺伊曼提出的电子计算机通用架构。冯·诺依曼架构将通用计算机定义为以下 3 个基本原则:
- 采用二进制: 指令和数据均采用二进制格式;
- 存储程序: 一个计算机程序是由成千上万条指令组成的。指令和数据均存储在存储器中,而不是早期的插线板中,计算机按需从存储器中取指令和取数据;
- 计算机由 5 个硬件组成:运算器、控制器、存储器、输入设备和输出设备。在最开始的计算机中,五个部件是围绕着运算器运转的,这使得存储器和 I/O 设备之间的数据传送也需要经过运算器。 而现代计算机中,五个部件是围绕着存储器运转的,这使得存储器和 I/O 设备可以直接完成数据传送,而不需要经过 CPU。
DMA与RDMA
DMA(Direct Memory Access,直接内存访问)是一种允许外设(如硬盘、网卡、显卡等)直接与计算机内存进行数据传输的技术,无需 CPU 全程参与控制。
RDMA 是一种跨网络的直接内存访问技术,允许一台计算机直接访问另一台计算机的内存,无需经过对方 CPU 的处理。其具有以下特点:
- 低延迟:绕开远程主机的 CPU 和操作系统内核,直接在内存层面传输数据,减少协议栈处理开销。
- 低 CPU 占用:远程主机无需参与数据处理,本地 CPU 也仅需发起传输请求,适合高并发、大数据量场景。
尝试查找资料或询问AI回答下述问题:
- 在DMA出现前CPU是如何控制外设与计算机内存之间进行数据传输的?
- (选做)RDMA中绕开远程主机的 CPU 和操作系统内核的目的是什么?如果不进行绕过会有什么开销?
计算机常见硬件1¶
CPU¶
简介:
CPU 是整个计算机的核心,承担着运算和控制的功能。CPU 从存储设备中读取代码和数据,尽管现代处理器对代码和数据会有不同的处理,但是其本质上并没有严格的区分。代码由一条一条的指令组成,CPU 按照顺序一条一条执行从存储设备中读取的指令(至少从软件和程序员等使用者的视角看是这样),指令可以是修改 CPU 的状态,进行运算,或者是从其他硬件读取信息或者输出信息。
指令集(ISA):
ISA 定义了程序员如何使用 CPU,如处理器有哪些指令、指令编码方式和功能、寄存器的大小数量和功能、怎么寻找和读取数据、能暂存多少数据、每个暂存的数据有多大、数据存放的顺序如何等等各种约定,并不是指令的简单集合。 下面是一些常见的 ISA:
- x86: 最经典最常见的 ISA,同学案头计算机的处理器基本都基于此,历史兼容性好(低情商:历史包袱重)
- ARM: 移动端(包括手机)常见的 ISA,苹果、华为等厂商的处理器都使用该 ISA。
- RiscV: 新兴的开源 ISA,嵌入式设备中有使用,由于其设计比较简单,易于阅读和学习,学校也喜欢用于教学
我们将在下一节较为详细介绍有关指令集与CPU运行的知识
内存¶
内存用于暂时存放CPU中的运算数据,以及与硬盘等外部存储器交换的数据。它是外存与CPU进行沟通的桥梁,计算机中所有程序的运行都在内存中进行。
硬盘¶
硬盘有时候也被称作“闪存”或者“外存”
目前市面上主要有两种硬盘,一种是机械硬盘 HDD,另一种是固态硬盘 SSD。SSD里面没有高速运转的机械结构,取而代之的是闪存芯片和控制电路,在闪存芯片内部有大量的单元通过捕获电荷来存储信息。固态硬盘具有较为明确的寿命特点,其写入次数有限(个人日常使用的实际寿命并不一定比机械硬盘差,各有优缺),且由于电荷流失,长时间不通电时不能保证数据稳定。
GPU¶
GPU(Graphic Process Unit)是专门用于处理图像和图形相关运算的处理器。与CPU不同,GPU采用并行编程模型,能够在处理大量数据时显著提高速度,因此在深度学习等计算密度较大的任务中有十分重要的作用,也是高性能计算的核心硬件。
关键概念:
- 线程块: 线程块是一组线程的集合,它是 GPU 并行计算的基本调度单元。一个线程块内的所有线程在 同一个流处理器中执行。线程块之间是相互独立的,也就是说,它们不能直接通信,但块内的线程可以共享数据并进行同步。 每个线程块有一个独立的 共享内存(Shared Memory),这是 GPU 内存中速度最快的部分。线程块内的线程可以通过共享内存进行高效的通信和数据交换。
- 流式多处理器(SM): 流式多处理器是GPU中进行并行计算的核心单元。一个GPU通常包含多个SM,每个SM内部有多个计算核心(CUDA Cores),这些核心负责实际的计算工作。 每个SM管理多个线程块。线程块被分配到不同的SM上执行,这样GPU可以实现高度的并行计算。
GPU的本质
GPU的本质就是用高度并行化提升核心数量来掩盖高延迟的问题,与降低延迟为导向的CPU相比,GPU的设计是以增大吞吐为目的。
网卡¶
网卡,即网络接口卡(network interface card),也叫NIC卡,是一种允许网络连接的计算机硬件设备。网卡是工作在第二层链路层的网络组件,是局域网中连接计算机和传输介质的接口,不仅能实现与局域网传输介质之间的物理连接和电信号匹配,还涉及帧的发送与接收、帧的封装与拆封、介质访问控制、数据的编码与解码以及数据缓存的功能等
网络是通过模拟信号将信息转化为电流传播的,网卡在这里面就充当了一个解码器的作用,将电信号重新转换文文字图像等就是网卡的责任。网卡的其他功能还有监控上传及下载流量,控制网速稳定的作用。
存储的层次结构2¶
Tip
由于不同存储介质的性能、价格有较大的差别,因此为了在维持计算机价格的合理程度同时维持较高的存储性能,一种层次化的存储结构是当下最主要使用的内存结构
寄存器 (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
易失性:
内存是一种易失性存储,随着断电,内存存储的内容也会丢失。
DDR:
DDR(Double Data Rate 双倍数据速率)是一种内存传输技术,简单来说,这项技术可以让内存在一个时钟周期内传输两次数据,从而使得数据传输的带宽(速度)翻倍,而无需提高时钟频率本身。
当下的DDR技术已经从DDR发展到了DDR5代,每次迭代主要是在双倍数据速率的基础上降低功耗、提升带宽、升高频率
硬盘 (Disk)¶
磁盘比SRAM慢约40000倍,比DRAM慢2500倍
SSD与HDD的比较如下:
-
优势 (Advantages)
- 无机械移动部件:因此速度更快、功耗更低、更坚固耐用。
-
劣势 (Disadvantages)
- 存在磨损和写入寿命限制。
- 价格更昂贵,单位容量成本更高。
总结¶
Summary
- 寄存器(Register): 这种存储器封装在CPU内,用于存放当前正在执行的指令和使用的数据。用触发器实现,速度极快,容量很小(几个字节到几十字节)
- 缓存(Cache): 位于CPU内部或附近,用来存放当前要执行的局部程序段和数据。用SRAM实现,速度可与CPU匹配,容量小(几十KB到几MB)
- 内存(Memory): 位于CPU之外,用来存放已被启动的程序及所用的数据。用DRAM实现,速度较快,容量较大(目前来说通常是2GB到16GB)
- 硬盘(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¶
-
Bryant, Randal E., and David R. O'Hallaron. "The Memory Hierarchy." Computer Systems: A Programmer's Perspective, 3rd ed., Pearson, 2016, pp. 577-676. ↩