本书未发布

开篇

未匹配的标注

简图

开篇

CPU + 内存 + 主板

1.cpu (centeral progressing unit)
2.内存 (memory):
3.主板(mother board): 芯片组(chipset)总线(bus),总线速度决定数据传输速度。南桥芯片组控制I/O设备和总线的通信 4.其他: 电源,硬盘,I/O设备,机箱,风扇 5.显卡: Graphics Card 里面的图形处理器GPU(Graphics Processing Unit)

冯·诺依曼体系结构

  • 首先是一个包含算术逻辑单元(Arithmetic Logic Unit,ALU)处理器寄存器(Processor Register)处理器单元(Processing Unit),用来完成各种算术和逻辑运算。因为它能够完成各种数据的处理或者计算工作,因此也有人把这个叫作数据通路(Datapath)或者运算器。

  • 然后是一个包含指令寄存器(Instruction Register)程序计数器(Program Counter)控制器单元(Control Unit/CU),用来控制程序的流程,通常就是不同条件下的分支和跳转。在现在的计算机里,上面的算术逻辑单元和这里的控制器单元,共同组成了我们说的 CPU。

  • 接着是用来存储数据(Data)和指令(Instruction)的内存。以及更大容量的外部存储,在过去,可能是磁带、磁鼓这样的设备,现在通常就是硬盘。

  • 最后就是各种输入和输出设备,以及对应的输入和输出机制。

开篇

学习路径

开篇

划分为四大部分:

  1. 计算机的基本组成

    也就是运算器、控制器、存储器、输入设备和输出设备这五大基本组件。除此之外,你还需要了解计算机的两个核心指标,性能和功耗。性能和功耗也是我们在应用和设计五大基本组件中需要重点考虑的因素。

  2. 计算机的指令和运算

    (1) 在计算机指令部分,你需要搞明白,我们每天撰写的一行行 C、Java、PHP 程序,是怎么在计算机里面跑起来的?
    (2) 需要了解我们的程序是怎么通过编译器和汇编器,变成一条条机器指令这样的编译过程(编译原理课程)
    (3) 需要知道我们的操作系统是怎么链接、装载、执行这些程序的(计算机操作系统)
    而这一条条指令执行的控制过程,就是由计算机五大组件之一的控制器来控制的。
    在计算机的计算部分,你要从二进制和编码开始,理解我们的数据在计算机里的表示,以及我们是怎么从数字电路层面,实现加法、乘法这些基本的运算功能的。实现这些运算功能的 ALU(Arithmetic Logic Unit/ALU),也就是算术逻辑单元,其实就是我们计算机五大组件之一的运算器。
    了解浮点数的设计,这是大数据使用最多的一种数据类型。

  3. 处理器设计

    CPU 时钟可以用来构造寄存器和内存的锁存器和触发器,CPU时钟是学习CPU前导条件。
    数据通路,其实就是连接了整个运算器和控制器,并最终组成了 CPU。而出于对于性能和功耗的考虑,你要进一步理解和掌握面向流水线设计的 CPU、数据和控制冒险,以及分支预测的相关技术。
    既然 CPU 作为控制器要和输入输出设备通信,那么我们就要知道异常和中断发生的机制。在 CPU 设计部分的最后,我会讲一讲指令的并行执行,看看如何直接在 CPU 层面,通过 SIMD 来支持并行计算。

  4. 存储器和IO系统

    通过存储器的层次结构作为基础的框架引导,你需要掌握从上到下的 CPU 高速缓存、内存、SSD 硬盘和机械硬盘的工作原理,它们之间的性能差异,以及实际应用中利用这些设备会遇到的挑战。存储器其实很多时候又扮演了输入输出设备的角色,所以你需要进一步了解,CPU 和这些存储器之间是如何进行通信的,以及我们最重视的性能问题是怎么一回事;理解什么是 IO_WAIT,如何通过 DMA 来提升程序性能。对于存储器,我们不仅需要它们能够正常工作,还要确保里面的数据不能丢失。于是你要掌握我们是如何通过 RAID、Erasure Code、ECC 以及分布式 HDFS,这些不同的技术,来确保数据的完整性和访问性能。

首先,学会提问自己来串联知识点。学完一个知识点之后,你可以从下面两个方面,问一下自己。

  • 我写的程序,是怎样从输入的代码,变成运行的程序,并得到最终结果的?
  • 整个过程中,计算器层面到底经历了哪些步骤,有哪些地方是可以优化的?

性能

