Fork me on GitHub

重学计算机组成原理(二)- 制定学习路线,攀登“性能”之巅

0 学习路线的知识点概括

学习计算机组成原理,就是学习计算机是如何协调运行的

  • 计算机组成原理的英文叫Computer Organization

Organization 意"组织机构"。

该组织机构能够进行各种计算、控制、读取输入,进行输出,达成各种强大的功能。

把整个计算机组成原理的知识点拆分成了四大部分

  • 计算机的基本组成
  • 计算机的指令和计算
  • 处理器设计
  • 存储器和I/O设备。

0.1 计算机的基本组成

计算机的硬件组成

这些硬件,怎么对应到经典的冯·诺依曼体系结构的

除此之外,还需要了解计算机的两个核心指标

  • 性能
  • 功耗

性能和功耗也是我们在应用和设计五大基本组件中需要重点考虑的因素。

0.2 计算机的指令和计算

需要搞明白,我们每天撰写的一行行C、Java、PHP程序,是怎么在计算机里面跑起来的。

  • 了解我们的程序是怎么通过编译器和汇编器,变成一条条机器指令这样的编译过程(编译过程展开,就是编译原理)
  • 知道我们的操作系统是怎么链接、装载、执行这些程序的(深入学习,就是操作系统)。而这一条条指令执行的控制过程,就是由计算机五大组件之一的控制器来控制的。

计算部分,要从二进制和编码开始,理解我们的数据在计算机里的表示,以及我们是怎么从数字电路层面,实现加法、乘法这些基本的运算功能的。

实现这些运算功能的ALU(Arithmetic Logic Unit/ALU),算术逻辑单元,计算机五大组件之一的运算器。

特别重要的就是浮点数(Floating Point)。

浮点数是我们在日常运用中非常容易用错的一种数据表示形式。掌握浮点数能让你对数据的编码、存储和计算能够有一个从表到里的深入理解。尤其在AI火热的今天,浮点数是机器学习中重度使用的数据表示形式,掌握它更是非常有必要。

0.3 CPU的设计

CPU时钟可以用来构造寄存器和内存的锁存器和触发器,因此,CPU时钟应该是我们学习CPU的前导知识。搞明白我们为什么需要CPU时钟(CPU Clock),以及寄存器和内存是用什么样的硬件组成的之后,我们可以再来看看,整个计算机的数据通路是如何构造出来的。

数据通路,其实就是连接了整个运算器和控制器,并最终组成了CPU。而出于对于性能和功耗的考虑,你要进一步理解和掌握面向流水线设计的CPU、数据和控制冒险,以及分支预测的相关技术。

既然CPU作为控制器要和输入输出设备通信,那么我们就要知道异常和中断发生的机制。在CPU设计部分的最后,我会讲一讲指令的并行执行,看看如何直接在CPU层面,通过SIMD来支持并行计算。

0.4 存储器的原理

通过存储器的层次结构作为基础的框架引导,需要掌握从上到下的CPU高速缓存、内存、SSD硬盘和机械硬盘的工作原理,它们之间的性能差异,以及实际应用中利用这些设备会遇到的挑战。存储器其实很多时候又扮演了输入输出设备的角色,所以你需要进一步了解,CPU和这些存储器之间是如何进行通信的,以及我们最重视的性能问题是怎么一回事;理解什么是IO_WAIT,如何通过DMA来提升程序性能。

对于存储器,我们不仅需要它们能够正常工作,还要确保里面的数据不能丢失。于是你要掌握我们是如何通过RAID、Erasure Code、ECC以及分布式HDFS,这些不同的技术,来确保数据的完整性和访问性能。

计算机组成原理的学习办法

相较于整个计算机科学中的其他科目,计算机组成原理更像是整个计算机学科里的“纲要”。这门课里任何一个知识点深入挖下去,都可以变成计算机科学里的一门核心课程。

  • 程序怎样从高级代码变成指令在计算机里面运行,对应着“编译原理”和“操作系统”这两门课程
  • 计算实现背后则是“数字电路”
  • 如果要深入CPU和存储器系统的优化,必然要深入了解“计算机体系结构”。

为了更快更好地学计算机组成,总结了三个学习方法

