<audio id="audio" title="01 | 程序员为什么要关心代码性能？" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/cd/08/cdfe1a5ef37d32cf2d66403a97353808.mp3"></audio>

你好，我是庄振运。

感谢你加入这个专栏的学习，我也非常高兴能有机会和你一起探索这个领域。

我在计算机和互联网行业已经研究和工作近 20 年了，一直从事性能优化和容量管理相关的工作。从今天起，我就和你分享我这些年的经验和感悟。

提起计算机和互联网，多数人首先想到的职业是程序员。中国有多少程序员呢？很多人估计有600万左右。全球的人数就更多了，肯定超过2000万。

我虽然也在互联网领域，也做过几年写程序的工作，但是现在的工作，严格意义上不算程序员，而是性能工程师。不过我和很多程序员朋友一起工作过，也讨论过。谈到性能优化和系统容量管理的时候，一开始他们经常会问我一个问题，就是程序员为什么需要了解性能和容量这些东西？通俗点说，这个问题就是：我就是一介程序员，性能和系统容量听起来很重要，但与我何干？

这个问题问得很好。我可以和你肯定地说，程序员应该关心，也必须关心代码性能和系统容量。今天这一讲，我们先说说程序员为什么需要关心性能。

## 怎么定义“性能”和 “性能好”？

说起代码性能，首先我们需要弄清楚什么样的代码算是性能好？怎么样算是性能不好？

代码性能表现在很多方面和指标，比较常见的几个指标有吞吐量（Throughput）、服务延迟（Service latency）、扩展性（Scalability）和资源使用效率（Resource Utilization）。

- 吞吐量：单位时间处理请求的数量。
- 服务延迟：客户请求的处理时间。
- 扩展性：系统在高压的情况下能不能正常处理请求。
- 资源使用效率：单位请求处理所需要的资源量（比如CPU，内存等）。

必须说明的是，这几个指标之外，根据场景，还可以有其他性能指标，比如可靠性（Reliability）。可靠性注重的是在极端情况下能不能持续处理正常的服务请求。不过，我们这个专栏的讨论，主要围绕前四个更常见的目标。

性能好的代码，可以用四个字来概括：“多快好省”。

<img src="https://static001.geekbang.org/resource/image/95/e6/95a0a14caf49b53ee4859fe892596fe6.jpg" alt="">

看到这四个字，你可能想起了咱们国家当年制定的大跃进总路线，那就是：“鼓足干劲、力争上游、多快好省地建设社会主义”。没错，高性能代码的要求和这个“社会主义建设总路线”相当一致。这里的“多”，就是吞吐量大；“快”，就是服务延迟低；“好”，就是扩展性好；“省”，就是资源使用量低（也即是资源使用效率高）。

用这样的四个指标来衡量，那么性能不好的代码的表现就是：吞吐量小、延迟大、扩展性差、资源使用高（资源使用效率低）。

## 程序员为什么要关心代码性能？

对程序员来讲，写出的代码就是他的产品、他的生命线、他的形象和价值。代码性能不好，就是质量差，不靠谱。轻者影响程序员的声誉，重者影响他的工作。

对一个公司来讲，产品质量差，公司或许会倒闭。对程序员所在的互联网公司而言，如果公司的业务依赖于程序员写的代码，那么代码性能差，关键时刻掉链子，比如双十一促销的时候，公司的业务性能就会经常出问题，进而会影响公司的运营和营收，这可是天大的事情。

因此，如果一个程序员写出性能很差的代码，无异于耍流氓，并且相关程序员的工作也很难保住。

反过来讲，如果写出的代码性能很高，那代码的作者必定是我们大家认可的“靠谱”程序员，少不了“人见人爱”——客户喜欢，同事喜欢，领导也喜欢。

## 不同级别的程序员都需要关心性能

还有些朋友或许认为：代码性能是**某些人或者其他人**应该负责的；我就负责把代码写出来，优化的事，他们负责。这里的“某些人和其他人”可以是指软件测试人员、运维人员、技术专家，或者是性能工程师。