提升性能,就是提升CPU的执行效率,减少CPU的执行时间。

程序的 CPU 执行时间 = 指令数×CPI×Clock Cycle Time
CPI: 指令数×每条指令的平均时钟周期数(Cycles Per Instruction,简称 CPI)

  • 时钟周期时间,就是计算机主频,这个取决于计算机硬件。我们所熟知的摩尔定律就一直在不停地提高我们计算机的主频。比如说,我最早使用的 80386 主频只有 33MHz,现在手头的笔记本电脑就有 2.8GHz,在主频层面,就提升了将近 100 倍。
  • 每条指令的平均时钟周期数 CPI,就是一条指令到底需要多少 CPU Cycle。在后面讲解 CPU 结构的时候,我们会看到,现代的 CPU 通过流水线技术(Pipeline),让一条指令需要的 CPU Cycle 尽可能地少。因此,对于 CPI 的优化,也是计算机组成和体系结构中的重要一环。
  • 指令数,代表执行我们的程序到底需要多少条指令、用哪些指令。这个很多时候就把挑战交给了编译器。同样的代码,编译成计算机指令时候,就有各种不同的表示方式。

我们可以把自己想象成一个 CPU,坐在那里写程序。计算机主频就好像是你的打字速度,打字越快,你自然可以多写一点程序。CPI 相当于你在写程序的时候,熟悉各种快捷键,越是打同样的内容,需要敲击键盘的次数就越少。指令数相当于你的程序设计得够合理,同样的程序要写的代码行数就少。如果三者皆能实现,你自然可以很快地写出一个优秀的程序,你的“性能”从外面来看就是好的。

性能的提升(响应时间,吞吐率):

  1. 初期各厂商通过提升CPU的性能来提升CPU的执行效率。好比从走路->自行车->汽车->火车->飞机,速度的提升很快。但是到了飞机之后再想提升性能就有点难了。而且速度的提升也会带来散热和功耗的问题。
  2. 竟然CPU的性能已经到了瓶颈,但是可以提升吞吐率。好比原来每次飞一架飞机,现在每次飞2/4/8/16架飞机。这就是多核CPU。

    并发执行条件:
    第一,需要进行的计算,本身可以分解成几个可以并行的任务。好比上面的乘法和加法计算,几个人可以同时进行,不会影响最后的结果。
    第二,需要能够分解好问题,并确保几个人的结果能够汇总到一起。
    第三,在“汇总”这个阶段,是没有办法并行进行的,还是得顺序执行,一步一步来。

优化后的执行时间 = 受优化影响的执行时间 / 加速倍数 + 不受影响的执行时间

比如上面的各个向量的一小段的点积,需要 100ns,加法需要 20ns,总共需要 120ns。这里通过并行 4 个 CPU 有了 4 倍的加速度。那么最终优化后,就有了 100/4+20=45ns。即使我们增加更多的并行度来提供加速倍数,比如有 100 个 CPU,整个时间也需要 100/100+20=21ns。

其他提升性能的方法

1.加速大概率事件。最典型的就是,过去几年流行的深度学习,整个计算过程中,99% 都是向量和矩阵计算,于是,工程师们通过用 GPU 替代 CPU,大幅度提升了深度学习的模型训练过程。本来一个 CPU 需要跑几小时甚至几天的程序,GPU 只需要几分钟就好了。Google 更是不满足于 GPU 的性能,进一步地推出了 TPU。后面的文章,我也会为你讲解 GPU 和 TPU 的基本构造和原理。

2.通过流水线提高性能。现代的工厂里的生产线叫“流水线”。我们可以把装配 iPhone 这样的任务拆分成一个个细分的任务,让每个人都只需要处理一道工序,最大化整个工厂的生产效率。类似的,我们的 CPU 其实就是一个“运算工厂”。我们把 CPU 指令执行的过程进行拆分,细化运行,也是现代 CPU 在主频没有办法提升那么多的情况下,性能仍然可以得到提升的重要原因之一。我们在后面也会讲到,现代 CPU 里是如何通过流水线来提升性能的,以及反面的,过长的流水线会带来什么新的功耗和效率上的负面影响。

3.通过预测提高性能。通过预先猜测下一步该干什么,而不是等上一步运行的结果,提前进行运算,也是让程序跑得更快一点的办法。典型的例子就是在一个循环访问数组的时候,凭经验,你也会猜到下一步我们会访问数组的下一项。后面要讲的“分支和冒险”、“局部性原理”这些 CPU 和存储系统设计方法,其实都是在利用我们对于未来的“预测”,提前进行相应的操作,来提升我们的程序性能。

