学计组的亿点小建议!
很早之前分享过我学计算机网络和操作系统的心得,详见:怎么学操作系统和计算机网络呀?
期间一直有不少读者问计算机组成原理怎么学,大部分人觉得这个学科跟硬件有关系就非常怕。
计算组成原理确实是分为两个方向,一个是硬件电路的,一个是软件程序的。
我自己本身是干开发的,所以我这次分享的机组资料是跟软化程序有关的,不会涉及到硬件电路的东西,即使你不会数字电路、微机原理也是可以直接学习的。
好了,不多废话,直接开车了!
学计组有什么用?
我猜应该很多人都有这样的困惑,就是觉得学计组有什么用?感觉实际工作过程中用不到,感觉理论学了个寂寞。
这个困惑很正常,因为学计组这东西主要是为了搞懂计算机是如何工作的,计算机怎么工作的都是被前辈们实现好的,我们一般也不会参与到造计算机这种工作中。
但是学了计组后,你能看到的视角是和别人不一样的,而这个不一样的视角就能在一些关键的问题上得到突破。
我这里举个简单例子,你觉得下面这两个 for
循环哪个效率会更高呢?为什么会更高呢?
这个问题的关键在于,大家知不知道 CPU Cache 这个东西。
我自己曾经在还没有学计组的时候,只觉得存储设备就是我们常见的内存和硬盘,学了后我才发现还有 CPU Cache 这个东西,而能不能充分利用到这个东西,就决定你程序的性能。
CPU Cache 是 CPU 内部的一个缓存,它的读写速度远高于内存,所以它充当内存的缓存角色,当 CPU 要访问的数据命中了 CPU Cache,就不用去从内存上找数据,就像 MySQL 和 Redis 之间的关系。
CPU Cache 一般会分为三层,分别是 L1 Cache、L2 Cache、L3 Cache ,其中 L1 Cache 是各个 CPU 核心独立的,剩下的 L2 Cache、L3 Cache 是各个核心共享的。
CPU Cache 的数据是从内存中读取过来的,它是以一小块一小块读取数据的,而不是按照单个数组元素来读取数据的,在 CPU Cache 中的,这样一小块一小块的数据,称为 Cache Line(缓存块)。
如果 L1 Cache Line 大小是 64 字节,也就意味着 L1 Cache 一次载入数据的大小是 64 字节。
比如,有一个 int array[100] 的数组,当载入 array[0] 时,由于这个数组元素的大小在内存只占 4 字节,不足 64 字节,CPU 就会顺序加载数组元素到 array[15] ,意味着 array[0]~array[15] 数组元素都会 被缓存在 CPU Cache 中了,因此当下次访问这些数组元素时,会直接从 CPU Cache 读取,而不用再从内 存中读取,大大提高了 CPU 读取数据的性能。
CPU Cache 的概念简单介绍完了,再来说说刚才的 for 循环问题。
我直接说答案:形式一 array[i][j]
执行时间比形式二 array[j][i]
快好几倍。
之所以有这么大的差距,是因为二维数组 array
所占用的内存是连续的,比如长度 N
的指是 2
的话,那么内存中的数组元素的布局顺序是这样的:
形式一用 array[i][j]
访问数组元素的顺序,正是和内存中数组元素存放的顺序一致。当 CPU 访问 array[0][0]
时,由于该数据不在 Cache 中,于是会「顺序」把跟随其后的 3 个元素从内存中加载到 CPU Cache,这样当 CPU 访问后面的 3 个数组元素时,就能在 CPU Cache 中成功地找到数据,这意味着缓存命中率很高,缓存命中的数据不需要访问内存,这便大大提高了代码的性能。
而如果用形式二的 array[j][i]
来访问,则访问的顺序就是:
你可以看到,访问的方式跳跃式的,而不是顺序的,那么如果 N 的数值很大,那么操作 array[j][i]
时,是没办法把 array[j+1][i]
也读入到 CPU Cache 中的,既然 array[j+1][i]
没有读取到 CPU Cache,那么就需要从内存读取该数据元素了。很明显,这种不连续性、跳跃式访问数据元素的方式,可能不能充分利用到了 CPU Cache 的特性,从而代码的性能不高。
那访问 array[0][0]
元素时,CPU 具体会一次从内存中加载多少元素到 CPU Cache 呢?这个问题,在前面我们也提到过,这跟 CPU Cache Line 有关,它表示 CPU Cache 一次性能加载数据的大小,可以在 Linux 里通过 coherency_line_size
配置查看 它的大小,通常是 64 个字节。
也就是说,当 CPU 访问内存数据时,如果数据不在 CPU Cache 中,则会一次性会连续加载 64 字节大小的数据到 CPU Cache,那么当访问 array[0][0]
时,由于该元素不足 64 字节,于是就会往后顺序读取 array[0][0]~array[0][15]
到 CPU Cache 中。顺序访问的 array[i][j]
因为利用了这一特点,所以就会比跳跃式访问的 array[j][i]
要快。
因此,遇到这种遍历数组的情况时,按照内存布局顺序访问,将可以有效的利用 CPU Cache 带来的好处,这样我们代码的性能就会得到很大的提升,
CPU Cache 的内容远不止于我说的这些,还有缓存一致性协议、伪共享、write through 和 write back 的方式等,这些都是非常重要的知识。
计组怎么学?
计算机组成原理会有两个方向深入的点,一个是面向硬件电路,一个是面向软件开发的。
我自己本身就是个开发者,所以下面分享的学习资料都是偏向软件开发点计组原理,对于硬件电路这块的资料不做介绍,因此不会涉及到数字电路、微机原理等这些课程。
计算机组成原理主要有四大块内容。
第一大块,计算机的基本组成,主要包含:
硬件设备组成:CPU、主板、内存、硬盘、显示器等;
冯诺依曼体系结构:运算器、控制器、存储器、输入设备、输出设备;
计算机性能:CPU 主频、响应时间、吞吐率
第二块,计算机的指令和运算,主要包含:
计算机指令:机器码(编译 -> 汇编 -> 机器码、指令格式和跳转、函数调用和程序栈)、程序的编译、链接、装载和执行;
计算机运算:二进制编码(整数、反码、补码、浮点数、定点数)、数字电路(门电路、加法器、乘法器);
第三块,处理器设计,主要包含:
CPU:建立数据通路、面向流水线和设计、控制冒险和数据冒险、分支预测、异常和中断、并行计算
第四块,存储器和 I/O 系统,主要包含:
存储器的层次结构:SRAM 存储技术、寄存器、CPU 高速缓存、内存、固态硬盘、机械硬盘;
存储器和 I/O 系统:虚拟内存、CPU和内存的通信、DMA技术、访问输入输出设备;
CPU 高速缓存:局部性原理、缓存一致性协议、伪共享问题、write through 和 write back;
虚拟存储:缺页异常、TLB 加速地址转化、MMU 虚拟地址和物理地址转换;
其中第一、第二、第四是对开发者而言是比较重要的内容,而第三部分处理器的设计如果没时间可以先不用去了解。
别看这些内容很多,就被吓到了。
建议你在学习计算机原理的时候,心里要带着一个核心的问题去学习:「我们写的程序是如何在计算机里跑起来的?」
带着这个问题去学你就不知觉的会把知识点给串起来了,一层层的深入下去,一个知识点一个知识展开。
如果把这个问题能解释出来,那你对计算机组成原理有了一定的认识了。
计组 - 入门学习
计算机科学速成课
先极力推荐 b 站的《计算机科学速成课》,这个课程是国外录制的,内容真的是好,视频的动画很精美,讲课的时候不会很死板,反正就是不看后悔、相见很晚系列。
对于入门计算机组成,可以先看前 10 个视频,看完这 10 个视频也就不到 2 个小时,看完前 10 个视频对计算机的工作方式就有一个基本的了解了。
看完前 10 个视频就可以开始看书了。
《计算机是怎么样跑起来》和《程序是怎么跑起来的》
讲真,不太建议小白一上来就看那些厚的不行的计算机组成原理的黑皮书,这些书是经典的没错,也正是由于它们是经典的,所以这些书的知识体系很全、很多、很厚。
但是这样很容易让初学者迷失在里头,可能刚兴致勃勃看几十页就放弃了,于是这些厚的不行的书就成为了你们的垫书神器,知识没学多少,颈椎病倒是治好了。
对于初学者,我推荐两本书《计算机是怎么样跑起来》和《程序是怎么跑起来的》,这两本很薄而且图文并茂,作者都是用大白话的方式来阐述知识,这点对初学者非常友好。
这两本不用 1 个月就能看完,因为在看这两本书的时候,你会看的很顺畅,相比学习的心态,你更多的是会带着「好奇心」的心态去读。
其中《程序是怎么跑起来的》是一个「微缩版本」的计算机组成原理,你可以只选择看这一本。
从这本书的名字也可以知道,它是从计算机是怎么运行程序的视角来讲的,然后把涉及到的计算机硬件和它们之间是如何协作的一点一点的给大家带出来,让大家能瞬间明白这些计算机硬件的作用。
这本仅仅是入门级别,主要的作用是让初学者明白计算机组成原理这门课是学什么的,以及梳理主要的知识体系,用了这本书的概念后,在去深入计算机组成的时候,就不会雨里雾里的。
《编码》
《编码:隐匿在计算机软硬件背后的语言》这本书也很不错,是本科普类的书,非常适合非科班的同学。
主要讲是计算机工作的原理(二进制编码、加减法运算、计算机部件、浮点数定点数、处理器等),也就是跟计组息息相关的知识,它的内容很有趣味性,并不想教科书那样晦涩难懂,丝毫不会让你感到生硬,读起来很畅快。
计组 - 深入学习
《计算机组成与设计:硬件 / 软件接口》
想要深入学习计算机组成原理的同学,我首先推荐《计算机组成与设计:硬件 / 软件接口》这本书,
这本书确实很厚,差不多 500 多页,但是书从来没有人规定一定要从头读到尾,一页页的读的。重要的不是看完一本书,而是从书上学到多少,解决了什么问题。
大家可以挑这几个章节看,跟开发者关系比较大的章节:
第一章:计算机抽象以及相关技术,这个章节主要是介绍了计算机组成的思想,可以简单快读看,不用重点读;
第二章:指令,大体上讲的是计算机是如果识别和运行指令的,以及代码到指令的过程;
第三章:计算机的算数运算,介绍的是计算机是如何进行加减乘除法的,以及浮点数的运算;
第五章:层次化存储,讲的是计算机的存储层次结构,而且重点讲的是 CPU Cahe。
计算机组成原理视频课程
看书觉得很累,也可以结合视频一起看,这里推荐哈工大的《计算机组成原理》视频,在 b 站就可以直接看,大家自己去搜索就可以。
看书和看视频可以相互结合的,比如你看视频看了计算机指令的内容,然后你可以不用继续往下看,可以回到一本书上,看书上对应这个章节的内容,这是个很好的学习方法,视频和书籍相辅相成。
你要是觉得哈工大的计组课程太难,你可以看王道考研的计算机组成原理的视频课程,同样 b 站就可以看。
这个视频虽然是针对考研的,但是也是可以作为学习计组的资料,讲的内容不会太深,适合你快速建立计算机组成原理体系,和梳理计组知识的脉络。
《深入理解计算系统》
另外,在推荐一本《深入理解计算系统》这本书,人称 CSAPP。
可能大家以为这本书是讲操作系统的,我最开始也以为是这样。后面当我开始啃这本书的时候,发现我大错特错,它远不止我想的那样。
这本书是从程序员的角度学习计算机系统是如何工作的,通过描述程序是如何映射到计算机系统上,程序是如何执行的,以及程序效率低下的原因,这样的方式可以让大家能更好的知道「程序与计算机系统」的关系。
CSAPP 涵盖的内容非常多,有计算机组成 + 操作系统 + 汇编 + C语言 + Linux系统编程,涉猎的领域比较多,是一本综合性的书,更是一本程序员修炼内功的指引书。
CSAPP 主要包括以下内容:
信息表示(如何使用二进制表示整型、浮点数等);
C 和汇编语言的学习(通过汇编语言更深入地理解C语言是什么);
计算机体系结构(存储层次结构、局部性原理、处理器体系结构);
编译链接(C语言如何从文本变成可执行文件、静态链接、动态链接);
操作系统的使用(异常控制流、虚拟内存、多个系统调用介绍);
网络及并发编程(并发的基本概念、网络相关的系统调用的介绍)。
你会发现有部分内容和《计算机组成与设计:硬件 / 软件接口》这本书重合了,重合的部分就是重中之重的计算机组成原理知识了,而且内容都是差不多的,你可以看完一本书的内容,然后跳到另外一本看相同章节的内容,多本书的结合可以让我们更加容易理解。
这两本书有个区别:
《计算机组成与设计:硬件 / 软件接口》讲的指令格式是 RISC 的;
《深入理解计算系统》讲的指令格式是 x86 的;
其他重合的计组知识都大同小异。
CSAPP 的视频课程是国外老师录制的,但是在 b 站已经有好人帮我们做了中文字幕,看了这视频,相当于在国外上了一门计算机课的感觉。
如果你是在校生,有了一定 C 语言基础后,非常建议你就开始看这本书,有精力也可以做做 CSAPP 的 lab。越早开始看,你的收益就越大,因为当计算机体系搭建起来后,你后面再深入每一个课程的时候,你会发现学起来会比较轻松些。
对于已经工作了,但是计算机系统没有一个清晰认识的读者,也可以从这本书开始一点一点学起来,这本书是很厚,但是并不一定要把书完完看完,每个章节的知识点还是比较独立的,有关硬件的章节我们可以选择跳过。
这就是我学计组的心得啦。
没学过计组的同学,可以找个时间补补了,提高下自己的「内功」。
干就完啦!