[Code翻译]"更好的C "基准线

302 阅读10分钟

原文地址:zserge.com/posts/bette…

原文作者:zserge.com/

发布时间:2021年3月18日

C语言已经快50岁了。对于一瓶酒来说是个很好的年龄,但对于一个快速发展的行业的编程语言来说,就不是那么好了。在过去的10年里,出现了许多新的语言,有不同的味道,都试图在一定程度上成为C语言的替代品。

当一种新的语言变得越来越流行时--开发者们开始写基准,显示这种语言的软件性能如何,使用了多少CPU功率和多少内存,二进制文件有多大等等。

在这里,我想在另一个层面上进行一个小实验--编程语言的用户体验,开发者在使用这种或那种语言时有多大的生产力,使用它们有多容易,有什么常见的挫折,人们在阅读代码时有什么感觉。我相信编程语言的用户体验和它们的技术特点一样重要,它对一门语言的成功有很大的贡献。

警告:帖子的其余部分是非常主观的。

实验

让我们写一个应用程序,它可以递归扫描当前目录中的所有文件,并打印文件中与给定通配符匹配的行。类似于ag或grep,但使用通配符而不是正则表达式。二进制文件应该被忽略。

我发现这个问题是一个很好的练习,因为它展示了如何实现一个非常简单的通配符匹配算法(它适用于字符串和数字等纯数据,不需要学习任何库或API)。这个算法应该可以通过一些非常简单的测试。然后,它需要一些很常见但又很低级的API,比如递归扫描目录或逐行读取文件。问题的所有部分都非常简单,体积小,范围大。我假设这样的实用程序应该很容易在任何语言中实现,即使之前没有任何经验。

我想测试这门语言对常规的 "写-编译-运行-调试 "循环有多 "友好",为匹配算法写测试有多容易,找到与文件系统和基本I/O工作所需的API有多容易,编译器指出错误时有多友好,这门语言有多 "直观 "等等。

我的样本量相当简陋--只有我自己。但为了避免偏见,鼓励你自己也做同样的事情(应该不会花太多时间),并比较结果。当实用程序用不同的语言写好之后--我请我的同行开发者(大约20人)阅读它,并告诉他们它做了什么,哪些地方不清楚,哪些地方感觉 "更容易 "阅读和理解。这些开发人员对这两种语言都没有经验,但却经常用其他语言编码,比如Java、C#、JavaScript、Kotlin、Swift等。

我这里测试的语言有C++、Go、Rust和Zig。我得到的结果程序在github.com/zserge/glob-grep ,欢迎批评指正。

ZIG

一切都是因为我想看看Zig是一种什么样的语言。我从来没有用过它,但听到一些好的反馈。在没有任何经验的情况下,我打开vim开始编码。

我花了大约1个小时完成了这个程序。通配符匹配算法(我之前就知道,只需要在Zig中实现)花了我大约20分钟。剩下的就是寻找API来做目录扫描和文件读取。

TLDR:很棒的、直观的小语言,可怜的stdlib和docs。

我喜欢的地方:这门语言对于一个C语言编码者来说,出乎意料的直观。感觉非常简单,关于该语言的文档(但不是stdlib)非常清晰和友好。

对于一门年轻的语言来说,Vim的集成也非常好(在我启用vim插件之前--我对格式化错误(即编译器错误)感到很恼火)。

我喜欢它的错误处理方法。喜欢语言中自带的测试线束。甚至喜欢字符串只是字节的数组,就像C语言一样。

带着分配器到处跑,我的第一反应是震惊,但实际操作中根本看不出来。给人一种极简主义的感觉,语言的核心非常简单,甚至没有使用动态内存。同样,非常接近C语言。

我在写这个的时候要看很多Zig编译器和stdlib的源码,代码非常清晰简洁。

我不喜欢的是:stdlib文档很糟糕。我所了解到的关于目录扫描和文件I/O的一切信息--我都是从github搜索结果中得到的,这些信息也是相当稀少的。

编译器的信息也远非友好,但对于熟悉C语言的人来说,这并不是什么大不了的事。

stdlib中缺少字符串处理例程是出乎意料的,要想串联字符串就必须手动完成一切--分配缓冲区,把字符串放在那里。或者用formatter和一个分配器把两个字符串并排打印出来,之后释放缓冲区。这和s1+s2还是很不一样的。

总的来说,核心语言很简单,我也很喜欢,但是stdlib比libc更加局限。我希望这只是语言早期的一个标志。

读过Zig代码的人都提到,是它让他们意识到了这个程序的作用。它有点啰嗦,但很明确,可预测,而且容易理解。这并不奇怪,因为这门语言在设计时就考虑到了可读性(没有隐藏的控制流,没有隐藏的分配,没有宏,没有操作符重载,没有元编程等等)。

RUST

我曾几次尝试学习Rust,但都失败了。我花了2个多小时才完成这个程序,完成后我感到很沮丧。

