1.1 信息就是位 + 上下文
位即比特,其值为 0 或 1。上下文即解释比特序列的方法。
系统中的所有信息 —— 包括磁盘文件、内存中的程序、内存中存放的用户数据以及网络上传送的数据,都是由一串比特序列表示的。相同的比特序列在不同的场景中有着不同的含义,比如同样一串序列可以表示一个正数、浮点数、字符或者机器指令。
1.2 程序被其他程序翻译成不同的格式
一个用 C 语言描述的文本文件(hello.c),必须经过四个步骤才能变为一个可执行文件(hello):
- 预处理阶段(产出 hello.i 文件):预处理器根据以字符 ‘#’ 开头的命令修改 C 代码,比如展开头文件,处理宏(#define, #ifndef 等)。
- 对应命令:
gcc -o hello.i -E hello.c;
- 对应命令:
- 编译阶段(产出 hello.s 文件):将 C代码编译为汇编代码。
- 对应命令:
gcc -o hello.s -S hello.i;
- 对应命令:
- 汇编阶段(产出 hello.o 文件):将汇编代码翻译为机器语言指令,并打包为一个可重定位目标程序(relocatable object pragram)格式的文件。
- 对应命令:
gcc -o hello.o -c hello.s
- 对应命令:
- 链接阶段(产出 hello 文件):将一个或多个可重定位目标程序按照某种方式链接为可执行目标文件。
- 对应命令:
gcc -o hello hello.o
- 对应命令:
1.3 了解编译系统工作是大有益处的
这一小节主要是提出问题。这里先记下来,后面整理答案。
性能优化相关的:
- switch VS 一连串 if-else,谁更快?
- 函数调用的开销有多大?
- while-loop VS for-loop ,谁更快?
- 指针引用 VS 数组索引,谁更快?
- 将循环求和的值存入本地变量 VS 存入通过引用传递过来的参数,谁更快?
- 重新排列算术表达式的括号就能让函数变快?
链接相关:
- 链接器报告无法解析一个引用是什么意思?
- 静态变量和全局变量的区别?
- 不同的 C文件中分别定义了命中相同的全局变量会发生什么?
- 静态库和动态库的区别?
- 编译时在命令行上排列库的顺序有什么影响?
- 为什么有些链接错误直到运行时才会出现?
安全相关:
- 堆栈原理和缓冲区溢出错误?
1.4 处理器读并解释储存在内存中的指令
本小节先简单介绍了计算机的各主要模块:
- 总线:用于在各个组件之间传递数据。通常被设计成传送定长的字节块,也就是字(word),通常是 4 字节或 8 字节。
- I/O 设备:用于人和机器交互的设备,比如键盘、鼠标、显示器、磁盘等。
- 主存:临时存储设备,在处理器执行程序时,用来存放程序及相关数据。从物理上讲,主存是一组「动态随机存取存储器(DRAM)」芯片组成。从逻辑上讲,主存是一个线性的字节数组。
- 处理器:用来执行存储在主存中的指令的组件。主要包含三部分 PC、ALU、寄存器。大体流程是CPU通过PC从主存中读取指令并执行,在执行时可能会控制ALU进行运算、读写寄存器或主存、更新 PC。
- PC:程序计数器(program counter)。用于记录下一条指令在主存的地址。
- ALU:算术/逻辑单元。用来执行运算。
- 寄存器:用于存储命令所需的参数或者执行结果。
接着介绍了执行文件时数据的宏观流程:
- 从键盘读取命令"./hello",这里要注意数据经过CPU才进入到内存,说明普通的I/O设备不能和主存直接通信。
- 键入完命令后,计算机开始从磁盘加载文件到内存。这里主存和磁盘可直接通信。这种技术成为 DMA —— 直接存储器存取。
- 加载完后,开始执行命令,CPU根据执行将主存中的字符串 “hello, world\n” 送往显示器。这里也要注意显示器和内存也不能直接通信,也需绕路CPU。
1.5 高速缓存至关重要
通过上一节内容不难发现,在执行一个文件时,总是在不停地复制数据。这些复制就是额外的开销,拖慢了CPU的计算速率。
为了降低 I/O 对 CPU 计算速率的影响,引入了高速缓存存储器,它距离CPU更近,访问更快,但容量更小。
存储器的容器和读写速率总是负相关的:
- 容量:磁盘 > 主存 > 高速缓存
- 速率:磁盘 < 主存 < 高速缓存
在编写程序时,应该妥善使用高速缓存。
1.6 存储设备形成层次结构
每一层都可视作下一层的高速缓存。
1.7 操作系统管理硬件
应用程序无法直接和硬件交互,它们必须依靠操作系统提供的服务。操作系统主要有两个功能:
- 权限控制:防止硬件被预期之外的程序使用。
- 一致抽象:向应用程序提供简单一致的抽象,以屏蔽硬件设备之间的差异。
操作系统提供了几种重要的抽象:
- 进程:是操作系统对一个正在运行的程序的抽象,使得执行中的应用程序像是独占了计算机的所有硬件。
- 虚拟内存:操作系统通过虚拟地址空间的概念,使得每个进程在运行时都像是独占了整个主存储器。虚拟地址空间按照功能划分为几块:
- 程序代码和数据。
- 堆。
- 共享库。
- 栈。
- 内核虚拟内存。
- 文件:操作系统通过文件的概念,向应用程序屏蔽了各种I/O设备之间的差异。无论是何种I/O设备,应用程序都是用相同的读写函数来和它们交互。
1.8 系统之间利用网络通信
得益于计算机网络技术的进步,计算机不再是一个个孤立的系统,计算机可以像读写本地文件一样和其他计算机进行通信。
1.9 重要主题
1.9.1 Amdahl 定律
该定律的主要观点——要想显著加速整个系统,必须提升全系统中相当大的部分的速度。加速比可是表示为
S
=
1
(
1
−
a
)
+
a
k
S = \frac{1}{(1-a) + \frac{a}{k}}
S=(1−a)+ka1
其中, a a a 为优化部分的执行时间在总体时间中的占比, k k k 为优化部分的提升比例,可表示为 k = 优 化 前 的 执 行 时 间 优 化 后 的 执 行 时 间 k=\frac{优化前的执行时间}{优化后的执行时间} k=优化后的执行时间优化前的执行时间
1.9.2 并发(concurrency)和并行(parallelism)
- 硬件级别的并行
- 多核处理器是指将多个CPU集成到一个集成电路芯片上。
- 超线程 (hyperthreading) 是对单个CPU 的优化,它运行单个CPU并行的执行两个线程,但任意时刻只有一个线程在执行。它可以在一个指令周期内完成执行线程的切换。
- 指令级并行
- 引入流水线的概念,可以使得CPU可以同时处理多条指令。
- 单指令、多数据并行
- SIMD(single instruction, multi data?),比如一条指令可以同时处理八个浮点数。
1.9.3 计算机系统中抽象的重要性
- 指令集架构:提供了对实际处理器硬件的抽象,使得机器代码像是运行在一个一次只执行一条指令的处理器上,但处理器实际上在同时处理多条指令。
- 操作系统的抽象:
- 文件:对 I/O 设备的抽象。
- 虚拟内存:对程序存储器的抽象。
- 进程:对一个正在运行的程序的抽象。
- 虚拟机:提供了对整个计算机的抽象,包括操作系统、处理器和程序。使得一台计算机上可以同时运行多个操作系统。
练习题 1.1
A
T o l d = 25 h T_{old} = 25h Told=25h
T n e w = 1000 k m 100 k m m / h + 1500 k m 150 k m / h = 20 h T_{new} = \frac{1000km}{100kmm/h} + \frac{1500km}{150km/h} = 20h Tnew=100kmm/h1000km+150km/h1500km=20h
S = T o l d T n e w = 1.25 S = \frac{T_{old}}{T_{new}} = 1.25 S=TnewTold=1.25
B
不妨先设通过蒙大拿州的平均速度为 X,则可得方程
25
h
1000
k
m
100
k
m
m
/
h
+
1500
k
m
X
k
m
/
h
=
1.67
\frac{25h}{\frac{1000km}{100kmm/h} + \frac{1500km}{X km/h}} = 1.67
100kmm/h1000km+Xkm/h1500km25h=1.67
解得 X ≈ 301.807 k m / h X≈301.807 km/h X≈301.807km/h
练习题 1.2
根据公式可得
2
=
1
1
−
0.8
+
0.8
k
2 = \frac{1}{1-0.8 + \frac{0.8}{k}}
2=1−0.8+k0.81
解得 $ k = 2.67$