《深入理解计算机系统》是计算机领域的经典书籍。我花了一个周末的时间重读了这本书。有很多新的感悟。本文是我的读书笔记和思考。包括这本书讲了什么。为什么值得读。对我触动最大的几个点。以及对编程和计算机科学的一些思考。如果你是程序员。或者对计算机系统感兴趣。希望这篇文章能给你一些启发。

一、为什么重读这本书

先说说我为什么在这个周末重读这本书。

《深入理解计算机系统》(Computer Systems: A Programmer's Perspective)。简称CSAPP。是卡内基梅隆大学的教材。也是我大学时候的课本。

大学的时候学过这本书。但是那时候只是为了考试。很多东西没有真正理解。工作之后做了几年开发。越来越觉得计算机基础的重要性。很多工作中遇到的问题。追根溯源都是计算机基础的问题。

正好这个周末有空。我就把这本书翻出来重读了一遍。这一次读。和大学时候的感受完全不一样。很多以前不懂的东西。现在有了实际的编程经验之后。一下子就懂了。而且有很多新的感悟。

这篇文章就是记录我重读这本书的思考和感悟。

二、这本书讲了什么

先简单介绍一下这本书讲了什么。

CSAPP从程序员的视角来讲计算机系统。不是讲硬件怎么设计的。而是讲程序员写的代码。在计算机系统里是怎么运行的。怎么被编译。怎么被执行。怎么和操作系统交互。

全书分为几个部分:

第一部分:程序结构和执行

这部分讲C程序怎么被编译成机器码。机器码怎么在CPU上执行。包括:

  • 信息的表示和处理。整数和浮点数怎么表示。为什么会有精度问题。
  • 程序的机器级表示。C代码编译成汇编是什么样的。
  • 处理器体系结构。CPU怎么执行指令。流水线是怎么回事。
  • 优化程序性能。怎么写出高效的代码。
  • 存储器层次结构。缓存是怎么回事。为什么会有缓存命中和失效。

第二部分:在系统上运行程序

这部分讲程序怎么在操作系统上运行。包括:

  • 链接。程序怎么被链接成可执行文件。
  • 异常控制流。中断、异常、信号是怎么回事。
  • 虚拟内存。虚拟内存是怎么工作的。为什么每个进程都有独立的地址空间。

第三部分:程序间的交互和通信

这部分讲程序之间怎么交互。包括:

  • 系统级I/O。文件I/O是怎么工作的。
  • 网络编程。程序怎么通过网络通信。
  • 并发编程。多线程和并发是怎么回事。

整本书从最底层的信息表示。一直讲到最上层的网络和并发。把计算机系统的各个层面串了起来。让你对整个系统有一个完整的理解。

三、对我触动最大的几个点

重读这本书。有几个点对我触动最大。

触动1:整数和浮点数的表示

第一章讲信息的表示。整数用补码表示。浮点数用IEEE 754标准表示。

大学的时候学这些。觉得就是背公式。没什么意思。工作之后遇到了好几次因为整数溢出和浮点数精度导致的bug。才真正理解了这些表示方法的重要性。

比如整数溢出。在C语言里。int是32位的。最大能表示21亿左右。如果超过了就会溢出。变成负数。我就遇到过一次。一个计数器因为溢出变成了负数。导致整个系统逻辑错误。

还有浮点数精度。0.1 + 0.2不等于0.3。这个问题几乎每个程序员都遇到过。读了这本书才知道为什么。因为浮点数是用二进制表示的。0.1和0.2在二进制里都是无限循环小数。存的时候就有精度损失。加起来自然就不对了。

理解了这些底层的表示。写代码的时候就会注意。比如涉及到钱的时候用Decimal而不是float。大数字的时候用long long而不是int。这些都是从这本书里学到的。

触动2:汇编和机器码

第三章讲程序的机器级表示。C代码编译成汇编是什么样的。

大学的时候学汇编。觉得很枯燥。不知道学了有什么用。工作之后做性能优化。调试一些奇怪的bug。才发现懂汇编太重要了。

比如有一次。一段C代码的性能怎么都上不去。我把它编译成汇编看了一下。发现编译器做了一个很蠢的优化。把一个循环里的变量每次都从内存读。我改了一下C代码。让编译器能把变量放到寄存器里。性能立刻提升了好几倍。

还有调试core dump的时候。如果懂汇编。就能从汇编指令看出程序在哪一行崩溃的。当时的寄存器状态是什么。栈是什么样的。能快速定位问题。

这本书让我不再害怕汇编。能读懂基本的汇编指令。知道C代码编译之后大概是什么样的。这对性能优化和调试都非常有帮助。

触动3:存储器层次结构和缓存

第六章讲存储器层次结构。寄存器、缓存、内存、磁盘。速度越来越慢。容量越来越大。

这一章对我的触动最大。因为工作中很多性能问题。本质上都是缓存的问题。

比如为什么顺序访问数组比随机访问快。因为顺序访问能命中缓存。随机访问会导致缓存失效。为什么二维数组按行遍历比按列遍历快。因为数组是按行存储的。按行遍历缓存命中率高。

还有为什么有些代码看起来逻辑一样。性能差好几倍。就是因为缓存命中率不一样。

读了这一章。我写代码的时候会考虑数据的局部性。尽量让数据在内存中连续存放。尽量顺序访问。尽量复用缓存里的数据。这些小的优化。有时候能带来数量级的性能提升。

触动4:虚拟内存

第九章讲虚拟内存。这一章以前我一直没太看懂。这次重读终于懂了。

虚拟内存就是给每个进程一个独立的地址空间。每个进程都觉得自己拥有整个内存。实际上物理内存是共享的。由操作系统来映射。

虚拟内存的好处很多。比如进程之间不会互相干扰。一个进程崩溃了不会影响其他进程。比如可以用比物理内存大的地址空间。用磁盘来做交换。比如可以共享内存。多个进程映射同一块物理内存。

理解了虚拟内存。就理解了为什么程序里的指针都是虚拟地址。为什么每个进程的指针值可能一样但是指向不同的数据。为什么内存泄漏会导致问题。为什么会有段错误。

这些知识。对理解操作系统和调试内存相关的bug都非常有帮助。

触动5:并发编程

最后一章讲并发编程。多线程、互斥锁、信号量、死锁等。

并发编程是程序员最容易出bug的地方。 race condition、死锁、活锁。这些问题很难调试。因为它们不是每次都出现。

这本书从底层讲了并发的原理。为什么会有race condition。因为线程切换可能发生在任何指令之间。为什么需要内存屏障。因为CPU和编译器可能会重排指令。

理解了这些底层原理。写并发代码的时候就会更谨慎。知道哪里需要加锁。哪里需要用原子操作。怎么避免死锁。

四、这本书改变了我对编程的理解

重读这本书。不仅学到了知识。更重要的是改变了我对编程的理解。

1. 编程不是只写业务逻辑

以前我觉得编程就是写业务逻辑。实现功能就行。底层的东西不需要懂。

读了这本书才明白。不懂底层。写出来的代码可能有性能问题。可能有安全漏洞。可能有难以调试的bug。

真正优秀的程序员。不仅要懂业务逻辑。还要懂底层的计算机系统。知道自己写的代码在底层是怎么运行的。这样才能写出高效、可靠、安全的代码。

2. 抽象是有代价的

现代编程语言给了我们很多抽象。比如Java的虚拟机。Python的解释器。各种框架和库。这些抽象让我们编程更方便。不用关心底层的细节。

但是抽象是有代价的。抽象层会有性能损失。会有泄漏。会有一些你不理解的行为。如果不懂底层。遇到问题就不知道怎么解决。

这本书让我明白。抽象是好的。但是不能只停留在抽象层。要理解抽象下面是什么。这样才能用好抽象。而不是被抽象困住。

3. 基础比框架重要

现在技术更新很快。新的框架和库层出不穷。很多人热衷于学各种新框架。觉得会用最新的框架就是厉害。

但是读了这本书我发现。最有价值的知识是那些不变的基础。比如计算机系统的原理。数据结构和算法。操作系统的原理。网络协议。这些东西几十年都没变过。学会了之后。不管什么新框架新语言。都能快速上手。

而框架是会过时的。今天学的框架。明天可能就被淘汰了。但是基础永远不会过时。

4. 知其然还要知其所以然

以前用一些技术。知道怎么用。但是不知道为什么。比如为什么用Redis做缓存。为什么用消息队列。为什么数据库要建索引。

读了这本书之后。我会去思考这些技术背后的原理。为什么这样设计。解决了什么问题。有什么 trade-off。

知其然还要知其所以然。这样才能真正掌握技术。而不是只会用API。

五、我对计算机科学的一些思考

读这本书的过程中。我也对计算机科学有了一些思考。

1. 计算机科学的本质是管理复杂性

计算机系统非常复杂。从晶体管到CPU。从CPU到操作系统。从操作系统到应用程序。一层层的抽象。每一层都在管理复杂性。

计算机科学的本质。就是用抽象来管理复杂性。用分层来隔离关注点。用接口来解耦。这样我们才能在有限的脑力下。构建出如此复杂的系统。

理解了这一点。就理解了为什么编程要模块化。为什么要面向对象。为什么要设计模式。这些都是为了管理复杂性。

2. 没有银弹。只有trade-off

计算机系统里到处都是trade-off。时间和空间的trade-off。性能和可维护性的trade-off。一致性和可用性的trade-off。

没有完美的方案。只有在特定场景下最合适的方案。理解了这些trade-off。做技术选型的时候就不会盲目追求最新最好的技术。而是根据实际情况选择最合适的。

3. 简单是终极的复杂

计算机系统很复杂。但是最底层的原理其实很简单。比如CPU就是取指、译码、执行。循环往复。虚拟内存就是一个映射表。网络就是分组交换。

复杂的系统都是由简单的原理构建起来的。理解了简单的原理。就能理解复杂的系统。

编程也是一样。好的代码是简单的。清晰的。容易理解的。把复杂的问题拆解成简单的部分。用简单的方式实现。这才是真正的高手。

六、给想读这本书的人的建议

如果你也想读这本书。我有几个建议。

1. 要有C语言基础

这本书的例子都是用C语言写的。如果不懂C语言。读起来会很吃力。建议先学一下C语言的基础。再读这本书。

2. 不要只看。要动手

这本书有很多实验和练习题。不要只看书。要动手做。比如把C代码编译成汇编看看。写代码测试缓存的性能。写并发程序体验race condition。

动手做了之后。理解会深刻得多。

3. 第一遍可以略读

这本书很厚。内容很多。第一遍读的时候。可以略读。了解大概讲了什么。不用每个细节都搞懂。

然后在工作中遇到相关问题的时候。再回来细读对应的章节。这样带着问题读。效果更好。

4. 反复读

这本书值得反复读。每次读都会有新的收获。大学的时候读是一个感觉。工作几年之后读又是一个感觉。有了更多经验之后再读。还会有新的感悟。

我这次是重读。以后还会再读。每次读都能学到新东西。

5. 不要害怕

这本书看起来很厚。内容也比较深。很多人不敢读。其实不用怕。它写得很通俗易懂。例子也很多。只要有C语言基础。耐心读。一定能读懂。

七、写在最后

那个周末。我从早上读到晚上。读完了这本书的大部分章节。合上书的时候。感觉对计算机系统的理解又深了一层。

这本书不像很多技术书。教你怎么用某个框架或者某个工具。它教你的是原理。是那些不会过时的基础知识。这些知识。可能不会立刻让你涨工资。但是会在长期的职业生涯中。不断地给你回报。

作为一个程序员。我觉得每个人都应该读一读这本书。不管你用什么语言。做什么方向。理解计算机系统的底层原理。都会让你成为一个更好的程序员。

当然。读完这本书不是终点。而是起点。它打开了一扇门。让你看到计算机系统的广阔世界。后面还有很多东西要学。比如操作系统。计算机网络。编译原理。数据库原理。这些都是计算机科学的基础。值得我们花时间去学习。

最后用一句话结束本文:"基础不牢。地动山摇。"愿每一个程序员都能打好基础。在技术的道路上走得更远更稳。