Golang 之于 DevOps 开发的利与弊:速度 vs.缺少泛型

首发于:https://studygolang.com/articles/12614

这是我们关于 DevOps 开发流程之中使用 Golang 之利与弊的六部曲系列。在这篇文章里,我们会讨论 Golang 的运行时/编译/维护的速度(好的方面);以及缺少泛型(缺点)。

在阅读这篇文章之前,请确保你已经阅读了上一篇关于“接口实现以及公有/私有命名方式”,或者订阅我们的博客更新提醒来获取此六部曲后续文章的音讯。(我们会隔周更新,但是鉴于我们正在忙着发布我们的 beta 平台我们的进度确实有点延后。)

Golang 之利: 速度

我基于以往的经验:在我们在写 Blue Matador agent 之时在乎什么,把速度这个优势分解成三个不同的类别。(1)运行时,(2)编译过程,(3)维护流程。确切数据的量会随着分类的递进而减少 - 你会明白原因的.

运行时速度

因为我们的 agent 是跑在客户服务器之上,运行时速度是处于第一优先级的,除此之外还有安全和自动更新。很不幸的是,我们直到被我们的 Python 版本的 agent 狠狠坑了一次之后,才明白什么方面才是第一优先级的。现在回顾,我很高兴我们远离了 Python 。

鉴于这是一篇讨论 Golang 速度的优点的博文,而不是探讨我们为什么从 Python 迁移到 Golang ,我会去除两种持续慢于 Golang 的语言: Python3 和 Node.js。在把干扰项去掉之后,你会看见 Golang 和其他顶级竞争者的结果:

图中显而易见的是,C++ 是最快的语言 - 完全不出意料。实际上,即使 Java 都把 Golang 击败了,但这出于两个很好的理由:(1)Java 虚拟机(JVM)从1995年就开始开发了,比 Golang 多了 17 年。以及(2) 比起Golang ,JVM 在测试中花了 2 到 30 倍的内存使用量 - 这意味着总体上 Golang 的垃圾回收员比JVM的工作得更勤劳。

编译速度

我们之前的 agent 是用 Python 写的(并不是一个编译语言),但这并不意味着我们不熟悉 Go 的编译时优势。

从 Golang 的一开始,较短的编译时间一直是一个严苛的要求。 Go 是 Google 的 Ken Thompson 和 Rob Pike 创造的。Google,有超过20亿行代码,毫无疑问对于编译所会浪费的时间极其严肃。

在这个帖子里,有一些非常棒的信息,我也会在这总结。(我强力推荐你阅读这个帖子,因为它既精炼又细致。)

首先看这幅关于一个相对大的代码库的编译时间的图。代码细节可以在之前提到的链接中找到。注意看,Golang 在一众竞争者中的表现多棒,除了比不过 Pascal 这个被设计成只为了跑一遍代码的语言 - 基本上编译时间会确保是 O(n)。考虑到 Golang 的语言规范依旧可用(不像 Pascal ),我会把 Go 微小的劣势视为 Go 的胜利。

我不理解为什么这次测试所有语言的编译速度都如此快,但我明白至少一部分的原因是因为依赖管理系统。当一个文件被编译时,编译器只看这个文件列出的直接依赖。它并不需要去递归加载所有依赖文件的依赖。

Blue Matador Agent 有 29 个包,116824 行代码。它还有 3 个目标操作系统,2 个目标架构,以及 3 个模块。所有的这些都能在 10 秒内完成并行编译(在一个 8 核的开发笔记本上)。好消息是我们几乎不用花时间在编译上。但坏消息是我们就没时间像这幅 XKCD 漫画一样击剑了。

维护速度

现在,转到浑水区。在我开始细说之前,让我先澄清几点:

我们的 Golang 代码库是刻意保持在一个较小的体量

可能因为体量小,我们只有 2 个被报告的 bug

目前我们有 3 个全职开发者,其中只有两个碰过 agent 的代码

因此,当我说维护 Golang 代码是非常简单并且不费时间的时候,请理解我们是多么缺少依据和经验。关于这一点我可能完完全全是错的。如果以后程序员们在诅咒着我的名字,我确信我会找到这个问题的答案。

尽管如此,以下是我认为 Go 的代码维护效率更好的原因:

没有内存管理。在 C/C++ 有很多代码是只为了内存管理而存在。你为了管理内存不得不做了一堆古怪的事。这在 Go 里面,得益于垃圾回收机制,完全不是问题。