指令

五大类指令:
(1)计算类指令
(2)数据传输类指令
(3)与非逻辑判断指令
(4)计算类指令
(5)跳转类指令

a,b := 1,2 //数据传输类指令
if a == 1 && b == 2  { //条件分支类指令 和 与非逻辑判断指令
ret := a + 2 //计算类指令
}

goto AGAIN //跳转类指令

逻辑上,我们可以认为,CPU 其实就是由一堆寄存器组成的。而寄存器就是 CPU 内部,由多个触发器(Flip-Flop)或者锁存器(Latches)组成的简单电路。

N 个触发器或者锁存器,就可以组成一个 N 位(Bit)的寄存器,能够保存 N 位的数据。比方说,我们用的 64 位 Intel 服务器,寄存器就是 64 位的。

开篇

一个是 PC 寄存器(Program Counter Register),我们也叫指令地址寄存器(Instruction Address Register)。顾名思义,它就是用来存放下一条需要执行的计算机指令的内存地址。
第二个是指令寄存器(Instruction Register),用来存放当前正在执行的指令。
第三个是条件码寄存器(Status Register),用里面的一个一个标记位(Flag),存放 CPU 进行算术或者逻辑计算的结果。

除了这些特殊的寄存器,CPU 里面还有更多用来存储数据和内存地址的寄存器。这样的寄存器通常一类里面不止一个。我们通常根据存放的数据内容来给它们取名字,比如整数寄存器、浮点数寄存器、向量寄存器和地址寄存器等等。有些寄存器既可以存放数据,又能存放地址,我们就叫它通用寄存器。

条件分支类指令

if/else for/while 指令用的就是跳转类指令,switch/case用的也是跳转类指令吗?汇编代码上的区别是什么?

switch/case 如果生成跳转表,则和if/else不同,跳转时不是一个个的if比较执行,而是直接跳转到对应的地址。而如果case不多则生成的汇编代码和if/else一样。

内联和函数调用会临时存放在栈中。

ELF文件和静态链接

ELF文件:Execute Linkedable File (可执行可链接文件)
编写代码到CPU可执行的过程:

  • 编译器编译代码成汇编代码
  • 汇编代码通过汇编器成CPU可解析执行的机器代码

开篇

内存

说起来只是装载到内存里面这一句话的事儿,实际上装载器需要满足两个要求。

  • 第一,可执行程序加载后占用的内存空间应该是连续的。

    执行指令的时候,程序计数器是顺序地一条一条指令执行下去。这也就意味着,这一条条指令需要连续地存储在一起。

  • 第二,我们需要同时加载很多个程序,并且不能让程序自己规定在内存中加载的位置。

    虽然编译出来的指令里已经有了对应的各种各样的内存地址,但是实际加载的时候,我们其实没有办法确保,这个程序一定加载在哪一段内存地址上。因为我们现在的计算机通常会同时运行很多个程序,可能你想要的内存地址已经被其他加载了的程序占用了。

要满足这两个条件,想到的一个办法就是把程序用到的内存地址映射到实际的物理地址。我们把指令里用到的内存地址叫作虚拟内存地址(Virtual Memory Address),实际在内存硬件里面的空间地址,我们叫物理内存地址(Physical Memory Address)。

内存分段
开篇

缺点1:内存碎片
解决方法:内存交换后,把空闲内存合并在一起。
缺点2: 内存交换程序较大,耗费更多的时间。
解决方法:内存分页

交换内存的过程:先把内存置换到硬盘,在重新读取到内存。程序越大,置换时间就会越长。能明显感觉到卡顿。我们在给系统分区时,让我们指定的swap空间大小,就是用来给电脑做交换内存用的。当我们查询数据库,频繁发生交换内存时,就会用到swap空间。查询速度也会降低。这也是MySQL提升性能的重点考虑因素。

内存分页
将原来分段的内存,切成一小片,每片的大小默认为4KB。称为分页。程序也对应的分页。

优点:

  • 交换内存的大小变小了,速度更快。
  • 程序不需要一次性全部装载,用到哪一页装载哪一页即可。
  • 可以装载比内存更大的程序。

如果内存越大,交换内存的次数就会越小,理所当然的程序运行速度也会更快!这也是为什么MySQL在查询时尽量不用*的原因。

本文章首发在 LearnKu.com 网站上。

上一篇 下一篇
讨论数量: 0
发起讨论 只看当前版本


暂无话题~