计算机组成原理概述

一、图灵机、量子计算机

图灵机的背景

在计算机科学中, 可计算性(calculability) 是指一个问题是否存在解决算法。对于一个问题,如果能够使用有限的机械的步骤求出结果,就是可计算的,反之则认为这个问题是不可计算的。

一开始,人们普遍认为任何问题都是有算法的,都是可计算的,而科学家的工作正是找出这些问题的解决算法。后来,人们经过长时间研究,发现很多问题依然找不到算法,也无法做出不存在算法的证明。例如数学家希尔伯特提出的 23 个数学问题中的第 10 个问题:

  • 希尔伯特第 10 问题: 是否存在一个算法能判定任何丢番图方程有无整数解?

这个问题其实是希尔伯特提出的另一个问题的子集:

  • 可判定性问题: 是否存在一个算法能够判定任何数学命题的真伪? 如果存在这样的算法,那么很多数学问题都可以直接得到答案。如果不存在这样的算法,希尔伯特第 10 问题自然也不成立。

在图灵之前,美国数学家阿隆佐·丘奇(图灵的导师)率先提出了可判定性问题的解决方法 —— Lambda 演算 数学表达系统,并证明了不存在这样的通用判定算法,但其使用了非常多的数学技巧,难以理解和应用。

直到 1936 年(小伙子才 24 岁!),英国科学家艾伦·图灵在数学杂志上发表了论文 《论可计算数及其在可判定性问题上的应用》 ,其中提出了解决 “可判定性问题” 的一个开创性思路。论文我看不懂,我尽量将自己能理解到的核心思路概括为 3 点,如果有错误,欢迎你帮我指出:

  • 1、自动机(Automatic Machines): 图灵观察了人类使用笔和橡皮擦在纸上进行计算的过程,将现实世界中的所有计算行为归结为一系列简单机械的动作。在论文中,图灵将这种以简单机械的方式运行的想象中的机器称为自动机,而后人则将这种机器称为 “图灵机”;同时,图灵证明了只要提供足够的时间和内存,图灵机总是能够实现任何计算;
  • 2、通用图灵机(Universal Turing Machines): 假设有一个特殊的图灵机,它能够接收另外一些图灵机作为输入,并且能够产生和这些图灵机一样的输出,那么我们说这个特殊的图灵机就是通用图灵机。 在这里,我们可以把图灵机想象成一个程序或一个算法,而通用图灵机就是能够运行程序的计算机,目前我们所能接触到的所有现代电子计算机都是通用图灵机。
  • 3、停机问题(Halting Problem): 为了解决希尔伯特的可判定性问题,图灵将 “判定数学命题真伪” 的问题转化为 “判定图灵机是否会停机” 的问题,即著名的停机问题 —— “是否存在一个能够判定其它图灵机是否会停机的通用图灵机”。 随后,图灵通过一个巧妙的逻辑矛盾证明了不存在这样的通用图灵机,等于证明了 “不存在一个算法能够判定任何数学命题的真伪”。

图灵机的意义

图灵机的应用价值主要体现在 2 个方面:

  • 1、奠定了现代计算机的抽象逻辑模型: 其实,在图灵机模型之前,也有其他科学技术提出了能够描述人类计算能力的模型,例如 Lambda 演算。但相比于其他模型,图灵机模型的优势在于简单直接,而且很容易通过机械技术或电子技术实现。在图灵机模型的价值被世人认可后,图灵机模型也为现代计算机奠定了理论基础。 图灵后来被誉为 “计算机科学之父”,图灵机模型的贡献比重很大。
  • 2、证明了计算机存在计算边界: 图灵先是证明了图灵机总是能够实现任何计算,但又证明了停机问题无法用图灵机解决。将这两点结合起来,说明不是所有问题都能用计算解决,例如决策问题。这一理论后来建立了可计算理论的基础,后人称之为 “丘奇-图灵论题” :无论有多少时间或内存,有些问题是计算机无法解决的,除非建立完全超越图灵机的计算模型,或者说 “超计算”。

什么是量子计算机

量子计算机(Quantum computer)一种使用 “量子物理实验代替数学计算” 的计算机。

在 1982 年,诺贝尔物理奖获得者理查德·费曼在报告 《计算机模拟物理学》中最早提出相对成熟的量子计算概念:对于千变万化且初始状态不确定的问题,如果单纯依靠计算难以解决,那么直接用量子实验来模拟,再观察实验的结果来获得计算结果。根据大数定律,只要实验采样的次数足够多,就能以足够大的精度获得结果。举个类似的例子,18 世纪的蒲丰投针实验,就是一种用反复投针的物理实验得到圆周率的方法,也是用实验获得计算结果的案例。

然而,量子计算机依然遵循丘奇-图灵论题,量子计算机在可计算性方面并没有任何优势。 任何可以由量子计算机解决的问题,只要提供足够的时间,都可以由经典计算机解决。虽然如此,量子计算机在处理某些特定问题上会存在时间上的绝对优势,这就是量子优越性。

量子优越性

经典计算机和量子计算机解决的问题有一定交叉,但两者擅长的方向不一样。量子计算机在解决特定问题上的绝对优势,也叫量子优越性。 例如,在路径规划、密码破解、材料设计、人工智能,原子结合,基因序列等,只需要知道答案,不需要知道过程的问题上,量子计算机强于经典计算机。那么,量子计算机是如何实现这一优越性的呢?—— 量子比特。

