lazy ideas in programming(編程中的惰性思想)

  lazy形容詞,懶惰的,毫無疑問是一個貶義詞。可是,對於計算機領域,lazy倒是很是重要的優化思想: 把任務推遲到必須的時刻,好處是避免重複計算,甚至不計算。本文的目的是拋磚引玉,總結一些編程中的lazy idea,以期有一些啓發。google 「lazy」這個單詞,在計算機領域高頻出現三個詞: lazy loading(惰性加載)、 lazy initializing(惰性初始化)、 lazy evaluation(惰性求值),本文並不刻意區分,由於不論是loading、initializing仍是evaluation都須要耗費計算機的運算資源,並且,loading(initializing)也是某種意義上的evaluation。
 

lazy ideas:

  在GOF的設計模式中,並無一個叫「lazy loading」之類的設計模式,可是其思想貫穿在不少設計模式中。其中比較明顯的就是singleton和proxy模式。

singleton

  單例模式的實現通常都有兩種方式,要麼在調用以前就建立好單例對象( eager way),要麼在第一次調用的時候生成單例對象( lazy way),二者對象的代碼大體是這樣的:
  
 1 class eager_meta(type):
 2     def __init__(clz, name, bases, dic):
 3         super(eager_meta, clz).__init__(name, bases, dic)
 4         clz._instance = clz()
 5 
 6 class singleton_eager(object):
 7     __metaclass__ = eager_meta
 8 
 9     @classmethod
10     def instance(clz):
11         return clz._instance
12 
13 
14 class singleton_lazy(object):
15     __instance = None
16     @classmethod
17     def instance(clz):
18         if clz.__instance is None:
19             clz.__instance = singleton_lazy()
20         return clz.__instance

 

   PS:在python中,這樣使用單例模式不是很pythonic,更好的辦法可見在stackoverflow上的這篇文章《creating-a-singleton-in-python》。另外在多線程環境下,要實現線程安全的單例仍是很複雜的,具體討論可參見iteye上的分析。javascript

 

proxy:

  代理模式屬於責任型模式, 使得一個對象表明另外一個對象進行各類操做,經常使用場景包括
  • remote proxy(遠程代理),如RMI, RPC
  • virtual proxy(虛代理),根據須要建立開銷很大的對象,如文檔中圖片的加載
  • (保護代理):控制對原始對象的訪問, 如智能指針
  其中 viatual proxy是使用lazy loading很好的例子
 

Short-circuit evaluation:

  短路求值在絕大多數編程語言都有實現,比較常見的語法以下:
     x and y(x && y)
    x or y(x || y)
    x if bool else y(bool? x : y )
  短路求值基本上都是數學邏輯的體現,若是第一個參數已經能推導出這個表達式的意義,那麼後面的參數是無需計算的。短路求值很是有用,不只能避免無用的計算,對於邏輯判斷也很是有用。好比在python中,判斷一個對象的is_ok屬性爲True,咱們通常這麼寫
   if(obj.is_ok)
  若是obj被賦值成了None,那麼就會報一個異常,因此能夠寫成
   if(obj is not None and obj.is_ok)
 
  python中,一些函數也有短路求值的特性,好比在 這篇文章中提到的any函數:
1     ret = any(self.calc_and_ret(e) for e in elements)
2     def self.calc_and_ret(self, e):
3         # do a lot of calc here which effect self
4         return True(or False)
    本意是但願對全部的element都計算,而後返回一個結果,但事實上因爲短路求值, 可能後面不少的元素都不會再調用calc_and_ret
 

generator:

  在python和javascript語言中都有generator,generator與普通的函數相比,能夠屢次(甚至無限次)返回,並且返回值是在須要的時候才生成。在python中,下面兩段代碼很是類似,但事實上差別很是大:
1     for x in [i*i for i in xrange(10000)]
2       # do sth with i
3 
4     for x in (i*i for i in xrange(10000)]
5       # do sth with i
 
  generator更普遍的應用能夠參見 《python yield generator 詳解》。javascript中generator的語法和使用與python都很是相似,能夠參見 這篇文章
 

函數式編程語言中的應用:

  lazy evaluation在函數式編程語言中使用得很是頻繁,python也能夠當作函數式編程語言來使用,而更爲明顯的是 haskell,在其首頁的features介紹裏面就有大大的「lazy」

 

cache:

  cache也是一種lazy思想,若是以前有計算結果,那麼直接複用以前的結果就好了,幹嗎還要從新計算呢?並且最開始的緩存內容, 也是在須要的時候才計算的,而不是一開始就計算好。 wiki上有python實現的簡單例子:
   
 1 class Fruit:
 2     def __init__(self, item):
 3         self.item = item
 4     
 5 class Fruits:
 6     def __init__(self):
 7         self.items = {}
 8     
 9     def get_fruit(self, item):
10         if item not in self.items:
11             self.items[item] = Fruit(item)
12         
13         return self.items[item]
14 
15 if __name__ == '__main__':
16     fruits = Fruits()
17     print(fruits.get_fruit('Apple'))
18     print(fruits.get_fruit('Lime'))

 

Dirty Flag:

  在《 Dirty Flag模式及其應用》一文中,列舉了Dirty Flag模式的諸多應用場景。Dirty Flag顯然也是很明顯的lazy evaluation。好比《 game programming pattern》中的例子:子模型的世界座標取決於父模型的世界座標以及子模型在父模型座標空間的相對座標,若是父模型的世界座標變化時就主動去從新計算子模型的座標,由於兩幀之間父模型的座標可能屢次變換,每每會形成冗餘的計算。因此Dirty Flag只是在父模型座標變化的時候標記,繪製的時候再計劃全部受影響的模型的世界座標。
 

CopyOnWrite:

   CopyOnWrite即寫時複製,若是你們對一份資源只有讀請求時,那麼資源是能夠共享的,當某個訪問者須要修改資源(寫操做)時,就將資源拷貝一份給該訪問者使用。即資源的拷貝被延遲到了第一次"寫"的時候。CopyOnWrite最廣爲人知的兩個應用場景,一個是Unix like系統fork調用產生的子進程共享父進程的地址空間,知道寫操做纔會拷貝一份。另外一個是java中的copyonwrite容器,用於多線程併發狀況下的高效訪問, cookshell上有對copyonwrite容器的詳細介紹。
 

web開發中的惰性加載與惰性預加載:

  在web前端和APP開發中,當提到惰性加載或者動態加載,你們每每會想到 分頁、輪播圖、瀑布流,這些都體現了惰性加載的思想。其中,瀑布流在出諸多圖片分享網站中使用很是普遍,好比 花瓣網,當滑動到屏幕底部的時候纔會去加載新的內容。爲何要使用惰性加載,第一個是用戶體驗的問題,圖片資源流量比較大,一次加載太多對服務器和瀏覽器壓力都很大,對帶寬要求也很高;另外,可能用戶根本就不會滑動到最下面,多加載的內容就白白浪費了。 
  固然太」Lazy」了也是很差的,總不能讓玩家滑動到底部才一邊顯示loading icon,一邊開始加載。爲了提升用戶體驗,這類網站也會作預加載(predictive loading),即多準備一兩頁的內容,或者在滑屏到必定程度時開始加載新的一頁,不過這樣的預加載也是 惰性預加載(lazy predictive loading)
 

總結:

  本文列舉了惰性計算在編程中的一些具體例子,但願能給本身以及你們有所啓發,在之後遇到問題的時候多一種解決思路。因爲本人編程領域以及編程語言的侷限性,確定還有諸多遺漏,歡迎你們在評論裏補充。
 
references:
相關文章
相關標籤/搜索