TLDR:复杂。

我喜欢的是:编译器的信息很好。对语言的文档也很好。但大概就是这样了。至少我这次不用和终身错误做太多斗争。围绕语言的工具很现代,很好。

我不喜欢的是:编译器信息太啰嗦,占据了整个屏幕。不,我不想为每一个错误运行rustc--解释。请不要惩罚我。文档有时也太啰嗦了。我的意思是,文档多比少好,但先有一个TLDR版本会更好。stdlib也是一样,用一句话简明扼要地列出函数和它们的作用,作为读者会更容易浏览。有&str,Str和[u8]在显然是必要的,但让一个新手感到惊讶。

总的来说,用Rust编码给我的感觉就像解谜一样。可能会很有趣、很刺激,尤其是当把Rust作为一种爱好语言时,但对于大多数任务来说,我宁愿选择一种 "符合人体工程学 "的语言,成为一种几乎不引人注意的工具。

阅读由此产生的Rust代码的人经常会在过程中提出至少几个 "wtf "的问题。他们经常抱怨语法不清晰,需要注意细节。另外,模式匹配对于 "主流 "开发者来说,还是一个陌生的东西。

GO

这是在作弊。显然,我以前有一些使用围棋的经验,但我还是想在这个实验中试试。我花了大约15分钟使我的完整的 "glob "实用程序工作,正如我所期望的那样。

TLDR:有成效,但有意见。

我喜欢的地方:感觉很有成效,docs对我的口味来说是惊人的--简短,但很有用,人们可以立即打开相关的stdlib函数源并进一步调查。从过去的经验来看,在写应用的时候,我已经设想了如何让它成为多线程并提升性能(简单的扇出)。

我不喜欢的是:太多的东西都在罩子下面(缓冲I/O,GC)。你不觉得自己能控制一切(像Zig那样)。太有主见了--这是列表中唯一一个需要我创建3个独立的文件才能使其工作的语言。仍然有可能犯愚蠢的错误,比如意外的变量阴影,或者在循环里面使用defer。

看了结果的围棋代码的人都觉得很清楚,有的人对内联走位函数感到奇怪(不一定要内联,他们是对的)。有的人想知道多重赋值,即a,b=c,d,那里也是不需要的,使事情更加混乱。讽刺的是,如果我是围棋新手--我会写出更直接的围棋代码。

C++

虽然我有一定的C语言经验,但离现代C++比较遥远,所以我决定试一试。我花了大约20分钟就完成了,这是我意想不到的。

TLDR:好一个老 "冤家"。

我喜欢的地方:感觉很熟悉,就像见到了过去的老朋友。我很喜欢文档,有很多例子,可读性很好。我很惊讶地看到现在的stdlib是多么强大。在文本编辑器和IDE中的支持也很扎实。

我不喜欢的是:糟糕的工具--没有构建系统,没有测试线束,没有linter。我们从过去就习惯了,但这不是一个现代开发者所期望的。太强大了--对于这个非常的任务,C++感觉很有成效,但我可以想象自己在某些时候会出现决策麻痹症,因为有很多不同的方法可以做某件事,而且都一样好(或不好)。

读到由此产生的C++代码的人其实过去也读过C或C++,至少作为大学课程的一部分。很多人抱怨使用了::,所以我应该正确使用命名空间,我想。总的来说,由于我对C++代码没有什么 "品味"--我相信它可以写得更清楚,但我也看到它可以写得更糟糕,甚至没有注意到。

其他基准

所有语言都产生了静态可执行文件,大小都差不多(2.5MB)。最小的是Zig,最大的是Rust。当扫描我的整个/usr/include文件树时,所有语言的性能都差不多。这就是为什么我想强调技术特性往往不如开发者的体验重要。

我想单独提到构建时间。我把整个构建+测试+清理的循环跑了一百次。Go来的最快(正如预期的那样),其他三个都是基于LLVM的,速度慢了约3-4倍。

这一切意味着什么?结果并不出人意料,往往遵循着关于语言的老生常谈。Go读起来很简单,Rust很复杂,C++很熟悉,Zig看起来很有前途,但还太年轻,无法判断。

如果我要写一个新的服务/实用程序,不需要与C代码进行大量交互--我肯定会选择Go。如果我必须调用一些C或C++库--我会坚持使用C++(不幸的是)。Rust和Zig在现代编程世界中会占据什么样的位置--只有时间才能证明。我希望Zig有一个更好的文档来获得普及,免得它变得太小众、太模糊。我一定会更加密切地关注它,到目前为止,它是我遇到的第一个真正的C替代者,尤其是在低级编码方面。

不过当然,C虽然年代久远,但还是要留下来。还有太多的领域,C是唯一真正的选择。而我很高兴C语言的存在。

我希望你喜欢这篇文章。你可以在Github、Twitter上关注--并做出贡献,或者通过rss订阅。


通过www.DeepL.com/Translator(免费版)翻译