这种想法也是不对的。我下面就用几个案例来举例说明，代码性能是各个级别的程序员都应该关心和负责的。事实上，程序员从学校出来开始，一步步地在职业上攀升，每一步都应该和性能结伴而行。

我用一张图来表示一个成功程序员的技术职业轨迹（注意里面的职位和年限仅供参考）。

<img src="https://static001.geekbang.org/resource/image/0b/de/0b74f5861099d26e00ec76007913b6de.jpg" alt="">

学生刚刚从学校毕业，加入互联网公司，一般是入门级程序员。工作1到3年后，就成为普通的程序员。工作三五年后，可以算是资深程序员。工作6到10年后，可以成长为技术专家。10年以上，可能成为高级专家或者架构师。

### 举例1：刚入门的程序员

小李刚刚大学毕业，进入一个互联网公司。

领导给他的任务是写一个小模块，其中有一个需求是统计两个日期之间有几个正常工作日（也就是多少是周一到周五）。小李采取的是简单暴力法，就是用一个循环，循环的起始和截至日期就是给定的两个日期。在循环里面，对每一个日期判定一次，确定是工作日还是休息日，然后把工作日累加起来。

这样的代码显然性能不高，生产环境里面跑起来很快就会出问题。比如，如果两个日期差距很大，这个模块可能就需要很长时间才能处理完。

如果小李注重代码性能，他完全可以用更高效的方法，比如快速判定给定的两个日期间有多少个星期，然后乘以5，因为每个星期有5个工作日。然后，对头尾的星期进行特殊处理。这样的代码跑起来快多了。我可以想象，小李在优化完代码后，或许会吟诵两句“何当金络脑，快走踏清秋”来形容新代码的性能。

### 举例2：普通的程序员

小王做程序员2年了，在公司里已经可以独立负责一个模块了。有一天，他需要把一个二维整数数组进行重新赋值，于是，他写出了下面的二重循环：

<img src="https://static001.geekbang.org/resource/image/35/a9/3549fc43c8199499d31b2bf5432f23a9.jpg" alt="">

如果小王了解计算机内存和缓存的知识以及大小，他或许会写出下面的循环。虽然只有两个字母的差别，性能却提升了很多倍。

<img src="https://static001.geekbang.org/resource/image/20/d9/2055088aa3fc67f23d0274435c1e48d9.jpg" alt="">

原因是什么呢？

因为计算机通常都会有数量不大的缓存。数组在内存里是连续存放的，所以，如果访问数组元素的时候能够按照顺序来，缓存可以起到极大的加速作用。

小王一开始的二重循环，恰恰没有有效地使用缓存，反而对数组元素类似随机访问。第二个版本就改正了这个错误，优化了性能。

### 举例3：资深的程序员

小赵工作4年了，已经算是资深的C++程序员，负责一个程序的开发和设计。他的一个程序需要使用一个Map的数据结构。他开始使用的是STD库的标准实现：`unordered_map`。但是他发现，在数据量大的时候，键值的插入操作需要的时间很长。虽然做了各种代码优化，但性能总是不尽人意。

其实，如果他了解C++有些库有更高效的Map实现，比如`google::dense_hash_map`，他或许可以酌情采用，从而大幅度提升性能。

