翻译 如何在源码阶段写出更快的 Ruby

etnl · May 25, 2015 · Last by jjzxcc replied at June 24, 2015 · 8219 hits
Topic has been selected as the excellent topic by the admin.

此文章翻译自How To Write Ruby Faster at the Source Code Level, 本人水平有限,错误之处,还望各位指正。


优化可以采取许多不同的形式,但程序员关注何处以及如何开发项目的过程中进行优化。这个讲演来自 RubyConf India 2015, 总部位于柏林的 SoundCloud 开发者 Erik Michaels-Ober(twittergithub) 对于在源码这个层级如何提升 Ruby 性能优化,进行了一次演讲。

Michaels-Ober 首先注意到的是,大多数开发人员对过早的性能优化有偏见。他引用了斯坦福教授、算法设计和分析之父 Donald Knuth,在 1974 年的一句话 "过早的优化乃万恶之源"。Michaels-Ober 指出适当的但不合时宜的优化的确会让代码“丑陋,难以阅读和更复杂”,所以只有在必要或合时宜时,进行代码优化。

什么时候进行优化呢?这里,Michaels-Ober 再次引用 Knuth,他有两个标准:第一,优化可以轻易获得;第二,优化的好处巨大 (Knuth 给出 12% 的阈值)。Michaels-Ober 增加了第三个标准,借用自 Ruby 的主设计者Matz(Yukihiro Matsumoto): Ruby 为程序员快乐而生,所以优化也应使程序员感到快乐 -- 不论是让代码更“漂亮”或拥有更好的性能。

优化层次

Michaels-Ober 接着概述了可能适用于任何语言和项目的不同层次的优化:设计、源码、构建、编译和运行时。Michaels-Ober 的重点是源码优化。再次搬出 Knuth ,大神认为:“程序员的直觉预测电脑会如何解释的代码、是否优化时会出现偏差”。 “对于代码的快慢,我们没有很好的直觉”Michaels-Ober 说到。“我们常常犯错,所以如果过早的优化,或许你认为你在优化它,但实际上你在写一个非最优代码”。

那么如果知道哪个版本是更优的呢? “你必须跑个分”, Michaels-Ober 说到。Ruby 内置基准测试库允许对一个方法运行指定的次数。然而 Michaels-Ober 指出内置基准测试的问题是你得猜运行多少次才是明显的提升。

作为替代,Michaels-Ober 建议使用 benchmark-ips(iteratorions per second), 它使用每秒钟运行次数而不是每次运行秒数。每个版本的代码运行五秒,会生成一个代码运行次数的“报告”。 “5 秒时间是一个足够长的时间,足以移除统计误差;运行次数越多,误差会越低,所以不必担心这些误差”。

在代码阶段写出更快的 Ruby

Michaels-Ober 接下来展示了一些如何简化代码来达到运行更快、更易读的例子(你可以参照 Speaker Deck 主题幻灯)。

Block vs. Symbol#to_proc

Symbol#to_problock快 20 倍,因为 Ruby 解释器内部做了优化。Symbol#to_proc最初作为 RAILS 快捷方法,后来添加到了 Ruby。Ruby 知道如何运行Symbol#to_proc,并在内部进行优化。

Enumerable#map and Array#flatten vs. Enumerable#flat_map

map会返回一个列表的列表。如果想让一个列表map然后再flatten,可以使用可读性更好的flat_map代替mapflatten,俩工作方式相同,且flat_map快 4.5 倍,因为它只迭代一次,而不是两次。

Enumerable#reverse and Enumerable#each vs. Enumerable#reverse_each

reverse_each不会拷贝列表,只是反方向迭代。这使得它比另一种方法快 17%。

Hash#keys and Enumerable#each vs. Hash#each_key

Ruby 有个内置方法创建哈希键列表。更快的方法是不立即创建列表,而只是返回键的值,这让它快了 33%。

Array#shuffle and Array#first vs. Array#sample

假设有个列表,想随机的提取一些值。一个方法是使用array.shuffle.first,打乱列表然后取列表的第一个元素,或者使用array.sample,它比前一个方法快 15 倍。

Hash#merge vs. Hash#merge!

不可变版本修改哈希对比可变版本创建一个哈希的拷贝,再进行合并和拷贝。因为在更小的空间内修改哈希,所以快了 3 倍。同样适用于Hash#[],快 2 倍速度。

Hash#fetch vs. Hash#fetch with block

fetch第二个参数是 block 要比直接传递 block 的结果快 2 倍。

String#gsub vs. String#sub

gsub将字符中所有的第一个字符替换成第二个字符。如果你只想进行一次替换,也不扫描多余的字符。使用sub快 50%。

String#gsub vs. String#tr

想进行全部替换时,总是可以使用tr替代gsub,使用tr快 5 倍。

Parallel vs. sequential assignment

并行赋值需要分配列表,且可读性不高。并行赋值在交换值的时候很有用,除此之外,使用顺序赋值,它可读性更好、快 40 %。

Throw/catch

exceptions相比,使用throw/cath控制流跳转,它快 5 倍。

Michaels-Ober 说到:“尽管其他的优化可以在下一个代码编译阶段完成,但上面这些源码的优化技术可以快速、无痛的完成,可以使 Ruby 代码更快、性能更好、可读性更高”。


如果上面这几个不过瘾的话,大力推荐 fast-ruby 。感谢 @JuanitoFatas

很赞!比如 merge gsub 等等平时就用的比较多,惭愧啊!

赞一个

4 Floor has deleted

感谢分享!好

Symbol#to_proc 比 block 快 20 倍

涨知识了。。。

8 Floor has deleted

谢谢分享!!

#1 楼 @clarkyi merge! 是比较快的吧

@lithium4010 手误,已更新! 😊

涨姿势了

感觉应该是「在代码(这个)层级」而不是「在代码阶段」吧…

:plus1:

涨姿势,很实用,不错,而且个人觉得 ruby2.x 版本的性能其实不差

感觉读起来像《Effective Ruby》

22 Floor has deleted

:plus1:

You need to Sign in before reply, if you don't have an account, please Sign up first.