量子计算最基础的单元是 ”量子比特“,与电子比特在同一时刻只能表示 “0” 或 “1” 不同,量子比特既可以是 “0” 或 “1” 其中之一,也可以是 “0” 和 ”1“ 的叠加态。那么,1 个量子比特可以是 2 个电子比特的叠加态,2 个纠缠的量子比特就可以是 4 种电子比特的叠加态,4 个纠缠的量子比特就是 16 种电子比特的叠加态…… 依次类推,nn 个纠缠的量子比特就是 2n2n 种电子比特的叠加态。

这个特点有什么用呢?举个例子,要寻找 1 亿种密码中的正确密码,经典计算机的解法是用 穷举 法依次尝试 1 亿种可能性,直到出现命中正确答案的结果后停止。 而量子计算机可以直接制造叠加所有可能性的量子比特,一次性尝试所有可能性。 再通过量子干涉操控放大命中正确答案的信号,而缩减错误答案的信号,使得最终量子态包含引起正确答案的信号, 通过观察得到正确答案。

量子计算两大核心难点

多比特 & 高精度:

  • 多比特数: 目前还未实现超过百级的比特数;
  • 高精度: 操控量子的精度不够,且比特数越多,操控难度越大。当操作次数增加后,错误也会累积,最终量子态包含的正确信息也会越少,反而丢失了量子优越性。在现有的量子纠错方案下,维护 1 个物理比特的正确性需要另外 1000 个物理比特来纠错。

参考

计算机系统 #1 从图灵机到量子计算机,计算机可以解决所有问题吗?

二、停机问题

背景知识

  • 「停机问题」研究的是:是否存在一个「程序」,能够判断另外一个「程序」在特定的「输入」下,是会给出结果(停机),还是会无限执行下去(不停机)。
  • 在下文中,我们用「函数」来表示「程序」,「函数返回」即表示给出了结果。

正文

我们假设存在这么一个「停机程序」,不管它是怎么实现的,但是它能够回答「停机问题」:它接受一个「程序」和一个「输入」,然后判断这个「程序」在这个「输入」下是否能给出结果:

def is_halt(program, input) -> bool:
  # 返回 True  如果 program(input) 会返回
  # 返回 False 如果 program(input) 不返回

(在这里,我们通过把一个函数作为另一个函数的输入来描述一个「程序」作为另一个「程序」的「输入」,如果你不熟悉「头等函数」的概念,你可以把所有文中的函数对应为一个具备该函数的对象。)

为了帮助大家理解这个「停机程序」的功能,我们举个使用它的例子:

from halt import is_halt

def loop():
  while(True):
      pass

# 如果输入是 0,返回,否则无限循环
def foo(input):
  if input == 0:
    return 
  else:
    loop()

is_halt(foo, 0)  # 返回 True
is_halt(foo, 1)  # 返回 False

是不是很棒?

不过,如果这个「停机程序」真的存在,那么我就可以写出这么一个「hack 程序」:

from halt import is_halt

def loop():
  while(True):
      pass

def hack(program):
  if is_halt(program, program):
    loop()
  else:
    return

这个程序说,如果你( is_halt )对 program(program) 判停了,我就无限循环;如果你判它不停,我就立刻返回。

那么,如果我们把「hack 程序」同时当做「程序」和「输入」喂给「停机程序」,会怎么样呢?

is_halt(hack, hack) 

你会发现,如果「停机程序」认为 hack(hack) 会给出结果,即 is_halt(hack, hack) 返回 True ,那么实际上 hack(hack) 会进入无限循环。而如果「停机程序」认为 hack(hack) 不会给出结果,即 is_halt(hack, hack) 返回 false,那么实际上 hack(hack) 会立刻返回结果……

这里就出现了矛盾和悖论,所以我们只能认为,我们最开始的假设是错的:

这个「停机程序」是不存在的。

意义

简单的说,「停机问题」说明了现代计算机并不是无所不能的。

上面的例子看上去是刻意使用「自我指涉」来进行反证的,但这只是为了证明方便。实际上,现实中与「停机问题」一样是现代计算机「不可解」的问题还有很多,比如所有「判断一个程序是否会在某输入下怎么样?」的算法、Hilbert 第十问题等等,wikipedia 甚至有一个 List of undecidable problems

漫谈

  1. 最初图灵提出「停机问题」只是针对「图灵机」本身的,但是其意义可以被推广到所有「算法」、「程序」、「现代计算机」甚至是「量子计算机」。
  2. 实际上「图灵机」只能接受(纸带上的)字符串,所以在图灵机编程中,无论是「输入」还是另一个「图灵机」,都是通过编码来表示的。
  3. 「图灵机的计算能力和现代计算机是等价的」,更严谨一些,由于图灵机作为一个假象的计算模型,其储存空间是无限大的,而真实计算机则有硬件限制,所以我们只能说「不存在比图灵机计算能力更强的真实计算机」。
  4. 这里的「计算能力」(power)指的是「能够计算怎样的问题」(capablity)而非「计算效率」(efficiency),比如我们说「上下文无关文法」比「正则表达式」的「计算能力」强因为它能解决更多的计算问题。
  5. 「图灵机」作为一种计算模型形式化了「什么是算法」这个问题(邱奇-图灵论题)。但图灵机并不是唯一的计算模型,其他计算模型包括「Lambda 算子」、「 μ - 递归函数」等,它们在计算能力上都是与「图灵机」等价的。因此,我们可以用「图灵机」来证明「可计算函数」的上界。也因此可以证明哪些计算问题是超出上界的(即不可解的)。
  6. 需要知道的是,只有「可计算的」才叫做「算法」。
  7. 「停机问题」响应了「哥德尔的不完备性定理」。

