又來推薦 PDF 武功秘笈, Writing Efficient Ruby Code。
Ruby 是個很慢的語言,但有些作法應用得當,還是會有不錯的改善。不過要知道程式碼的可讀性跟執行效率有時候是衝突的,這點還需拿捏,尤其 software life cycle 一開始可讀性比較重要。有句最佳化的經典名言一定要引一下:
未成年就這麼優,是一切邪惡的根源
Premature optimization is the root of all evil
這裡紀錄一些看到有趣的事情,PDF 裡有更詳盡的 example code。
Instance Variables versus Accessors
@attrubute 比 self.attrbute 快(method call 比較貴),如果你不需要 public method 或有 sub-class 的需求,請考慮不要用 attr_accessor 等方式來建立 read/write method。
Local Variables Are Cheap
method 中傳進來的參數若常用,可以先存成 Local Variables 再來多次使用。
Interpolated Strnges
方法一 s = “:#{a}.#{b}” 比方法二 s = “:” << a.to_s << “.” << b.to_s 快多囉!! 不需要多用 << method call。另外單引號跟雙引號沒有效率上的差別。
In-Place Updates
直接修改比複製一份快: gsub! 比 gsub 快,merge! 比 merge 快。例如這個範例 s.gsub().gsub!().gsub! 而不是 s.gsub.gsub.gsub
不過要小心,Ruby 對於 ! 版本的方法回傳值有時候不是自己(得查 API 確定一下),因此沒辦法用串接寫法。
Set Versus Array
如果你只用到 Array 的 uniq, |, %, – 等群集操作,請考慮改用 Set 會比較快,像是 include? 就可以在 O(1) 做完。而 Hash 又比 Array 貴。
Make Decisions at Load Time
注意到在 module 或 class 的 definition scope 也是可以執行程式的,而且只有第一次 require source code 時會執行編譯一次。
class Foo
if C=’somesetting’
def A
…version 1
end
else
def A
…version 2
end
end
end
Self-Modifying Code
避免寫出自己修改自己定義的程式(也不好讀code吧),改用 singleton 方式來做 alias_method 跟 remove_method。
Test Most Frequent Case First
用 case 時把較常發生的放前面,若都差不多,把貴的操作的放後面。
Optimize Access to Global Constants
在 Constant 前面加上 namespace operator :: 會比較快,減少查詢時間
Caching Data in Instance Variables
例如在 controller 裡面 def captial_letters { (“A”..”Z”).to_a } end 請改用 def captial_letters { @captial_letters ||= (“A”..”Z”).to_a } end
Caching Data in Class Variables
承上,如果 data 較大又持續存在,可以改用 @@capital_letter = (“A”..”Z”).to_a 然後定 def captial_letters { @@captial_letters } end。在 ActiveRecord 中也可以先把 DB 的資料讀出來當作 CONSTANTS,就不需要每次都查資料庫了:
class State < ActiveRecord::Base
NAMES_ABBR = self.find(:all).map do { |s| [s.name,s.abbr] }
end
Coding Variable Caching Efficiently
愛用 @var ||= begin …expr… end 而不是 @var = begin …expr… end unless @var,這樣只需查 @var 一次。但如果 @var要能吃 nil 或 false 就只能多一個 boolean 變數來幫忙了。
Initializing Variables with nil
若沒有給值,就不需要初始成nil
Using .nil?
如果要測試的值已知不是 Boolean,那就不需要用 .nil 多一個 method call,直接 if x 就好了而不是 if x.nil?
nil? or empty? versus blank?
ActionPack 有提供 x.blank?,不需要用 x.nil? || x.empty?
Using return
雖然 Ruby 很聰明會自動回傳最後運算的值,但明確寫 return 會比較快 (Ruby 1.9有改善)
Using any?
用 empty? 來測試 string, array or hash 而不要用 any?,速度差個兩倍 (而且 Ruby 1.9 也拿掉 any?這個method了)
Block Local Variables
在 Ruby 1.8 裡面,在 Block 裡面存取 Block 外面的 local variable 竟然比裡面的 local variable 還快!! 不過 Ruby 1.9 修正這個奇怪的事實就是了。
Parallel Assignments
很方便沒錯,但 a,b=1,2 運算還丟出來 Array [1,2] 是垃圾。(Ruby 1.9 修正了,改丟 true)
Date Formatting
用 parse_date 把 String 變成 Date 是個非常昂貴的操作,建議你直接用字串操作(正規表示法)把 String(例如從DB撈出來的db_date) 變成你想要的格式,或是從中拆出你想要的年月日。(PDF 有 example code)
Temporary Data Structure Constants
如果用到一個暫時的資料結構(如Array)但接下來不會更改,可以改用 Constant 宣告,並避免複製。
ObjectSpace.each_object
DO NOT USE IT,你不會想在 per-request 下去執行的,很貴。
Unnecessary Block Parameters
def foo(bar, &block) 的 &block 若在函式內不需要用到(可以直接用 yield 使用的),請不要加。轉成 Proc 比單純 yield 的慢5倍!!
Symbol.to_proc
ActiveSupport 提供的 @var.map(&:name) 語法,雖然方便,但是用 inline block 的方法 @var.map{ |a| a.name| } 執行效率比較快。
Requiring Files Dynamically
在 Rails 中若碰到一個未定義的常數,它會自動去 load source files 來載入定義。為了避免混淆 file-loading 機制,不需寫 require ‘client’ (這又是另一套 auto-loaded 機制),只要單單一行寫 Client 就好了。
Including Modules versus Opening Classes
使用 module mix-in 會比打開 class 加入 method 還慢一些,如果你的 module 只針對一個 class 做 mix-in,那不如直接打開來加入 methods 就好了。
原來Ruby還可以這樣搞呀,真是長知識!
很不错,学习了
SET VERSUS ARRAY
如果你只用到 Array 的 uniq, |, %, – 等群集操作,請考慮改用 Set 會比較快,像是 include? 就可以在 O(1) 做完。
这里的再O(1)做完没有看懂@_@,可以简单说下吗谢谢
演算法的 time-complexity (aka “Big O” or “Big Oh”)。O(1) 意指無論數量多大,都是一個常數時間內完成,O(n) 則表示和數量成線性。
查詢一個元素是否在 Set 裡是 O(1) 的演算法,換成用 Array 的話,變成 O(n)。
Caching Data in Class Variables
self.find(:all).map do <== 這邊有錯, 沒有 do