本文来自依云's Blog,转载请注明。
最近又看到 Rust 的相关东西了,入门指南也写得挺不错的。这语言我越看越喜欢。
Rust 的目标是系统级编程,就像 C 那样,快速高效。同时它继承了 Haskell 的诸多特性,包括其类型系统(包括类型类和类型推断)、模式匹配。而读写起来,又和 Python 差不多简单明了。简直是把这三种语言的优点全学到了!(当然 Rust 不仅仅受到了这几种语言的影响啦。)
当然,要体验一门编程语言,最好的方式就是使用它。于是我拿它实现了我最开始用来练习 Haskell 用的 swapview 程序。
swapview 的功能是,读取/proc
下每一个进程目录下边的cmdline
和smaps
文件,得到其命令行和 swap 使用量,然后排序、格式化,并打印出来。
Haskell 第一版实现挺慢的:
swapview 1.27s user 0.26s system 98% cpu 1.555 total
我随手写了个 Python 版,效率翻了一倍还要多!很令人惊讶的呢。作为解释执行、还一直被认为很慢的 Python 竟然在没有任何优化的情况下就超过了编译型的 Haskell:
swapview.py 0.35s user 0.18s system 97% cpu 0.548 total
后来在 IRC 上遇到一位懂行的人,用了不少手段优化,最终得到了 Haskell 第二版:
swapview2 0.42s user 0.15s system 98% cpu 0.583 total
比 Python 版略慢。
才学 Rust 没几天,我对 Rust 比对 Haskell 更不熟。花了不少时间查阅文档、调整代码。不过因为之前的 Haskell 基础,也没遇到太大的困难。结果如下:
swapview 1.84s user 0.15s system 97% cpu 2.038 total
呃呃呃,怎么比 Haskell 版本还要慢上不少啊?
本来是找 profiling 方法的。翻着 rustc 的 man 文档,看到了-O
选项,眼前一亮——我忘记告诉编译器要优化了!这是启用优化的结果,比 Python 版又快了一倍:
swapview 0.10s user 0.13s system 96% cpu 0.237 total
真棒呢~
不过很遗憾的是,它的格式化函数的第一个参数必须是字面量,连常量都不行。因为那是个宏,要在编译期解析格式……另外似乎也不支持现在连 JavaScript 都已经支持了的 generator(只支持 iterator,得先写一个 struct 才能用)。
PS: Rust 的文档挺赞的,和 Python 的一样有 JavaScript 实现的搜索功能,比起 Nimrod 和 Zimbu 的好用太多了。
PPS: 谁有兴趣可以贡献个 Go 版、C 版、C++ 版、LuaJIT 版什么的=w=
2014年12月25日更新:目前的结果是(运行时间):Rust < LuaJIT < C++14 (gcc 4.9.2) < Lua 5.1 / 5.2 << Python 3 < Haskell <<< OCaml < SBCL。手动测试的。有空我再写个好点的自动测试程序。
2015年1月6日更新:添加了更多的编程语言,以及更准确的运行时间测试,请见新文章编程语言对决——战场:swapview。
Dec 24, 2014 09:28:54 AM
你的python里面那个通用?
我安装了myutils,但是还是有如下错误呢?
File "./swapview.py", line 7, in <module>
from myutils import filesize
ImportError: cannot import name 'filesize'
Dec 24, 2014 10:41:32 AM
Rust 大法好~
Dec 24, 2014 10:58:05 AM
你从哪里安装的?myutils 在我的 winterpy 仓库里的啦。
另外 Haskell 版本依赖我的 myhaskells 里的东西。
Dec 24, 2014 12:25:00 PM
我用 easy_install 安装的。 难怪
你的winterpy 在你的源里 ?
Dec 24, 2014 12:32:08 PM
不在。你去 https://github.com/lilydjwg/winterpy 里找到并把 myutils.py 下载回来和它放一起就可以了。
winterpy 里边乱七八糟的东西太多了……
Dec 24, 2014 12:58:54 PM
还依赖了nicelogger ...
不过我什么都没执行出来 ...
PID SWAP COMMAND
Total: 0B
Dec 24, 2014 01:36:18 PM
你的系统没有用到 swap 啦~
我刚把那些依赖去掉了。
Dec 24, 2014 03:08:53 PM
额,我才记起,我没划swap出来 -_-
Dec 24, 2014 03:59:51 PM
可是rust代码是python两倍长,不见得合算。
Dec 24, 2014 04:40:37 PM
因为代码结构不一样啦。Rust 使用花括号也会多占一些行。
Dec 25, 2014 02:44:49 AM
我這邊Rust版本的跑不起來呢……先說SignInt沒有,改成Signed之後說
the trait `std::path::BytesContainer` is not implemented for the type `&collections::string::String`
什麼的……
順便儘量按照Python版本逐行翻譯了個C++14的:
https://github.com/farseerfc/swapview/blob/master/C%2B%2B14/swapview.cpp
(不知道是否足夠C++14,感覺用的還都是老特性……)
Dec 25, 2014 10:47:40 AM
话说我见到很多人都很看好 Rust 呢,我也打算学一学
Dec 25, 2014 11:04:15 AM
请从 thestinger 源安装 rust-git。官方里是 0.12 版本。在到达 1.0 版本之前各个版本间的兼容性一直会很差的(我昨天更新了 rust-git,然后收到了三个 deprecated warning 呢)。
Dec 25, 2014 11:59:36 AM
merge 了喵~稍微改了一下。
我这里测试的结果是,比 Rust 版略慢。
Dec 25, 2014 12:28:03 PM
我rust也編譯成功啦。
是呢,我這邊也比Rust略慢一點。估計是因爲vector複製了之類的原因?有時間再試試手動優化一下。
Dec 25, 2014 06:03:34 PM
发了个lua5.1的pull request 0 - 0
Dec 25, 2014 06:30:08 PM
求修到能够与已有版本对比的程度 0.0
Dec 25, 2014 09:37:48 PM
我已经给修好了喵……
Dec 29, 2014 10:34:18 PM
我照抄Python用Haskell写了一模一样的一个,发现速度真的比Python慢很多倍,感掉好丢人啊。
BTW,没有自动类型提升,fromIntegral之类的方法多么操蛋啊。
https://gist.github.com/JexCheng/f523e6ec288889dd7471
Dec 29, 2014 10:52:18 PM
呃,你的没有做错误处理,是拿 sudo 跑的么……
你不会是拿 runhaskell 跑的吧。编译时加 -O2,我写的版本耗时是 Python 3 的两倍,而另一个优化过的版本和 Python 3 的已经很接近了。
Dec 30, 2014 10:56:49 AM
知道,故意不加错误处理的,也没用 printf,限定了类型,也O2了。速度比你的第一版本还慢。你的第二个版本我试了user运行时间大约是Python的两倍。
Dec 30, 2014 11:07:43 AM
是root 跑的。
以前听说即使搞程序证明的都用OCaml,现在有点明白为啥了。为了写出个性能表现正常些的Haskell程序要绕这么多的路 TOT 。
Rust将printf作为宏的想法,我不久前也在考虑,将format字符串在编译期解释,既可以获得类型安全,又可以获得性能提升,加上Constant Folding,我估计Rust够聪明的话,未来它应该只要求format string参数是可在编译期确定即可,好像听说C++新版的constexpr也在向这方面发展,不过估计他们得撸个C++/Rust的解释器内嵌到编译器中才行
Dec 30, 2014 11:15:12 AM
我预想的PL可以这样:
```
class A {
"forEach orderBy map reduce sort".words.forEach \methodName->{
self[methodName]= Enumerable.method methodName
}
}
```
上面的代码可在编译期expanding,不知道有没有哪个语言已经抢先实现了,Taiji?但性能表现不行就不如直接用Ruby了
Dec 30, 2014 11:19:25 AM
我的 OCaml 版本更慢呢……
Rust 那样是快,可是翻译怎么办呢?
Dec 30, 2014 11:33:58 AM
可以让"Hello:%(name)s,Id:%d" 变成
["Hello:","Id:"] `zipGensym` [name,itoa(i)]
zipGensym 是动态创建的一个函数,其类型根据formats 推导出来。
对应gettext文件编译格式也要转变。这样getext格式就不通用了,但总要有技术革新的嘛。(反正我也早受够那个叫什么po格式的文件了,还不如直接用JSON映射方便,修改一次还要重新编译
如果OCaml也慢的话,printf可能需要运行期的类型检查,regexp我不清楚具体实现,除了这些,我就不知道该说什么好了,TOT
Dec 30, 2014 11:35:18 AM
Racket呢?
Dec 30, 2014 12:43:37 PM
JSON 不支持注释,逗号的使用反人类,到处是引号也很烦。
po 我觉得还好。编译什么的,反正整个项目都是要编译的。
TOML 看上去不错的样子。
Dec 30, 2014 12:43:57 PM
不会写。求 PR~
Dec 30, 2014 12:45:08 PM
Lisp 宏就可以做到类似的效果啊。
Dec 30, 2014 01:38:09 PM
宏和这个不一样,试试更复杂的例子:
```
object=new A
B.methods.forEach \(methodName,method) -> {
object[methodName]=method
}
map print B.methods
```
(Class)B.methods是可在编译期确定的,假设类型为`[Method::B this -> Any]`,但除非B.methods.forEach、map本身是一个宏,否则做不到这点。
(卫生)宏摆脱不了这个缺陷,它跟语言的对象系统是分开的,普通函数和宏必须加以区别,这正是问题所在。比如String有regex match方法,如果regex是constexpr, string也是constexpr,那么结果就也可以是constexpr。再如,我要将Regex /a\d+/编译成DFA,我希望这个编译是在编译期完成,我觉得用宏实现不太实际,如果macro与普通函数不作区别,那么就轻松多了,只需要在编译期将所有constexpr执行一遍,相当于编译期的JIT(呃,可能Lisp的一些实现使用dump内存的编译方式或能达到类似效果。
具体细节以后等我实现好了再解释,反正我是不给(即使卫生)macro留地方站的
Dec 30, 2014 03:42:29 PM
照抄Python的Racket实现:
https://gist.github.com/JexCheng/165c14dea694f731b2f8
比Haskell稍微快那么一点点点
Dec 30, 2014 04:35:56 PM
没有错误处理啊,而且输出格式也不一样。
Dec 30, 2014 04:41:45 PM
是float precision么?它就那样的。
Dec 30, 2014 06:27:51 PM
不过指定格式的么?
Dec 30, 2014 08:27:50 PM
它的format 方法precision参数原来要设成 '(= 1) 才会始终保留".0"
更改过了的:
https://gist.github.com/JexCheng/165c14dea694f731b2f8
Jan 03, 2015 10:24:49 PM
有很多string copy, 不科学!
试试用一些std::move
Jan 03, 2015 10:33:48 PM
求补丁求PR~
Jan 04, 2015 03:13:13 AM
雖然標準裏沒有作強制要求,但是basic_string<char>和vector<char>的重要區別之一就是basic_string允許實現爲Copy-on-Write的。據我所知實際上gcc和VC和stlport的實現也的確是CoW的並且附帶了小串內嵌的優化。
TL;DR string的copy的開銷(不修改內容的話)和move語義差不多了啦,即使在98標準化前的C++也是這樣,這不像vector。
Jan 04, 2015 03:16:02 AM
雖然標準裏沒有作強制要求,但是basic_string<char>和vector<char>的重要區別之一就是basic_string允許實現爲Copy-on-Write的。據我所知實際上gcc和VC和stlport的實現也的確是CoW的並且附帶了小串內嵌的優化。
TL;DR string的copy的開銷(不修改內容的話)和move語義差不多了啦,即使在98標準化前的C++也是這樣,這不像vector。
不過歡迎改進和PR,自動benchmark基本準備好了,用std::move是否可以快一些跑一下就知道啦。