如何寫出有效率的 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 是個很慢的語言,但有些作法應用得當,還是會有不錯的改善。不過要知道程式碼的可讀性跟執行效率有時候是衝突的,這點還需拿捏,尤其 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 就好了。