很多的测试结果显示，`google::dense_hash_map`的性能可以比`std::unordered_map`快好几倍。下图（图片来自[https://tessil.github.io/](https://tessil.github.io/) ）正是同一种测试环境下，两种实现的处理时间比较，我们可以清楚地看出性能的差距。

<img src="https://static001.geekbang.org/resource/image/12/4e/124cbafaf8f452f8399c2c85a1a6534e.png" alt="">

### 举例4：技术专家

小刘工作8年了，在公司里已经算是不大不小的技术专家了。

有一天，他看到一份项目计划，其中有一段引起了他的兴趣。这份计划是为了提高服务器的CPU使用效率，提出把应用程序的线程池增大，建议程序线程池的主线程数目应该和服务器的逻辑CPU的数目相等。当然，这里的逻辑CPU，就是我们通常说的虚拟内核数。

小刘这几年对硬件和操作系统钻研良多，他立刻指出，这样部署不妥，他建议降低主线程池大小到逻辑CPU的一半。技术讨论过程中，小刘给大家仔细讲解了原因，大家最后认可了他的建议，小刘也获得了大家的青睐。

小刘之所以这样建议，是因为他知道，服务器的逻辑CPU不是物理CPU。在超线程技术（Hyper Threading）的情况下，服务器的吞吐量不是严格按照逻辑CPU的使用率来提升的，因为两个逻辑CPU其实共享很多物理资源。

比如下面这张图，就表示了在一台有８个逻辑CPU的服务器上，如果部署超过4个线程，得到的性能提升非常有限，甚至可能会带来其他不好的后果。这里具体的提升率和效果，取决于线程和应用程序的特性。（图片来自[http://blog.stuffedcow.net](http://blog.stuffedcow.net)）<br>
<img src="https://static001.geekbang.org/resource/image/fd/45/fd4b4e1797eed83f9967daaf0ea15745.png" alt="">

### 举例５：高级专家（架构师）

老周是公司里的架构师和高级专家。他最近对公司的一个重要业务进行了性能优化，用很小的代码改动，就给公司节省了几百万美元的运营成本（这是我身边发生的一个真实案例，除了名字不一样）。

这个业务的性能瓶颈是CPU。因为业务量大，这个业务部署了1万台以上的服务器，占用了很大一部分数据中心的容量。

老周仔细研究了业务的逻辑，并且进行了性能测试和分析。他发现代码的执行过程卡在了CPU取指令的速度上：因为内存和缓存的物理特性，CPU花了很大一部分时间在等待指令获取，从而造成了CPU浪费。

他经过考虑，决定进行**指令级别的提前获取优化**。具体来讲，就是用GCC的`__builtin_prefetch`指令来预先提取关键指令，从而降低缓存的缺失比例，也就提高了CPU的使用效率。

下图是GCC关于这个指令的官方文档。

<img src="https://static001.geekbang.org/resource/image/95/36/95c984c03bc60ccb3f56867e0c4bcb36.png" alt="">

经过这样的优化，一台服务器可以处理比以前多50%的请求，从而节省了相应比例的服务器和容量。从公司成本角度来看，这一优化节省了3千台以上的服务器，价值几百万美元，老周被CEO开会表扬，也是自然的事情了。

有趣的是，整个的代码改进只需要几行代码的改动，真真切切是“一字万金”。

## 总结

重要的事情需要多说几遍：每个IT从业人员，尤其是程序员，都需要关心代码性能。

如果不了解性能的知识，也许能写出可运行但性能不好的代码。但一个真正对工作、对公司和对自己负责的程序员一定会发现，性能不好的代码无异于耍流氓，不经用还隐患无穷，万万要不得。

换句话说，对程序员来说，生活不仅是眼前的代码，还有效率和性能的优化。唐代诗人孟郊在考中进士后写了一首《登科后》，其中有两句：“春风得意马蹄疾，一日看尽长安花。”

我们谁不希望写出来的代码也运行飞快，自己能春风得意呢？！

## 思考题

无论你工作几年了，也无论是现在具体做什么工作，你能举出一两个，因为代码性能不好并导致严重后果的例子吗？是什么样的性能问题呢？

换个角度来说，如果写代码的程序员一开始就考虑到各种性能问题，并且提前在代码里面解决，写出的代码跑得飞快，而且很稳定。这样靠谱的程序员你会不会给他点赞？

欢迎留言和我分享你的观点，也欢迎你把今天的内容分享给身边的朋友，和他一起讨论。