拓展阅读

中文:

Matrix67: 不可解问题(Undecidable Decision Problem)

Matrix67: 停机问题、Chaitin常数与万能证明方法

刘未鹏:康托尔、哥德尔、图灵--永恒的金色对角线(rev#2) - CSDN博客

卢昌海:Hilbert 第十问题漫谈 (上)

英文:

《Introduction to the Theory of Computation》

Turing Machines Explained - Computerphile

Turing & The Halting Problem - Computerphile

Why, really, is the Halting Problem so important?

参考

如何通俗地解释停机问题(Halting Problem)?

三、冯·诺依曼架构

冯·诺依曼架构将通用计算机定义为以下 3 个基本原则:

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

瓶颈:

  • 由于 CPU 和存储器之间共享同一个系统总线,并且 CPU 和存储器之间存在巨大的速度差,导致 CPU 需要不断地被迫等待数据读取或写入到存储器,因此遏制了 CPU 的吞吐量

要从根本上解决冯·诺依曼瓶颈,还是只能重新构建一套新的计算机体系,例如生物计算机、量子计算机。不过,目前它们都还处在非常原始的阶段。现代计算机体系只能采用优化策略来减弱冯·诺依曼瓶颈的影响,这些内容我们后面都会提到,例如:

  • 1、增加一个位于 CPU 和主内存之间的高速缓存
  • 2、将指令缓存和数据缓存分离
  • 3、CPU 分支预测
  • 4、将存储器集成到 CPU 芯片内部,以减少内存访问(SoC 芯片)

如果说图灵机描述的是计算机的抽象模型,那么冯·诺依曼架构则是对图灵机这个抽象模型的实现架构。 冯诺依曼架构确立了现代电子计算机的基础和结构,学习计算机组成原理,其实就是学习和拆解冯诺依曼架构。

四、负数为什么要用补码表示

补码的两个优势

在计算机中,我们会把带 “正 / 负” 符号的数称为 真值(True Value),而把符号化后的数称为 机器数(Computer Number)

机器数才是数字在计算机中的二进制表示。 例如在前面的数字中, +1110 是真值,而 0000, 1110 是机器数。新的问题来了:将符号数字化后的机器数,在运算的过程中符号位是否与数值参与运算,又应该如何运算呢?

为了解决有符号机器数运算效率问题,计算机科学家们提出多种机器数的表示法:

机器数 正数 负数
原码 符号位表示符号 数值位表示真值的绝对值 符号位表示数字的符号 数值位表示真值的绝对值
反码 原码本身 按位取反
补码 原码本身 在负数反码的基础上 + 1

使用补码表示法后,有符号机器数加法运算就只是纯粹的 加法运算,不会因为符号的正负性而采用不同的计算方法,也不需要减法运算。因此电路设计中只需要设置加法器和补数器,就可以完成有符号数的加法和减法运算,能够简化电路设计。

除了 消除减法运算 外,补码表示法还实现了 “0” 的机器数的唯一性

在原码表示法中,“+0” 和 “-0” 都是合法的,而在补码表示法中 “0” 只有唯一的机器数表示,即 0000,0000 。换言之补码能够比原码多表示一个最小的负数 1000,0000

补码是怎么设计出来的

这就要提到数学中的 “补数” 概念:

  • 1、当一个正数和一个负数互为补数时,它们的绝对值之和就是模;
  • 2、一个负数可以用它的正补数代替。

听起来很抽象对吧❓其实生活中,就有一个更加形象的例子 —— 时钟,时钟里就蕴含着补数的概念!

比如说,现在时钟的时针刻度指向 6 点,我们想让它指向 3 点,应该怎么做:

  • 方法 1 : 逆时针地拨动 3 个点数,让时针指向 3 点,这相当于做减法运算 -3;
  • 方法 2: 顺时针地拨动 9 个点数,让时针指向 3 点,这相当于做加法运算 +9。

可以看到,对于时钟来说 -3 和 +9 竟然是等价的! 这是因为时钟只能 12 个小时,当时间点数超过 12 时就会自动丢失,所以 15 点和 3 点在时钟看来是都是 3 点。如果我们要在时钟上进行 6 - 3 减法运算,我们可以将 -3 等价替换为它的正补数 +9 后参与计算,从而将减法运算替换为 6 + 9 加法运算,结果都是 3。

理解了补数的概念后,我们再多看一个十进制的例子:我们要计算十进制 354365 - 95937 = 的结果,怎么做呢?

  • 方法 1 - 借位做减法: 常规的做法是利用连续向前借位做减法的方式计算,这没有问题;
  • 方法 2 - 减模加补: 使用补数的概念后,我们就可以将减法运算消除为加法运算。

具体来说,如果我们限制十进制数的位长最多只有 6 位,那么模就是 1000000,-95937 对应的正补数就是 1000000 - 95937 = 904063 。此时,我们可以直接用正补数代替负数参与计算,则有:

354365 - 95937 // = 258428

= 354365 - (1000000 - 904063)

= 354365 - 1000000 + 904063 【减整加补】

= 258428

可以看到,把 -95937 等价替换为 +904063 后,就把减法运算替换为加法运算。细心的你可能要举手提问了,还是需要减去 1000000 呀?🙋🏻‍♀️

其实并不用,因为 1000000 是超过位数限制的,所以减去 1000000 这一步就像时针逆时针拨动一整圈一样是无效的。所以实际上需要计算的是:

// 实际需要计算的是:
354365 + 904063
= 1258428 = 258428
  ^
  最高位 1 超出位数限制,直接丢弃

丢弃最高位 1 其实就是上就是一个对 1000000 取模的过程。

为什么要使用补码

继续使用前文提到的 14 + (-1) 正负数相加的例子:

// 原码表示法
0000, 1110 + 1000, 0001 = 1001, 1111 // 14 + (-1) = -15 错误
^           ^          ^
符号位        符号位       符号位

// 补码表示法
0000, 1110 + 1111, 1111 = 1, 0000, 1101 // 14 + (-1) = 13 正确
^           ^          ^
符号位        符号位       最高位 1 超出位数限制,直接丢弃

如果我们限制二进制数字的位长最多只有 8 位,那么模就是 1, 0000, 0000 ,此时,-1 的二进制数 1000, 0001 的正补数就是 1111, 1111

我们使用正补数 1111, 1111 代替负数 1000, 0001 参与运算,加法运算后的结果是 1, 0000, 1101。其中最高位 1 超出位数限制,直接丢弃,所以最终结果是 0000, 1101,也就是 13,计算正确。

到这里,相信补码的设计原理已经很清楚了。

补码的关键在于:找到一个与负数等价的正补数,使用该正补数代替负数,从而将减法运算替换为两个正数加法运算。 补码的出现与 运算器的电路设计 有关,从设计者的角度看,希望尽可能简化电路设计和计算复杂度。而使用正补数代替负数就可以消除减法器,实现简化电路的目的。

参考

https://juejin.cn/post/7169966346753540103

五、为什么浮点数运算不精确

首先,浮点数牺牲精度换取更大表示范围。

其次,浮点数的小数部分也无法表示所有的小数,0.5 0.25 0.125 0.0625 ...,只有当小数部分是 5 的倍数时才能精确表示。

参考:https://juejin.cn/post/6860445359936798734

六、Unicode、UTF-8

什么是字符

字符(Character) 是对文字和符号的总称,例如汉字、拉丁字母、emoji 都是字符。在计算机中,一个字符由 2 部分组成:

  • 1、字符的编码: 字符在计算机上的存储格式。例如在 ASCII 字符集中,字符 A 的编码就是 65;
  • 2、用户看到的图画: 字符的编码经过软件渲染后,呈现给用户的图画。同一个编码,在不同的软件渲染后呈现的图画可以是不同的。披萨的 Unicode 编码是 U+1F355,而在不同的软件渲染的图片可能是不同的。

什么是字符集

字符集(Character Set) 是多个字符与字符编码组成的系统,由于历史的原因,曾经发展出多种字符集,例如:

字符集一多起来,就容易出现兼容问题: 即同一个字符在不同字符集上对应不同的字符编码。 例如,最早的 emoji 在日本的一些手机厂商创造并流行起来,使得 emoji 在不同厂商的设备间无法兼容。要想正确解析一个字符编码,就需要先知道它使用的字符编码集,否则用错误的字符集解读,就会出现乱码。想象一下,你发送的一个在女朋友的手机上看到的是另一个 emoji,是一件多么可怕的事情。

Unicode 字符集

为了解决字符集间互不兼容的问题,包罗万象的 Unicode 字符集出场了。Unicode(统一码)由非营利组织统一码联盟负责,整理了世界上大部分的字符系统,使得计算机可以用更简单统一的方式来呈现和处理文字。

Unicode 字符集与 ASCII 等字符集相比,在概念上相对复杂一些。我们需要从 2 个维度来理解 Unicode 字符集:编码标准 + 编码格式。

Unicode 编码标准

关键理解 2 个概念:码点 + 字符平面映射:

  • 码点(Code Point): 从 0 开始编号,每个字符都分配一个唯一的码点,完整的十六进制格式是 U+[XX]XXXX,具体可表示的范围为 U+0000 ~ U+10FFFF (所需要的空间最大为 3 个字节的空间),例如 U+0011 。这个范围可以容纳超过 100 万个字符,足够容纳目前全世界已创造的字符。

img

  • 字符平面(Plane):

    这么多字符并不是一次性定义完成的,而是采用了分组的方式。每一个组称为一个平面,每个平面能够容纳 \(2^{16}=65536\) 个字符。Unicode 一共定义了 17 个平面:

    • 基本多文种平面(Basic Multilingual Plane, BMP): 第一个平面,包含最常用的通用字符。当然,基本平面并不是填满的,而是刻意空出一段区域,这个我们下文再说。
    • 辅助平面(Supplementary Plane): 剩下的 16 个平面,包含多种语言的字符。

完整的 unicode 码点列表可以参考:unicode.org

Unicode 编码格式

Unicode 本身只定义了字符与码点的映射关系,相当于定义了一套标准,而这套标准真正在计算机中落地时,则有多种编码格式。目前常见到的有 3 种编码格式:UTF-8、UTF-16 和 UTF-32。UTF 是英文 Unicode Transformation Format 的缩写,意思是 Unicode 字符转换为某种格式。

别看编码格式五花八门,本质上只是出于空间和时间的权衡,对同一套字符标准使用不同的编码算法而已。 举个例子,字符 A 的 Unicode 码点和编码如下(先不考虑字节序):

  • 1、图像:A
  • 2、码点:U+0041
  • 3、UTF-8 编码:0X41
  • 4、UTF-16 编码:0X0041
  • 5、UTF-32 编码:0X00000041

当你根据 UTF-8、UTF-16 和 UTF-32 的编码规则进行解码后,你将得到什么结果呢?是的,它们的结果都是一样的 —— 0x41。懂了吗?

Unicode 的三种实现方式

用一张表总结一下 3 种编码格式:

ASCII UTF-8 UTF-16 UTF-32
编码空间 0~7F 0~10FFF 0~10FFF 0~10FFF
最小存储占用 1 1 2 4
最大存储占用 1 4 4 4

参考

七、CPU

在上一篇文章里,我们聊到了计算机的冯·诺依曼架构,以及计算机的五大部件:控制器、运算器、存储器、输入设备和输出设备。在现在计算机体系中,CPU 是整个计算机的核心,主要包含控制器和运算器两大部件。

什么是 CPU

中央处理单元(Central Processing Unit,CPU)也叫中央处理器或主处理器,是整个计算机的核心,也是整台计算机中造价最昂贵的部件之一。

从硬件的角度: CPU 由超大规模的晶体管组成;

从功能的角度: CPU 内部由时钟、寄存器、控制器和运算器 4 大部分组成。

  • 1、时钟(Clock): 负责发出时钟信号,也可以位于 CPU 外部;
  • 2、寄存器(Register): 负责暂存指令或数据,位于存储器系统金字塔的顶端。使用寄存器能够弥补 CPU 和内存的速度差,减少 CPU 的访存次数,提高 CPU 的吞吐量;
  • 3、控制器(Control Unit): 负责控制程序指令执行,包括从主内存读取指令和数据发送到寄存器,再将运算器计算后的结果写回主内存;
  • 4、运算器(Arithmetic Logic Unit,ALU): 负责执行控制器取出的指令,包括算术运算和逻辑运算。

冯·诺依曼架构:

img

通用处理器和专用处理器

在早期的计算机系统中,只有 1 个通用处理器,使用 1 个处理器就能够完成所有计算任务。后来人们发现可以把一些计算任务分离出来,单独设计专门的芯片微架构,在执行效率上会远远高于通用处理器,最典型的专用处理器就是 GPU 图形处理器。

这种用来专门处理某种计算任务的处理器就是专用处理器,那为什么专用处理器在处理某些特定问题时更快呢,我认为有 3 点解释:

  • 1、最优架构: 专用处理器只处理少量类型的工作,可以为特定工作设计最优芯片架构,而通用处理器只能设计全局最优架构,但不一定是执行特定工作的最优机构;
  • 2、硬件加速: 可以把多条指令的计算工作直接用硬件实现,相比于 CPU 一条条地执行指令,能够节省大量指令周期;
  • 3、成本更低: 专用处理器执行的计算流程是固定的,不需要 CPU 的流水线控制、乱序执行等功能,实现相同计算性能的造价更低。

现代计算机架构都是 1 个通用处理器加上多个专用处理器,这种将不同类型的计算任务采用不同的计算单元完成的设计,也叫 异构计算(Heterogeneous Computing)。

多处理器架构:

img

什么是 ISA

CPU 所能理解的机器语言就是 指令(Instruction Code), 一个 CPU 所能理解的所有指令就是 指令集(Instruction Set)。

为了保证芯片间的兼容性,芯片厂商并不为每款新芯片设计一个新的指令集,而是将指令集推广为标准规范,这个规范就是 指令集架构(Instruction Set Architecture,ISA)

相对于指令集架构,CPU 在实现具体指令集功能的硬件电路设计就是 微架构(Micro Architecture)。 如果用软件的思考方式,ISA 就是 CPU 的功能接口,定义了 CPU 的标准规范,而微架构就是 CPU 的功能实现,定义了 CPU 的具体电路设计,一种指令集可以兼容不同的微架构。

两种主流的 ISA

因为 CPU 位于整个计算机系统最底层且最核心的部件,如果 CPU 的兼容性都出问题了,那么以前开发的应用软件甚至操作系统将无法在新的 CPU 上运行,这对芯片厂商的生态破坏是致命的。因此,指令集架构是相对稳定的,芯片厂商在 ISA 中添加或删除指令时会非常谨慎。

目前,能够有效占领市场份额的只有 2 个 ISA ,它们也分别代表了复杂与精简 2 个发展方向:

  • x86 架构: Intel 公司在 1970 年代推出的复杂指令集架构;
  • ARM 架构: ARM 公司在 1980 年代推出的精简指令集架构,我们熟悉的 Apple M1 芯片、华为麒麟芯片和高通骁龙芯片都是 ARM 架构(其实,ARM 公司并不生产芯片,而是以技术授权的模式运行)。

CISC 和 RISC

在 CPU 指令集的发展过程中,形成了 2 种指令集类型:

  • 复杂指令集(Complex Instruction Set Computer,CISC): 强调单个指令可以同时执行多个基本操作,用少量指令就可以完成大量工作,执行效率更高;
  • 精简指令集(Reduced Instruction Set Computer,RISC): 强调单个指令只能执行一个或少数基础操作,指令之间没有重复或冗余的功能,完成相同工作需要使用更多指令。

在早期的计算机系统中,指令集普遍很简单,也没有复杂和精简之分。随着应用软件的功能越来越丰富,应用层也在反向推动芯片架构师推出更强大的指令集,以简化程序编写和提高性能。例如,一些面向音视频的指令可以在一条指令内同时完成多个数据进行编解码。

这在当时的确是不错的选择。 原因是 CPU 和主存的速度差实在太大了,用更少的指令实现程序功能(指令密度更高)可以减少访存次数。 凭借这一点,复杂指令集对精简指令集的优势是几乎全面性的:

  • 优势 1: 可以减少程序占用的内存和磁盘空间大小;
  • 优势 2: 可以减少从内存或磁盘获取指令所需要的带宽,能够提高总线系统的传输效率;
  • 优势 3: CPU L1 Cache 可以容纳更多指令,可以提高缓存命中率。且现代计算机中多个线程会共享 L1 Cache,指令越少对缓存命中率越有利;
  • 优势 4: CPU L2 Cache 可以容纳更多数据,对操作大量数据的程序也有利于提高缓存命中率。

然而,这些优势都是有代价的:

  • 缺点 1 - 处理器设计复杂化: 指令越复杂,用于解析指令的处理器电路设计肯定会越复杂,执行性能功耗也越大;
  • 缺点 2 - 指令功能重叠: 很多新增的指令之间产生了功能重叠,不符合指令集的正交性原则,而且新增的很多复杂指令使用率很低,但处理器却付出了不成正比的设计成本;
  • 缺点 3 - 指令长度不统一: 指令长度不统一,虽然有利于使用哈夫曼编码进一步提高指令密度(频率高的指令用短长度,频率高的指令用大长度),但是指令长度不同,执行时间也有长有短,不利于实现流水线式结构。

因此,到 1980 年代,精简指令集 RISC 逐渐浮出水面。目前,大多数低端和移动系统都采用 RISC 架构,例如 Android 系统、Mac 系统和微软 Surface 系列。

相比于复杂指令集,精简指令集更加强调 “正交性” ,单个指令只能执行一个或少数基础操作,指令之间没有重复或冗余的功能。而且精简指令集的每条 指令长度相同 ,非常便于实现流水线式结构。

网上很多资料有一个误区: 精简指令集简化了指令集的大小。 这是不对的,准确的说法是简化了指令集的复杂度。

总结一下: 复杂指令集凭借更高的指令密度,在性能方面整体优于精简指令集(内存 / 磁盘占用、CPU Cache 命中率、TLB 未命中率),而精简指令集牺牲了指令密度换取更简单的处理器架构,以性能换取功耗的平衡。

指令集类型 CISC RISC
指令数量 指令数量庞大 指令数量相对较少
指令长度 长度不同 长度相同
指令功能 有重叠 正交
举例 x86 ARM、MIPS

CPU 的性能指标

1. 执行系统参数

  • 1、主频(Frequency/Clock Rate): 在 CPU 内部有一个 晶体振荡器(Oscillator Crystal) ,晶振会以一定的频率向控制器发出信号,这个信号频率就是 CPU 的主频。主频是 CPU 最主要的参数,主频越快,计算机单位时间内能够完成的指令越快。 CPU 的主频并不是固定的,CPU 在运行时可以选择低频、满频甚至超频运行, 但是工作频率越高,意味着功耗也越高;
  • 2、时钟周期(Clock Cycle): 主频的另一面,即晶振发出信号的时间间隔, 时钟周期=1/主频;
  • 3、外频: 外频是主板为 CPU 提供的时钟频率,早期计算机中 CPU 主频和外频是相同的,但随着 CPU 主频越来越高,而其他设备的速度还跟不上,所以现在主频和外频是不相等的;
  • 4、程序执行时间:
    • 4.1 流逝时间(Wall Clock Time / Elapsed Time): 程序开始运行到程序结束所流逝的时间;
    • 4.2 CPU 时间(CPU Time): CPU 实际执行程序的时间,仅包含程序获得 CPU 时间片的时间(用户时间 + 系统时间)。由于 CPU 会并行执行多个任务,所以程序执行时间会小于流逝时间;
    • 4.3 用户时间(User Time): 用户态下,CPU 切换到程序上执行的时间;
    • 4.4 系统时间(Sys Time): 内核态下,CPU 切换到程序上执行的时间;

2. 存储系统参数

  • 字长(Word): CPU 单位时间内同时处理数据的基本单位,多少位 CPU 就是指 CPU 的字长是多少位,比如 32 位 CPU 的字长就是 32 位,64 位 CPU 的字长就是 64 位;
  • 地址总线宽度(Address Bus Width): 地址总线传输的是地址信号,地址总线宽度也决定了一个 CPU 的寻址能力,即最多可以访问多少数据空间。举个例子,32 位地址总线可以寻址 4GB 的数据空间;
  • 数据总线宽度(Data Bus Width): 数据总线传输的是数据信号,数据总线宽度也决定了一个 CPU 的信息传输能力。

区分其它几种容量单位:

  • 字节(Byte): 字节是计算机数据存储的基本单位,即使存储 1 个位也需要按 1 个字节存储;
  • 块(Block): 块是 CPU Cache 管理数据的基本单位,也叫 CPU 缓存行;
  • 段(Segmentation)/ 页(Page): 段 / 页是操作系统管理虚拟内存的基本单位。
  • 相关文章:计算机系统 #6 计算机的存储器金字塔长什么样?

影响 CPU 性能的因素

CPU 作为计算机的核心部件,未来一定是朝着更强大的性能出发。在看待 CPU 的视角上,我们也要具备一定的全局观:

  • 1、提升 CPU 性能不止是 CPU 的任务: 计算机系统是多个部件组成的复杂系统,脱离整体谈局部没有意义;
  • 2、平衡性能与功耗: 一般来说,CPU 的计算性能越高,功耗也越大。我们需要综合考虑性能和功耗的关系,脱离功耗谈性能没有意义。

1. 提升 CPU 主频

提升主频对 CPU 性能的影响是最直接的,过去几十年 CPU 的主要发展方向也是在怎么提升 CPU 主频的问题上。

不过,最近几年 CPU 主频的速度似乎遇到瓶颈了。因为想要主频越快,要么让 CPU 满频或超频运行,要么升级芯片制程,在单位体积里塞进去更多晶体管。这两种方式都会提升 CPU 功耗,带来续航和散热问题。如果不解决这两个问题,就无法突破主频瓶颈。

主频的瓶颈:

img

—— 图片引用自 Wikipedia

2. 多核并行执行

既然单核 CPU 的性能遇到瓶颈,那么在 CPU 芯片里同时塞进去 2 核、4 核甚至更多,那么整个 CPU 芯片的性能不就直接翻倍提升吗?

理想很美好,现实是性能并不总是随着核心数线性增加。在核心数较小时,增加并行度得到的加速效果近似于线性提升,但增加到一定程度后会趋于一个极限, 说明增加并行度的提升效果也是有瓶颈的。

为什么呢?因为不管程序并行度有多高,最终都会有一个结果汇总的任务,而汇总任务无法并行执行,只能串行执行。例如,我们用 Java 的 Fork/Join 框架将一个大任务分解为多个子任务并行执行,最终还是需要串行地合并子任务的结果。

这个结论也有一个经验定律 —— 阿姆达尔定律(Amdahl’s Law) ,它解释了处理器并行计算后效率提升情况。我们把串行的部分称为串行分量 \(Ws\) ,把并行的部分称为并行分量 \(Wp\) ,正是串行分量限制了性能提升的极限,串行分量越大,极限越低。

  • 并行后的执行时间是 \(\frac{W_p}{p} + W_s\)
  • 并行后的加速倍数是 \(\frac{W_s+W_p}{W_s+\frac{W_p}{p}}\) ,当并行度 p 趋向于 无穷大时,提升极限就是 \(\frac{W_s+W_p}{W_s}\)

并行度、并行分量对提升效果的影响:

img

—— 图片引用自 Wiki 百科

提示: 以绿色的曲线为例,程序可以的并行分量是 95%,串行分量是 5%,最终得出的提升极限就会 20 倍。

3. 指令重排序

增加核心数是提升并行度最直接的方法,但并不是唯一的方法。

现代 CPU 为了提高并行度,会在遵守单线程数据依赖性原则的前提下,对程序指令做一定的重排序。事实上不止是 CPU,从源码到指令执行一共有 3 种级别重排序:

  • 1、编译器重排序: 例如将循环内重复调用的操作提前到循环外执行;
  • 2、处理器系统重排序: 例如指令并行技术将多条指令重叠执行,或者使用分支预测技术提前执行分支的指令,并把计算结果放到重排列缓冲区(Reorder Buffer)的硬件缓存中,当程序真的进入分支后直接使用缓存中的结算结果;
  • 3、存储器系统重排序: 例如写缓冲区和失效队列机制,即是可见性问题,从内存的角度也是指令重排问题。

指令重排序类型:

img

4. SoC 芯片 —— 片内片外双总线结构

随着芯片集成电路工艺的进步,在冯·诺依曼架构中的五大部件(运算器、控制器、存储器、输入和输出设备接口)也可以集成在一个芯片上,形成一个近似于完整计算机的系统,这种芯片也叫 片上系统(System on Chip,Soc)。 SoC 芯片将原本分布在主板上的各个部件聚合到同一个芯片上,不同部件之间的总线信息传输效率更高。

八、系统总线

在之前的文章中,我们聊到了计算机的冯·诺依曼计算机架构,计算机由五大部件组成。那么,计算机的五大部件是如何连接成一个整体的呢?这就需要依赖总线系统。

什么是总线

在冯·诺依曼计算机架构中,计算机由控制器、运算器、存储器、输入设备和输出设备五各部分组成,而这五个部分必须进行 “连接” 起来相互通信才能形成一个完整的整体。 总线就是连接多个计算机部件的 数据通信规范

为什么使用总线结构

  • 原因 1 - 降低复杂性: 这个设计思路跟软件开发中的中介者模式是相同的。总线结构将 N-N 网型拓扑结构简化为 N-1-N 总线型结构或星型+总线型拓扑结构,不仅整体的系统结构清晰许多,可以提高系统稳定性。而且需要使用的布线数目也减少了,制造成本也更低;
  • 原因 2 - 促进标准化: 总线结构提供了一个标准化的数据交换方式,各个硬件按照总线的标准实现接口,而无需考虑对方接口或总线的工作原理,有利于各个部件模块化设计。

img

总线的内部结构

总线本身的电路功能,又可以拆分成 3 部分:

  • 1、地址总线(Address Bus,AB): 地址总线传输的是 地址信号。地址总线是单向的,地址信息只能从主设备发往从设备。地址总线宽度也决定了一个 CPU 的寻址能力,即多大可以访问多少数据空间。举个例子,32 位地址总线可以寻址 4GB 的数据空间;
  • 2、控制总线(Control Bus,CB): 控制总线传输 控制或状态信号。控制总线是双向的,信号可以从主模块发往从模块,也可以从从模块发往主模块(例如 CPU 对存储器的读写控制信号,例如 I/O 设备对 CPU 中断请求信号);
  • 3、数据总线(Data Bus,DB): 数据总线传输的是实际的 数据信息。数据总线是双向的,数据可以从主模块发往从模块(例如 CPU 向内存的写入操作),也可以从从模块发往主模块(例如 CPU 向内存的读取操作)。

举个例子,当 CPU 要从存储器读取数据时,三类总线的工作过程概要如下:

  • 1、CPU 通过地址总线发送要访问的存储单元的地址信息;
  • 2、CPU 通过控制总线发送读控制信号;
  • 3、存储器通过数据总线发送指定存储单元上的数据,从 CPU 的视角就是读取。

总线内部结构:

img

总线系统的架构

理解了总线的概念后,我们先来看总线系统的整体架构,现代计算机中的总线大多采用分层次多总线架构。

1. 单总线架构和多总线架构

在早期计算机中,会使用单一总线来连接计算机的各个部件,这种结构叫单总线架构。这种结构实现简单,但缺点有 2 个:

  • 缺点 1: 计算机不同组件之间的速度差较大,例如 CPU 与内存或 I/O 设备的速度差非常大,当传输数据量很大时,CPU 经常需要等待;
  • 缺点 2: 所有的信号都要经过同一个共享的总线,不允许两个以上的部件同时传输信号。

单总线架构:

img

因此,单总线系统很容易形成系统的性能瓶颈,就算是增大总线的带宽也无法从根本上解决系统性缺陷。目前,单总线结构只出现在微型计算机中。大多数现代计算机都采用了分层次多总线结构,所有的设计思路都是围绕单总线架构存在的 2 个缺点展开的:

  • 应对缺点 1: 将高速部件和低速部件分为不同层级,不同层级之间使用独立的总线,减少高速部件对低速部件的等待;
  • 应对缺点 2: 增加多条总线,使得数据可以同时在多个部件之间传输。

2. 双独立总线:片内 & 片外

现代 CPU 中通常会使用高速缓存,由于 “CPU-高速缓存” 和 “CPU - 内存” 的速度差非常大,计算机系统选择在 CPU 芯片内和 CPU 芯片外使用 双独立总线(Dual Independent Bus,DIB):

  • 前端总线(Front Side Bus,FSB): CPU 与外部连接的总线(即 CPU 连接北桥芯片的总线);
  • 后端总线(Back Side Bus,BSB): 也叫本地总线(Local Bus)或片内总线(On-chip Bus),是 CPU 芯片内部独立使用的总线。CPU 芯片内部一个或多个核心、Cache 之间的通信将不需要占用芯片外的系统总线。

提示: 前端总线和系统总线的概念容易混淆,不同资料的说法不一。我的理解是:前端总线是 “特指” 某些 Intel CPU 架构中,CPU 芯片与外部连接的这条总线,而系统总线 “泛指” 连接计算机各个部件的所有总线。小彭在后续专栏内容都会按照此理解讨论。

前端总线和后端总线:

img

3. 南北桥架构

南北桥架构是 Intel 提出的总线架构,也叫 Hub 架构 。它将计算机部件分为高速部件和低速部件两类,分为北桥芯片组合和南桥芯片组,中间用两颗桥芯片连接。使用南北桥设计有 2 个优点:

  • 1、缓冲功能: 南北桥芯片实现了两类总线信号速度缓冲;
  • 2、桥接功能: 南北桥芯片实现了两类总线信号的转换,有利于系统升级换代。例如在升级 CPU 时,只需要改动 CPU 和北桥芯片,其它南桥部分不需要改动。

南北桥架构:

img

  • 北桥芯片(Northbridge): 北桥处理高速信号。北桥芯片连接的设备都是高速传输设备,包含 CPU、GPU、存储器与南桥的通信。北桥芯片也是 CPU 与外部连接的纽带;
  • 南桥芯片(Southbridge): 南桥处理低速信号。南桥芯片连接的大多是 I/O 设备,例如 PCI 总线、USB 适配器、显卡适配器、硬盘控制器;
  • 内存控制器(Memory Controller): 管理 CPU 和内存之间的总线数据传输,控制着存储器的读取和写入信号,并且定时刷新 DRAM 内的数据(DRAM 的存储单元包含电容,会自动漏电);
  • 内存总线(Memory Bus): 连接北桥芯片与存储器的总线;
  • DMI 总线(Direct Media Interface): 连接北桥芯片和南桥芯片的专用总线;
  • I/O 总线:连接南桥芯片与 I/O 设备的总线;
    • PCI 局部总线: 连接高速 I/O 设备的标准;
    • ISA 局部总线: 连接低速 I/O 设备的标准。

4. 前端总线瓶颈

前端总线是 CPU 连接外界的唯一通道,因此前端总线的数据传输能力对于计算机系统的整体性能影响非常大。 近年来随着 CPU 主频不断提升,前端总线频率却一直跟不上后端总线频率,从而出现性能瓶颈。

为了解决这个问题,传统的南北桥架构被重新设计,北桥芯片的功能几乎都移动到 CPU 内部变成 “片上北桥”。前端总线被淘汰,CPU / 片上北桥继续使用 DMI 连接南桥或 PCH 等外部设备。

总线仲裁

总线既有共享性又有独占性,听起来有点矛盾,其实是表现的时机不一样:

  • 共享性: 总线的共享性是指总线对所有连接的设备共享,主从模块能通过总线传输数据。
  • 独占性: 总线的独占性是指同一时刻,只允许一个部件占有总线的控制权,这个部件就是主模块,主模块可以与一个或多个从模块通信,但同一时刻只有一个主模块。

总线的独占性天然地将事务串行化: 如果多个部件同时向总线发出总线事务,总线仲裁(Bus Arbitration)单元会对竞争做出总裁,未获胜的事务只能等待获胜的事务处理完成后才能执行。当其中一个总线事务在执行时,其他总线事务都会被禁止。

参考

https://juejin.cn/post/7158122900355022855

posted @ 2025-06-06 11:48  光風霽月  阅读(33)  评论(0)    收藏  举报