铁锈编译器不慢,我们慢

2020-06-16 23:36:48

这可能是一个有点不受欢迎的观点,所以点击诱饵标题应该是合适的!从1.6版开始,我已经学习和使用Rust将近四年半了。对于熟悉语言、工具和生态系统的一些问题来说,这是一段很长的时间。但这是一个略有不同的故事,这是对编译器速度慢的常见批评的异议。在这次讨论中,我将争辩说,这些说法充其量只是误导。有大量证据表明,与其他语言的编译器相比,Rustc速度较慢;Go可以在五分之一的时间内编译具有类似复杂性的应用程序,Clang或GCC可以为C编写的应用程序做同样的事情。可以接受的是,可以证明RUST宏和类型系统比通常用GO或C编写的要复杂得多,这需要RUST编译器承担更大的负担。虽然这种情绪有一定的道理,但我相信还有更多的原因。我认为,与其说这是一个算法问题,不如说是一个人类问题。

我经常光顾URLO,时不时地帮助回答问题。我主要用它来吸收信息,学习这种语言和围绕它的更大的生态系统。我有时也公开反对肥胖,我想通过这篇文章继续提高人们的意识。

首先,让我们简短地绕道来考察一下历史背景下的软件膨胀。我首先想到的是沃斯定律,它实质上说,软件[开发者]通过填补空白来抵消硬件性能上的所有进步。请阅读Niklaus Wirth的论文“精益软件的恳求”,这是非常值得的。这绝不是一个新的或独特的观察结果。继续探索这条臃肿的道路,任何进一步的探索都会对读者造成伤害,因为它偏离了我的主要观点。但这是另一个基本问题的例子,那就是人类是神秘的,人类的行为甚至更不清楚。

大多数开发人员在编写软件时都会权衡时间。对于开发人员来说,唯一真正的目标是解决特定的问题。比方说玛丽在做一个游戏,她没有无限的时间来完善这个游戏,但她可以在合理的时间框架内完成一些合理的事情。玛丽知道她剩下的心跳是有限的,这是激励生活本身的货币,所以她会明智地利用这些心跳,忽略细节,专注于手头的问题。这不是一件坏事!一款游戏制作完成,她就可以继续创作她的下一个大热门了。

但它对产品的内部结构说了些什么呢?它做了什么浪费的事吗?能不能把它做得更有效率一点?使用更少的能源(特别是电池电源)?当然可以!玛丽没有时间把它做得尽善尽美。这就是我要讲的故事。

我之所以描述这个假想的游戏开发者和她的软件,是因为我自己已经做过很多次了。我写代码,当我对它的工作方式感到满意时,我就会转移到其他事情上,在这个过程中留下大量的面包屑。任何人看到这种情况都会惊呼,哎呀,真是一团糟!谁会这么做?!";

我当前在生产环境中运行的大量代码是Python。每天有数以百万计的人依赖它,而且它的运营费用非常昂贵。学到的教训是,尽管语言的选择可能让我快速地将一个项目从构思发展到生产,但长期的赤字根本不值得早期微不足道的收获。如果多个项目用客观上很慢的语言编写,使用客观上不好的动态类型转换,您会如何处理这些项目?当然,您可以在JIT编译器下运行它们!如果它必须客观上很慢,我们至少可以通过一点积极的编译器优化来弥补一些严重的效率损失。

这终于引出了我的观点。软件开发人员并不完美,在如何利用他们的心跳方面做出明智的决定,他们通过利用前人编写的软件来构建软件。在这里选择一种语言和编译器,在那里添加少量的库和依赖项,然后将它们混合在一起,您就得到了一个很好的解决问题的方法。但当然,在你制造至少一个,也许是许多其他问题之前,你是不会这样做的。重要的是,这些决定不是出于懒惰,而是出于必要。

对于那些在优化代码上投入了大量精力的开发人员,再怎么赞扬也不为过。严格的基准测试和性能分析,密切关注CPU周期和堆块的去向,并控制过高的代码路径。我们在很多地方都可以看到这一点,从regex机箱到Actix框架,一直到编译器本身。如此多的积极和令人印象深刻的壮举,总是鼓舞人心的。