学会提问自己来串联知识点

学完一个知识点之后,你可以从下面两个方面,问一下自己。

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

无论是程序的编译、链接、装载和执行,以及计算时需要用到的逻辑电路、ALU,乃至CPU自发为你做的流水线、指令级并行和分支预测,还有对应访问到的硬盘、内存,以及加载到高速缓存中的数据,这些都对应着我们学习中的一个个知识点。建议你自己脑子里过一遍,最好时口头表述一遍或者写下来,这样对你彻底掌握这些知识点都会非常有帮助。

写一些示例程序来验证知识点

计算机科学是一门实践的学科。计算机组成中的大量原理和设计,都对应着“性能”这个词。因此,通过把对应的知识点,变成一个个性能对比的示例代码程序记录下来,是把这些知识点融汇贯通的好方法。因为,相比于强记硬背知识点,一个有着明确性能对比的示例程序,会在你脑海里留下更深刻的印象。当你想要回顾这些知识点的时候,一个程序也更容易提示你把它从脑海深处里面找出来。

通过和计算机硬件发展的历史做对照

计算机的发展并不是一蹴而就的。从第一台电子计算机ENIAC(Electronic Numerical Integrator And Computer,电子数值积分计算机)的发明到现在,已经有70多年了。现代计算机用的各个技术,都是跟随实际应用中遇到的挑战,一个个发明、打磨,最后保留下来的。这当中不仅仅有学术层面的碰撞,更有大量商业层面的交锋。通过了解充满戏剧性和故事性的计算机硬件发展史,让你更容易理解计算机组成中各种原理的由来。

比如说,奔腾4和SPARC的失败,以及ARM的成功,能让我们记住CPU指令集的繁与简、权衡性能和功耗的重要性,而现今高速发展的机器学习和边缘计算,又给计算机硬件设计带来了新的挑战。

学习资料推荐

最有效的办法还是“读书百遍,其义自见”。对于不够明白的知识点,多搜索,多看不同来源的资料,多和朋友、同事、老师一起交流,一定能够帮你掌握好想要学习的知识点。

入门书籍

进阶书籍

拓展阅读

路线学习小结

学习不是死记硬背,学习材料也不是越多越好。

最有效的办法,不是短时间冲刺,而是有节奏地坚持,多在留言区和其他朋友一起交流,就更容易能够“积小步而至千里”,在程序员这个职业上有更长足的发展。

欢迎重学计算机组成原理 ! ! !

  • 买电脑时,“原来的电脑性能跟不上啦”
  • 写程序时,“这个程序性能可以优化一下”

这虚无缥缈的“性能”到底指的是什么呢?

我们能不能给性能下一个明确的定义,然后来进行准确的比较呢?

在计算机组成原理乃至体系结构中,“性能”都是最重要的一个主题。

学习和研究计算机组成原理,就是在理解计算机是怎么运作的,以及为什么要这么运作。

“为什么”所要解决的事情,很多时候就是提升“性能”。

1 时间的倒数 - 性能

计算机的性能,其实和体力劳动很像,好比是我们要搬东西。

对于计算机的性能,我们需要有个标准来衡量。这个标准中主要有两个指标。

1.1 响应时间(Response time)/ 执行时间(Execution time)

让计算机“跑得更快”。

我们执行一个程序,到底需要花多少时间。花的时间越少,自然性能就越好。

  • 实际系统里性能监测工具NewRelic中的响应时间,代表了每个外部的Web请求的执行时间

1.2 吞吐率(Throughput)/ 带宽(Bandwidth)

让计算机“搬得更多”。

服务器使用的网络带宽,通常就是一个吞吐率性能指标.吞吐率是指我们在一定的时间范围内,到底能处理多少事情。这里的“事情”,在计算机里就是处理的数据或者执行的程序指令。

和搬东西对比,如果响应时间短,跑得快,我们可以来回多跑几趟搬几趟。

所以缩短程序的响应时间,一般来说都会提升吞吐率

除了缩短响应时间,我们还有别的方法吗?当然!

我们还可以多找几个人一起来搬,这就类似服务器都是多核的。

