ruby 撰寫上該注意的效能細節
ruby 提供非常多的函式,但同一種需求有不同寫法,往往我們都沒注意到效能細節,只知道要把功能寫出來。最近 github 上便有人針對類似功能的函式作些效能比較,在此針對特別或是速度差很多的地方作些個人筆記。原出處:https://github.com/JuanitoFatas/fast-ruby。
註:函式名稱會有標記哪種寫法較快,是 ruby 2.2.0 版本
多個變數的 assignment
def fast
_a, _b, _c, _d, _e, _f, _g, _h = 1, 2, 3, 4, 5, 6, 7, 8
nil
end
def slow
_a = 1
_b = 2
_c = 3
_d = 4
_e = 5
_f = 6
_g = 7
_h = 8
nil
end
Parallel Assignment 比 Sequential Assignment 快上 1.22 倍
Array#bsearch & Array#find
data = [*0..100_000_000]
Benchmark.ips do |x|
x.report('find') { data.find { |number| number > 77_777_777 } }
x.report('bsearch') { data.bsearch { |number| number > 77_777_777 } }
x.compare!
end
find 與 bsearch 主要是找到 array 裡面第一個符合條件的元素,在大筆數據上,bsearch 竟然比 find 快上 3137489.63 倍!
Array#length & Array#size & Array#count
ARRAY = [*1..100]
Benchmark.ips do |x|
x.report("Array#length") { ARRAY.length }
x.report("Array#size") { ARRAY.size }
x.report("Array#count") { ARRAY.count }
x.compare!
end
- length 單純回傳 array 裡面有幾個 element
- size 是 length 的別名(一樣的事情,只是不同名字)
- count 會實際去計算裡面有幾個 element
length = size 比 count 快上 1.24 倍(length 比 size 快上 1.01 倍,大概就是別名轉換的時間吧!)
Array#shuffle.first & Array#sample
sample 跟 shuffle.first 一樣都是做「取得隨機一個陣列中的元素」,但 sample 比 shuffle.first 快上 18.82 倍!(而且只在 array 只有 100 個元素下)
Array[] & Array#first & Array#last
Define: ARRAY = [*1..100]
- ARRAY[0] 比 ARRAY.first 快上 1.15 倍
- ARRAY[-1] 比 ARRAY.last 快上 1.12 倍
Enumerable#each_with_index & while
ARRAY = [*1..100]
def slow
ARRAY.each_with_index do |number, index|
number + index
end
end
def fast
index = 0
while index < ARRAY.size
ARRAY[index] + index
index += 1
end
end
Benchmark.ips do |x|
x.report('each_with_index') { slow }
x.report('While Loop') { fast }
x.compare!
end
這結果蠻令人吃驚,單純用 while 去做 index 比 each_with_index 快上 1.88 倍。
Enumerable#map & Array#flatten & Enumerable#flat_map
ARRAY = (1..100).to_a
def slow_flatten_1
ARRAY.map { |e| [e, e] }.flatten(1)
end
def slow_flatten
ARRAY.map { |e| [e, e] }.flatten
end
def fast
ARRAY.flat_map { |e| [e, e] }
end
flat_map 寫法快上 1.6x 倍!
Hash#merge & Hash#merge!
ENUM = (1..100)
def slow
ENUM.inject({}) do |h, e|
h.merge(e => e)
end
end
def fast
ENUM.inject({}) do |h, e|
h.merge!(e => e)
end
end
原來 merge! (有加驚嘆號)比 merge 快上整整 24 倍!(兩者差異在,沒加驚嘆號會是回傳一個新的 Array,有加驚嘆號是直接套用在呼叫的 Array 上)
cover? & include?
BEGIN_OF_JULY = Date.new(2015, 7, 1)
END_OF_JULY = Date.new(2015, 7, 31)
DAY_IN_JULY = Date.new(2015, 7, 15)
def fast
(BEGIN_OF_JULY..END_OF_JULY).cover? DAY_IN_JULY
end
def slow
(BEGIN_OF_JULY..END_OF_JULY).include? DAY_IN_JULY
end
range 內找是否有這個 element 的情況下,cover? 比 include? 快上 23.35 倍!