稳定可靠的核心库。除了错误返回系统之外,我们的代码十分精简。这是因为Go的核心库有我们需要的所有东西; 从 HTTP 请求和 JSON 编码/解码到进程 fork 和 IPC 管道。

没有泛型。对,我即将要说缺少泛型是这个语言的一个严重缺点。但是如果没有泛型的话,变量类型全都会是明确且已知的。当你读一个类文件的时候,你会明确知道预期结果。这让更改代码更容易并且更快。

关于速度的额外阅读资料

备注:我在我写完这个帖子之后才发现这个。我推荐你阅读这篇文章,如果你对于 Go 的速度想知道更多:5 件使得Go很快的事

Golang 之弊:缺乏泛型

Golang 没有泛型。我恨这点。毫无疑问这也是优点,但我真的很恨这点.

想象一下,在 C++ 下面工作却没有标准模板库(STL),并且还不允许你自己写一份的情景。想象一下,你决定抛弃 Array 和 Hash map,转而创造自己的数据结构,但却发现你并不能在其他任何地方复用的心痛。想象一下,你拥有一个类型语言的所有优点,却不能在一个强类型的类里复用。这让我想起了以前当我创建一些自定义网站的时候,我有写代码的一切能力,但我所能做的就只是复制粘贴 functions.php 到我客户的服务器上。

当到了即将要写一堆丑陋的代码去绕过不能用泛型的限制之际,我做了(或者考虑过)去做以下这些事情使得 Go 能像我想象中一样去运行。

Empty Interface

这个解决方案需要使用 empty interface 。类型为 interface{} 的变量可以是任何东西。这对于在规定变量类型有很好的灵活度。但同时,有各种各样的原因会让这个变得很糟糕。

如果你使用了一个 empty interface 作为数值的数据结构的话,那每次你需要和这个数据结构打交道的时候,你都要写一堆废话去做类型申明,而且这还不会返回一个错误值。忽略潜在的错误会是一条直通灾难的捷径,因为一个简单的重构就破坏了类型安全。

如果你在函数的传入参数中使用了一个 empty interface,你很有可能会在函数中有一个用于类型断言的 switch 语句。在一个不同的变量类型下复用这样的函数就意味着在 switch 语句中多加一行。我宁愿去做一个脑白质切断术,因为这真的不是“复用”,并且如果有越多开发者,这越行不通。

复制/粘贴

我们的免费 Watchdog 模块检测各类系统指标 - CPU,负荷,磁盘,网络等等。我写的第一个指标是 CPU 的各类占用率。当我写这些的时候,我赋予它们 float32 类型。然后,下一个指标是当前运行的进程数,这明显是一个属于 unit 干的活。

直到此时我才意识到 Go 跟我有私人恩怨.

我尝试了许多不同的方法去让查询语句和持久层能同时和 float32 以及 unit 兼容。我最不想做的就是复制/粘贴。幸运的是,我的故事是个成功的例子,我没有复制/粘贴。我用了次优类型(马上会谈到)。但那曾经是多么令人沮丧,我复制/粘贴了所有的代码 - 所有的逻辑,解析,持久性等等 - 并且准备提交代码。我真的做不到。

仅仅是 Go 语言规范几乎让我去提交一份复制/粘贴的复杂的查询语句和持久层代码这件事,就已经是一个让我换另一种语言的强有力理由了.

次优类型

当时我没有去复制/粘贴,而实际上我做的是在所有地方使用浮点数。没错, Watchdog 模块使用浮点数去记录当前运行进程数,磁盘写入次数以及换入/换出次数。

这个办法在数值类的类型中大都是没有伤害的,这也就是为什么我最终把它用在查询语句与持久层之中。不适用这个办法的情况其实远多于那些适用的情况。如果你有数值类的类型,考虑下这个办法。如果你没有,你要回到 empty interface 加上类型断言或者使用复制/粘贴.

至少,这比 CPU 占用率用 0-100 的 4 字节整数表示来的要好。

Unions

只是开个玩笑。Go 不支持 Unions,但这其实会非常有用。你认为呢?

via: https://blog.bluematador.com/posts/golang-pros-cons-devops-part-3-speed-lack-generics/

作者:Matthew Barlocker

译者:p31d3ng

校对:polaris1119

本文由 GCTT 原创编译,Go语言中文网 荣誉推出

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20180918B0FZTN00?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。

扫码关注云+社区

领取腾讯云代金券