人多力量大,同时处理数据,在单位时间内就可以处理更多数据,吞吐率自然也就上去了。

提升吞吐率的办法有很多。大部分时候,我们只要多加一些机器,多堆一些硬件就好了。

但是响应时间的提升却没有那么容易,因为CPU的性能提升其实在10年前就处于“挤牙膏”的状态了,所以我们得慎重地来分析对待。

下面我们具体来看。

我们一般把性能,定义成响应时间的倒数,也就是:

性能 = 1/响应时间

响应时间越短,性能数值越高。

同样一个程序

  • 在Intel最新的CPU Coffee Lake上,只需要30s就能运行完成
  • 而在5年前CPU Sandy Bridge上,需要1min才能完成

那么我们自然可以算出来,Coffee Lake的性能是1/30,Sandy Bridge的性能是1/60,两个的性能比为2。于是,我们就可以说,Coffee Lake的性能是Sandy Bridge的2倍。

过去几年流行的手机跑分软件,就是把多个预设好的程序在手机上运行,然后根据运行需要的时间,算出一个分数来给出手机的性能评估。

而在业界,各大CPU和服务器厂商组织了一个叫作SPEC(Standard Performance Evaluation Corporation)的第三方机构,专门用来指定各种“跑分”的规则

  • 一份SPEC报告通常包含了大量不同测试的评分

SPEC提供的CPU基准测试程序,就好像CPU届的“高考”,通过数十个不同的计算程序,对于CPU的性能给出一个最终评分。

这些程序丰富多彩,有编译器、解释器、视频压缩、人工智能国际象棋等等,涵盖了方方面面的应用场景。感兴趣的话,你可以点击这里

2 计算机的计时单位:CPU时钟

虽然时间是一个很自然的用来衡量性能的指标,但是用时间来衡量时,有两个问题。

2.1 时间的测不准原理

如果用你自己随便写的一个程序,来统计程序运行的时间,每一次统计结果不会完全一样。

为什么会不准呢?这里面有好几个原因。

2.1.1 统计时间方法

我们统计时间是用类似于“掐秒表”一样,记录 程序运行结束时间减去程序开始运行的时间。

这个时间也叫Wall Clock Time或者Elapsed Time

就是在运行程序期间,挂在墙上的钟走掉的时间。

但计算机可能同时运行着好多个程序,CPU实际上不停地在各个程序之间进行切换。

在这些走掉的时间里,很可能CPU切换去运行别的程序了。

而且,有些程序在运行的时候,可能要从网络、硬盘去读取数据,要等网络和硬盘把数据读出来,给到内存和CPU。

要想准确统计某程序的实际运行时间,进而比较程序之间的性能,须把这些额外时间除掉

那这件事怎么实现呢???

Linux下有一个叫time的命令,可助我们一臂之力,同样的Wall Clock Time下,程序实际在CPU上到底花了多少时间。

我们简单运行一下time命令。它会返回三个值

  • 第一个 real time
    也就是我们说的Wall Clock Time,即运行程序整个过程中流逝掉的时间
  • 第二个 user time
    CPU在运行你的程序,在用户态运行指令的时间
  • 第三个sys time
    CPU运行你的程序,在操作系统内核里运行指令的时间

程序实际花费的CPU执行时间(CPU Time),就是user time加上sys time

一般情况下,如果user+sys比real大,甚至仅user比real大的情况出现,都是因为对应的程序被多个进程或者多个线程并行执行了,也很常见。

在多核或者多cpu的机器上运行,seq和wc命令会分配到两个cpu上,user和sys是两个cpu时间相加的,而real只是现实时钟里走过的时间,极端情况下user+sys可以到达real的两倍

虽然seq和wc这两个命令都是单线程运行的,但是这两个命令在多核cpu运行的情况下,会分别分配到两个不同的cpu,于是user和sys的时间都是两个cpu上运行的时间之和,就可能超过real的时间。

可以这样来快速验证,运行

time seq 100000000 | wc -l &

让这个命令多跑一会儿,并且在后台运行。

然后利用 top 命令看不同进程的cpu占用情况

你会在top的前几行里看到seq和wc的cpu占用都接近100,实际是各被分配到了一个不同的cpu执行。

