The Kollected Kode Vicious

Kode Vicious - @kode_vicious

  下载本文的PDF版本 PDF

你想干什么?

一次缓存未命中比许多指令的开销更大。

尊敬的KV:

我一直在阅读一位开发人员提交的一些拉取请求,这位开发人员最近一直在处理我也需要时不时查看的代码。他提交的代码充满了奇怪的更改,他声称这些更改是优化。他没有简单地返回诸如 1、0 或 -1 之类的值来表示错误情况,而是分配一个变量,然后递增或递减它,然后跳转到返回语句。我没有费心检查这是否会节省指令,因为我从代码的基准测试中知道,这些指令并不是函数花费大部分时间的地方。他认为我们不执行任何指令都可以节省时间,而我的观点是他的代码令人困惑且难以阅读。如果他能证明速度提高了百分之五或百分之十,那可能值得考虑,但他还没有能够在任何类型的测试中证明这一点。我阻止了他的几次提交,但我更希望有一个可用的论据来反对这种类型的优化。

别开玩笑了

 

尊敬的别开玩笑:

节省指令——他真是太 1990 年代了。当人们关注细节时总是好的,但有时他们只是没有关注正确的细节。虽然 KV 永远不会鼓励开发人员浪费指令,但考虑到现代软件的状态,似乎已经有人这样做了。 KV 会像您一样,站在清晰易懂的一边,而不是节省一些指令。

似乎无论语言和编译器取得什么进步,总有一些程序员认为他们比自己的工具更聪明,有时他们是对的,但大多数时候他们不是。阅读汇编器的输出并计算指令对于某些人来说可能令人满意,但要证明代码模糊不清,最好有更多的证据。我只能想象一个模块充满了这样的代码:


if (some condition) {
   retval++;
   goto out
} else {
   retval--;
   goto out
}
...
out
   return(retval)

而且,老实说,我真的不想这样。现代编译器,甚至不是那么现代的编译器,都使用了程序员过去不得不手动使用的所有技巧——内联、循环展开和许多其他技巧——但仍然有一些程序员坚持与自己的工具作斗争。

当在代码清晰度和微小优化之间做出选择时,几乎总是必须选择清晰度。缺乏清晰度是错误的根源,拥有快速但错误的代码是没有好处的。首先代码必须正确,然后代码必须执行;这是任何理智的程序员都必须遵守的优先级。至于不理智的程序员,最好还是避而远之。最终,他们会搬到一个中美洲国家,在浴缸里混合自己的毒品,并声称他们可以解锁 iPhone。

建议代码的另一个重大问题是它违反了常见的编码习惯用法。所有语言,包括计算机语言,都有习惯用法,正如 Brian W. Kernighan 和 Rob Pike 在The Practice of Programming(Addison-Wesley Professional,1999 年)中详细指出的那样,我在十多年前就向读者推荐过这本书。我们先不去想这本书仍然具有现实意义,以及我每十年都在重复自己。无论您对计算机语言有何看法,您都应该尊重其习惯用法,原因与人们必须了解人类语言中的习惯用法相同——它们促进了沟通,这是所有语言(无论是编程语言还是其他语言)的真正目的。语言习惯用法是在语言使用过程中自然形成的。大多数 C 程序员,虽然并非所有程序员都如此,都会以这种方式编写无限循环:


for (;;) {
}

或如下所示:


while (1) {
}

并在内部的某个地方使用适当的 break 语句来处理在发生错误时退出循环的情况。实际上,查看The Practice of Programming这本书,我发现这一点在早期(在第 1.3 节中)就提到了。对于返回的情况,您提到通常使用诸如 1、0 或 -1 之类的值返回,除非返回编码的内容不仅仅是 true、false 或 error。分配堆栈变量并递增或递减并添加 goto 不是我在任何地方的代码中见过的习惯用法——既然您正在处理这个案例,我希望我永远不必看到它。

从这个具体的代码片段转向何时允许将某些形式的代码技巧混入其中的抽象问题,实际上取决于几个因素,但主要是取决于通过稍微调整代码以更紧密地匹配底层机器可以获得多少加速。毕竟,您在低级代码(尤其是 C 及其臃肿的表亲 C++)中看到的大多数手动优化之所以存在,是因为编译器无法识别将程序员想要执行的操作映射到底层机器实际工作方式的好方法。暂且不提大多数软件工程师实际上并不了解计算机的工作原理,也暂且不提他们被教导的大部分内容——如果他们被教导过——关于计算机的知识,都来自 20 世纪 70 年代和 80 年代,当时超标量处理器和深度流水线还是 CPU 的标准功能,但仍然有可能通过对编译器使用技巧来加快速度。