所以,如果编译器实际上已经相当好了,并且有一个团队致力于提高它的速度,那么为什么对于这么多Rust开发人员来说,它看起来这么慢呢?我怀疑这个概念主要是由开发人员自己强加的,他们很快就利用了引入依赖项的轻而易举的优势。几乎没有人抵制依赖于参数解析机箱代码--这就是事情可能会引起一些争议的地方--它及其所有依赖项在可执行文件中占据了400 KiB(在剥离调试符号之后)。您的二进制大小的400 KiB将用于参数解析吗?哦,好吧,我可以算了,400基布现在不卖了。

但是编译时间呢?那要多少钱?如果你看重你的心跳,你可能不喜欢clap花了这么长时间编译。在我的笔记本电脑上,编译20subCommands示例花费了1分14秒。而且这个例子根本没有任何业务逻辑。它完全只是一个命令行解析器。但是,编译时间在实践中是否相当可观呢?当然,clap将在实际应用程序中使用,在该应用程序中,参数解析仅在程序启动时的最初几十毫秒内完成。大多数应用程序逻辑的成本会高得多,对吗?对吗?

我的一个副业是用一块便宜的FPGA板学习数字逻辑。测试数字逻辑的一个方便工具是波形查看器,几周前我找到了一个非常简单的工具:dwfv。它在终端中运行,呈现TUI,具有编辑支持和Vim键绑定。干净利落!我注意到的第一件事是编译发布版本几乎花了两分半钟。这不是一个理想的第一印象,但事实就是如此。

当我玩它的时候,我意识到这可能是一个我会放在后面口袋里的工具。所以这是值得帮助的,为开放源码等做贡献。我的第一个公关是(惊喜!)。用口香糖代替鼓掌。现在,我过去在口香糖上取得了一些成功,所以我知道我要做的是什么。并不总是能够提供与CLAP提供的CLI完全相同的CLI,这也不例外。位置文件名必须更改为可选参数,以便子命令语法可以使用自由参数。在我看来,这是一个非常合理的权衡,可以将编译时间减少30秒。额外的好处;可执行文件的大小也减少了500 KiB。

好的,我很抱歉找了克拉普的麻烦。我知道它比更简单的参数解析板条箱有它的好处。如果我有自己的选择,我会直接使用环境,而不是进行参数解析。实话实说,论据解析更人性化,而且12个因素的应用程序指南大多是针对微服务的。另外,拍手确实有很强的表现力,我不能打折扣。

我替换的下一个东西是解析器生成器,用Nom替换了lalrop。在试图决定如何为我计划进行RIIR的汇编项目编写解析器时,我对这两个问题都进行了简要的研究。我为那个项目选择了NOM,总的来说,这是一次非常好的经历。事实证明,这对dwfv来说也是一个很好的改变,它保留了完全的语法兼容性,并将构建时间减少到了大约40秒。这次可执行文件的大小仅减少了32 KiB。

最后,我能够用一些简单的基于迭代器的解析器替换regex,将构建时间减少到26秒,并惊人地将可执行文件的大小减少了1.14MB。前后的整体情况如下所示:

这代表了编译时间的显著改进,最新的PR比我第一次发现该应用程序时快了五倍多。更少的依赖项和更小的可执行文件大小也带来了巨大的好处。所有这些都不会丢失任何功能,并且只需对CLI参数选项进行非常微小的更改。

从很多角度来看,这都是一个非常有趣的项目,我很高兴有机会分享我的发现。我在这里试图说明的是,有些人可能认为是慢编译器的很多东西都可以追溯到开发人员没有勤奋地进行依赖项管理,或者比编译时更喜欢运行时性能,甚至比编译时更喜欢开发速度(无论这意味着什么!),所以我在这里试图说明的是,有些人可能认为编译器速度很慢,这可以追溯到开发人员不勤奋地进行依赖项管理,或者比编译时更喜欢运行时性能,甚至比编译时更喜欢开发人员速度(无论这意味着什么!)。还记得我说过的关于Python及其允许开发人员快速构建东西的能力吗?有时正则表达式会有这种感觉。隐性成本总是让人大吃一惊。

然而,情况并不总是如此。有时您必须牺牲编译速度来换取运行时性能。这些应该是例外,而不是规则。大多数Rust开发人员只需稍微推动一下,编译更少的代码就可以减少编译时间。

此分析使用的工具是货树树(包含在货树中,从铁锈1.44开始!)。和货物膨胀。寻找替代板条箱并不总是容易的,我没有任何一般性的建议,任何人希望尝试任何类似的工作。如果您知道您的选择并且只需要权衡它们,那么lib.rs上显示的有关板条箱的依赖关系统计信息可以帮助您提供比较的基础。