1、芯片是怎么工作的呢?电能供给给芯片,芯片中的一种电子元件晶振(也就是石英晶体)通电后产生震荡,震荡会产生频率稳定的脉冲信号。通常这是一种高频的脉冲信号,每秒可达百万次。然后,我们通过谐振效应发放这个信号,形成方波。再通过电子元件调整这种脉冲的频率,把脉冲信号转换为我们需要的频率,这就形成了驱动芯片工作的时钟信号。这种信号的频率,我们也称作芯片的时钟频率。最后,时钟信号驱动着芯片工作,就像人体的脉搏一样,每一次脉冲到来,都让芯片的状态发生一次变化,用这种方法,最终存储器中的指令被一行行执行。
2、最早在 19 世纪初,德国著名数学家希尔伯特提出:这个世界可以建立一套完善的公理体系,由少数几个公理出发,推导出所有的定理和推论。后来哥德尔提出了不完备性定理,反驳了这种观点:即便在完善的公理体系中仍然可以找到不能被证明也不能被证伪的命题。
3、图灵发现如果一个问题是可计算的,那么它的解决方案就必须可以被具化成一条条的指令,也就是可以使用图灵机处理。因此,不能使用图灵机处理的问题,都是不可计算的问题。
4、不可计算问题,比如 “素数是不是有无穷多个?”尽管我们可以通过有限的步骤计算出下一个素数,但是,我们还是不能回答“素数是不是有无穷多个”这样的问题。因为要回答这样的问题,我们会不停地寻找下一个素数。如果素数是无穷的,那么我们的计算就是无穷无尽的,所以这样的问题不可计算。
5、不可计算问题,比如停机问题。我们无法实现用一个通用程序去判断另一个程序是否会停止。比如你用运行这段程序来检查一个程序是否会停止时,你会发现不能因为这个程序执行了 1 天,就判定它不会停止,也不能因为这个程序执行了 10 年,从而得出它不会停止的结论。
6、可计算问题,其计算开销又分为时间复杂度和空间复杂度。
7、在所有可以计算的问题中,像 O(N^1000) 的问题,虽然现在的计算能力不够,但是相信在遥远的未来,我们会拥有能力解决。这种我们有能力解决的问题,统称为多项式时间(Polynomial time)问题。
8、另外,还有一类问题复杂度本身也是指数形式的问题,比如 O(2^N)的问题。这类型的问题随着规模 N 上升,时间开销的增长速度和人类计算能力增长速度持平甚至更快。因此虽然这类问题可以计算,但是当 N 较大时,因为计算能力不足,最终结果依然无法被解决。我们记为 NP 问题。
9、图灵机在计算机科学方面有两个巨大的贡献:
10、图灵机的内部构造:
11、冯诺依曼模型遵循了图灵机的设计,并提出了用电子元件构造计算机,约定了用二进制进行计算和存储,并且将计算机结构分为以下五个部分:
12、冯·诺依曼体系结构的要点是:计算机的数制采用二进制。计算机应该按照程序顺序执行。它采用存储程序方式,指令和数据不加区别,混合存储在同一个存储器中。数据和程序在内存中是没有区别的,它们都是内存中的数据。当 EIP 指针指向哪,CPU 就加载哪段内存中的数据。如果是不正确的指令格式,CPU 就会发生错误中断。
13、冯诺依曼模型中 CPU 负责控制和计算。为了方便计算较大的数值,CPU 每次可以计算多个字节的数据。
14、64 位的 CPU 和 32 位比较有哪些优势?
15、CPU 离内存太远,需要一种离自己近的存储来存储将要被计算的数字,这种存储就是寄存器,寄存器就在 CPU 里,离控制单元和逻辑运算单元非常近,因此速度很快。
16、CPU 和内存以及其他设备之间,也需要通信,因此我们用一种特殊的设备进行控制,就是总线。总线分成 3 种:
17、CPU 的指令周期:
18、一段代码 a = 11 + 15
,它是怎么被 CPU 执行的呢?首先,这是一段高级语言,要被先编译成机器能识别的低级指令,这些指令和数据会被存储到专门的区域;然后 PC 会指向最开始的指令,依次执行:
19、CPU 指令从功能上来划分,大概可以分为以下 5 类:
20、CPU 是用石英晶体产生的脉冲转化为时钟信号驱动的,每一次时钟信号高低电平的转换就是一个周期,我们称为时钟周期。CPU 的主频,说的就是时钟信号的频率。比如一个 1GHz 的 CPU,说的是时钟信号的频率是 1G。
21、平时你编程做的事情,用机器指令也能做,所以从计算能力上来说它们是等价的,最终这种计算能力又和图灵机是等价的。如果一个语言的能力和图灵机等价,我们就说这个语言是图灵完备的语言。现在市面上的绝大多数语言都是图灵完备的语言,但也有一些不是,比如 HTML、正则表达式和 SQL 等。
22、我们把存储器分成几个级别:
半个 CPU 时钟周期
内完成,读写数量通常在几十到几百之间,每个寄存器可以用来存储一定字节(byte)的数据(32 位 CPU 存储 4 个字节;64 位 CPU 存储 8 个字节)。
2~4 个 CPU 时钟周期
。
10~20 个 CPU 周期
。
20~60 个 CPU 周期
。L3 缓存大小也是看型号的,比如 i9 CPU 有 512KB L1 Cache;有 2MB L2 Cache; 有16MB L3 Cache。
200~300 个 CPU 周期
之间。
23、当 CPU 需要内存中某个数据的时候,如果寄存器中有这个数据,我们可以直接使用;如果寄存器中没有这个数据,我们就要先查询 L1 缓存;L1 中没有,再查询 L2 缓存;L2 中没有再查询 L3 缓存;L3 中没有,再去内存中拿。
24、CPU 会把内存中的指令预读几十条或者上百条到读写速度较快的 L1- 缓存中,因为 L1- 缓存的读写速度只有 2~4 个时钟周期,是可以跟上 CPU 的执行速度的。这里又产生了另一个问题:如果数据和指令都存储在 L1- 缓存中,如果数据缓存覆盖了指令缓存,就会产生非常严重的后果。因此,L1- 缓存通常会分成两个区域,一个是指令区,一个是数据区。(L2/L3 不需要划分指令区和数据区,因为它们不需要协助处理指令预读的事情)
25、据统计,L1 缓存的命中率在 80% 左右,L1/L2/L3 加起来的命中率在 95% 左右。因此,CPU 缓存的设计还是相当合理的。只有 5% 的内存读取会穿透到内存,95% 都能读取到缓存。 这也是为什么程序语言逐渐取消了让程序员操作寄存器的语法,因为缓存保证了很高的命中率,多余的优化意义不大,而且很容易出错。
26、SSD、内存和 L1 Cache 相比速度差多少倍?因为内存比 SSD 快 10~1000 倍,L1 Cache 比内存快 100 倍左右。因此 L1 Cache 比 SSD 快了 1000~100000 倍。所以你有没有发现 SSD 的潜力很大,好的 SSD 已经接近内存了,只不过造价还略高。这个问题告诉我们,不同的存储器之间性能差距很大,构造存储器分级很有意义,分级的目的是要构造缓存体系。
27、假设有一个二维数组,总共有 1M 个条目,如果我们要遍历这个二维数组,应该逐行遍历还是逐列遍历?
【解析】 二维数组本质还是 1 维数组。只不过进行了脚标运算。比如说一个 N 行 M 列的数组,第 y 行第 x 列的坐标是: x + y*M。因此当行坐标增加时,内存空间是跳跃的。列坐标增加时,内存空间是连续的。
当 CPU 遍历二维数组的时候,会先从 CPU 缓存中取数据。关键因素在于现在的 CPU 设计不是每次读取一个内存地址,而是读取每次读取相邻的多个内存地址(内存速度 200~300 CPU 周期,预读提升效率)。所以这相当于机器和人的约定,如果程序员不按照这个约定,就无法利用预读的优势。
另一方面当读取内存地址跳跃较大的时候,会触发内存的页面置换。
28、怎么用非递归算法实现斐波那契数列?
public class Call {
public static void main(String[] args) {
int fib = fib(4);
System.out.println(fib);
int fib2 = fib2(4);
System.out.println(fib2);
}
static int fib(int n) {
if (n == 1 || n == 2) {
return n;
}
return fib(n - 1) + fib(n - 2);
}
private static int fib2(int n) {
if (n == 1 || n == 2) {
return n;
}
//初始化数据
int[] stack = new int[n];
int point = n - 3;
stack[n - 1] = 1;
stack[n - 2] = 2;
//数组模拟出栈入栈
while (point >= 0) {
stack[point] = stack[point + 1] + stack[point + 2];
point--;
}
return stack[0];
}
}