这些技巧本身对于本次对话并不那么重要;重要的是了解如何衡量它们对软件的影响。这是一项困难且复杂的任务。事实证明,像您的同事所做的那样简单地计算指令并不能告诉您底层代码的运行时。在现代 CPU 中,最宝贵的资源不再是指令,除非在极少数计算密集型工作负载中。现代系统不会被指令淹没;它们会被数据淹没。处理数据的缓存效应远远超过额外一两条或十条指令的开销。一次缓存未命中会带来 32 纳秒的惩罚,或者在 3 GHz 处理器上大约 100 个周期。根据丹麦技术大学的 Agner Fog 的说法(http://www.agner.org/optimize/instruction_tables.pdf),一个简单的 MOV 指令(将单个常数放入 CPU 的寄存器中)需要四分之一周期。

有人如此深入地记录了大量处理器的信息,这令人震惊,那些对优化性能感兴趣的人可能会沉浸在该网站中(http://www.agner.org)。

问题的关键在于,一次缓存未命中比许多指令的开销更大,因此优化掉一些指令并不能真正为您的软件赢得任何速度测试。要赢得速度测试,您必须测量系统,查看瓶颈在哪里,并在可能的情况下清除它们。但是,这是另一个话题了。

KV

Kode Vicious,凡人称之为 George V. Neville-Neil,为乐趣和利润从事网络和操作系统代码工作。他还教授有关编程相关主题的课程。他感兴趣的领域是代码探勘、操作系统和重写您的糟糕代码(好吧,也许不是最后一个)。他获得了马萨诸塞州波士顿东北大学的计算机科学学士学位,并且是 、Usenix 协会和 IEEE 的成员。 Neville-Neil 与 Marshall Kirk McKusick 和 Robert N. M. Watson 合着了The Design and Implementation of the FreeBSD Operating System(第二版)。他是一位狂热的自行车手和旅行家,目前居住在纽约市。

版权所有 © 2016,所有者/作者所有。出版权已授权给 。

acmqueue

最初发表于 Queue vol. 14, no. 2
数字图书馆 中评论本文





更多相关文章

Catherine Hayes, David Malone - 质疑评估非加密哈希函数的标准
尽管加密和非加密哈希函数无处不在,但在它们的设计方式上似乎存在差距。受各种安全需求驱动,加密哈希存在许多标准,但在非加密方面,存在一定程度的民间传说,尽管哈希函数历史悠久,但尚未得到充分探索。虽然针对真实世界数据集的均匀分布很有意义,但在面对具有特定模式的数据集时,这可能是一个挑战。


Nicole Forsgren, Eirini Kalliamvakou, Abi Noda, Michaela Greiler, Brian Houck, Margaret-Anne Storey - DevEx 在行动
随着领导者寻求在财政紧缩和人工智能等变革性技术的背景下优化软件交付,DevEx(开发者体验)在许多软件组织中越来越受到关注。技术领导者凭直觉接受良好的开发者体验能够实现更有效的软件交付和开发者幸福感。然而,在许多组织中,改进 DevEx 的拟议倡议和投资难以获得支持,因为业务利益相关者质疑改进的价值主张。


João Varajão, António Trigo, Miguel Almeida - 低代码开发生产力
本文旨在通过展示使用基于代码、低代码和极端低代码技术进行的实验室实验结果,研究生产力差异,从而为该主题提供新的见解。低代码技术已清楚地显示出更高的生产力水平,为低代码在短期/中期内主导软件开发主流提供了强有力的论据。本文报告了程序和协议、结果、局限性和未来研究的机会。


Ivar Jacobson, Alistair Cockburn - 用例至关重要
虽然软件行业是一个快节奏且令人兴奋的世界,其中不断开发新的工具、技术和技术来服务于商业和社会,但它也很健忘。在其快速前进的过程中,它容易受到时尚潮流的影响,并且可能会忘记或忽略已证实的解决其面临的一些永恒问题的方法。用例于 1986 年首次引入,后来普及,是这些经过验证的解决方案之一。





© 保留所有权利。

© . All rights reserved.