2.1.2 不一定可直接比较出性能

即使我们已经拿到了CPU时间,我们也不一定可以直接“比较”出两个程序的性能差异

即使在同一台计算机上,CPU可能满载运行也可能降频运行,降频运行的时候自然花的时间会多一些。

除CPU外,时间这个性能指标还会受到主板、内存这些其他相关硬件的影响。

所以,我们需要对“时间”这个我们可以感知的指标进行

2.2 CPU时间拆解

程序的CPU执行时间=CPU时钟周期数×时钟周期时间
  • 时钟周期时间
    你在买电脑的时候,一定关注过CPU的主频
    我手头的这台电脑就是
  • 2.8 GHz Intel Core i7

这里的2.8GHz就是电脑的主频(Frequency/Clock Rate)。

这个2.8GHz,我们可以先简单地理解为,CPU在1秒时间内,可以执行的简单指令的数量是2.8G条。

更准确点,这个2.8GHz就代表,我们CPU的一个“钟表”能够识别出来的最小的时间间隔。

就像我们挂在墙上的挂钟,都是一秒一秒地走,所以通过墙上的挂钟能够识别出来的最小时间单位就是秒。

而在CPU内部,有一个叫晶体振荡器(Oscillator Crystal)的东西,简称为晶振。

把晶振当成CPU内部的电子表来使用。

晶振带来的每一次“滴答”,就是时钟周期时间。

在我这个2.8GHz的CPU上,这个时钟周期时间,就是1/2.8G。

我们的CPU,是按照这个“时钟”提示的时间来进行自己的操作。

主频越高,意味着这个表走得越快,我们的CPU也就走得越快。

“超频”,这说的其实就相当于把买回来的CPU内部的钟给调快了,于是CPU的计算跟着这个时钟的节奏,也就自然变快了。当然这个快不是没有代价的,CPU跑得越快,散热的压力也就越大。就和人一样,超过生理极限,CPU就会崩溃了。

  • 回顾之前的CPU执行时间的公式程序的CPU执行时间=CPU时钟周期数×时钟周期时间

最简单的提升性能方案,自然缩短时钟周期时间,也就是提升主频。

换句话说,就是换一块好一点的CPU。

不过,这个是我们这些底层的研发人员控制不了的,所以我们就把目光挪到了乘法的另一个因子——CPU时钟周期数上。

如果能够减少程序需要的CPU时钟周期数量,一样能够提升程序性能。

  • 对于CPU时钟周期数,我们可以再做一个分解,把它变成CPU时钟周期数 = 指令数×每条指令的平均时钟周期数(Cycles Per Instruction,简称CPI)不同的指令需要的Cycles是不同的
    加法和乘法都对应着一条CPU指令
    但是乘法需要的Cycles就比加法要多,自然也就慢。
    在这样拆分了之后,我们的程序的CPU执行时间就可以变成这样三个部分的乘积。
程序的CPU执行时间=指令数 × CPI × Clock Cycle Time

因此,要解决性能问题,就是要优化这三者。

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

把自己想象成一个CPU,坐在那里写程序。
计算机主频就好像是你的打字速度,打字越快,你自然可以多写一点程序。

CPI相当于你在写程序的时候,熟悉各种快捷键,越是打同样的内容,需要敲击键盘的次数就越少。

指令数相当于你的程序设计得够合理,同样的程序要写的代码行数就少。

如果三者皆能实现,你自然可以很快地写出一个优秀的程序,“性能”从外面来看就是好的。

3 总结

学完本文,对“性能”这个名词,你应该有了更清晰的认识。

主要对于“响应时间”这个性能指标进行抽丝剥茧,拆解成了计算机时钟周期、CPI以及指令数这三个独立的指标的乘积,并且为指明了优化计算机性能的三条康庄大道。

也就是,提升计算机主频,优化CPU设计使得在单个时钟周期内能够执行更多指令,以及通过编译器来减少需要的指令数。

后面会讲解,具体怎么在电路硬件、CPU设计,乃至指令设计层面,提升计算机的性能。

参考

posted @ 2019-08-10 19:31  公众号-JavaEdge  阅读(1146)  评论(0编辑  收藏  举报