莊周夢蝶

          生活、程序、未來
             :: 首頁 ::  ::  :: 聚合  :: 管理

          Clojure的并發(三)Atom、緩存和性能

          Posted on 2010-07-17 13:58 dennis 閱讀(6139) 評論(3)  編輯  收藏 所屬分類: javaClojure
          Clojure 的并發(一) Ref和STM
          Clojure 的并發(二)Write Skew分析
          Clojure 的并發(三)Atom、緩存和性能
          Clojure 的并發(四)Agent深入分析和Actor
          Clojure 的并發(五)binding和let
          Clojure的并發(六)Agent可以改進的地方
          Clojure的并發(七)pmap、pvalues和pcalls
          Clojure的并發(八)future、promise和線程

          三、Atom和緩存

              Ref適用的場景是系統中存在多個相互關聯的狀態,他們需要一起更新,因此需要通過dosync做事務包裝。但是如果你有一個狀態變量,不需要跟其他狀態變量協作,這時候應該使用Atom了。可以將一個Atom和一個Ref一起在一個事務里更新嗎?這沒辦法做到,如果你需要相互協作,你只能使用Ref。Atom適用的場景是狀態是獨立,沒有依賴,它避免了與其他Ref交互的開銷,因此性能會更好,特別是對于讀來說。

           1、定義Atom,采用atom函數,賦予一個初始狀態:
          (def mem (atom {}))

          這里將mem的初始狀態定義為一個map。

          2、deref和@:可以用deref函數,也可以簡單地用宏@,這跟Ref一樣,取atom的值:
          @mem         => {}
          (deref mem)  => {}

          3、reset!:重新設置atom的值,不關心當前值是什么:
           (reset! mem {:1})

          查看mem:
          user=> @mem
          {
          :1}
          已經更新到新的map了。

          4、swap!:如果你的更新需要依賴當前的狀態值,或者只想更新狀態的某個部分,那么就需要使用swap!(類似alter):
          (swap! an-atom f & args)

          swap! 將函數f作用于當前狀態值和額外的參數args之上,形成新的狀態值,例如我們給mem加上一個keyword:
          user=> (swap! mem assoc :2)
          {
          :2, :1}

          看到,:b 2被加入了當前的map。

          5、compare and set:
          類似原子變量AtomicInteger之類,atom也可以做compare and set的操作:
          (compare-and-set! atom oldValue newValue)

          當且僅當atom的當前狀態值等于oldValue的時候,將狀態值更新為newValue,并返回一個布爾值表示成功或者失敗:
          user=> (def c (atom 1))
          #'user/c
          user=> (compare-and-set! c 2 3)
          false
          user
          => (compare-and-set! c 1 3)
          true
          user
          => @c
          3


          6、緩存和atom:
          (1)atom非常適合實現緩存,緩存通常不會跟其他系統狀態形成依賴,并且緩存對讀的速度要求更高。上面例子中用到的mem其實就是個簡單的緩存例子,我們來實現一個putm和getm函數:
          ;;創建緩存
          (defn make
          -cache [] (atom {}))

          ;;放入緩存
          (defn putm [cache key value] (swap
          ! cache assoc key value))

          ;;取出
          (defn getm [cache key] (key 
          @cache))


             這里key要求是keyword,keyword是類似:a這樣的字符序列,你熟悉ruby的話,可以暫時理解成symbol。使用這些API:
          user=> (def cache (make-cache))
          #'user/cache
          user=> (putm cache :1)
          {
          :1}
          user
          => (getm cache :a)
          1
          user
          => (putm cache :2)
          {
          :2, :1}
          user
          => (getm cache :b)
          2

          (2)memoize函數作用于函數f,產生一個新函數,新函數內部保存了一個緩存,緩存從參數到結果的映射。第一次調用的時候,發現緩存沒有,就會調用f去計算實際的結果,并放入內部的緩存;下次調用同樣的參數的時候,就直接從緩存中取,而不用再次調用f,從而達到提升計算效率的目的。
          memoize的實現就是基于atom,查看源碼:
          (defn memoize
            [f]
            (let [mem (atom {})]
              (fn [
          & args]
                (
          if-let [e (find @mem args)]
                  (val e)
                  (let [ret (apply f args)]
                    (swap
          ! mem assoc args ret)
                    ret)))))

          內部的緩存名為mem,memoize返回的是一個匿名函數,它接收原有的f函數的參數,if-let判斷綁定的變量e是否存在,變量e是通過find從緩存中查詢args得到的項,如果存在的話,調用val得到真正的結果并返回;如果不存在,那么使用apply函數將f作用于參數列表之上,計算出結果,并利用swap!將結果加入mem緩存,返回計算結果。

          7、性能測試
          使用atom實現一個計數器,和使用java.util.concurrent.AtomicInteger做計數器,做一個性能比較,各啟動100個線程,每個線程執行100萬次原子遞增,計算各自的耗時,測試程序如下,代碼有注釋,不再羅嗦:

          (ns atom-perf)
          (
          import 'java.util.concurrent.atomic.AtomicInteger)
          (import 'java.util.concurrent.CountDownLatch)

          (
          def a (AtomicInteger. 0))
          (
          def b (atom 0))

          ;;為了性能,給java加入type hint
          (defn java
          -inc [#^AtomicInteger counter] (.incrementAndGet counter))
          (defn countdown-latch [#^CountDownLatch latch] (.countDown latch))

          ;;單線程執行緩存次數
          (
          def max_count 1000000)
          ;;線程數 
          (
          def thread_count 100)

          (defn benchmark [fun]
            (let [ latch (CountDownLatch. thread_count)  ;;關卡鎖
                   start (System
          /currentTimeMillis) ]     ;;啟動時間
                 (dotimes [_ thread_count] (.start (Thread. 
          #(do (dotimes [_ max_count] (fun)) (countdown-latch latch))))) 
                 (.await latch)
                 (
          - (System/currentTimeMillis) start)))
                   

          (println 
          "atom:" (benchmark #(swap! b inc)))
          (println "AtomicInteger:" (benchmark #(java-inc a)))

          (println (.get a))
          (println @b)

              默認clojure調用java都是通過反射,加入type hint之后編譯的字節碼就跟java編譯器的一致,為了比較公平,定義了java-inc用于調用AtomicInteger.incrementAndGet方法,定義countdown-latch用于調用CountDownLatch.countDown方法,兩者都為參數添加了type hint。如果不采用type hint,AtomicInteger反射調用的效率是非常低的。

          測試下來,在我的ubuntu上,AtomicInteger還是占優,基本上比atom的實現快上一倍:

          atom: 9002
          AtomicInteger: 
          4185
          100000000
          100000000

          照我的理解,這是由于AtomicInteger調用的是native的方法,基于硬件原語做cas,而atom則是用戶空間內的clojure自己做的CAS,兩者的性能有差距不出意料之外。

          看了源碼,Atom是基于java.util.concurrent.atomic.AtomicReference實現的,調用的方法是
           public final boolean compareAndSet(V expect, V update) {
                  
          return unsafe.compareAndSwapObject(this, valueOffset, expect, update);
              }

          而AtomicInteger調用的方法是:

              public final boolean compareAndSet(int expect, int update) {
              
          return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
              }

          兩者的效率差距有這么大嗎?暫時存疑。
             


          評論

          # re: Clojure的并發(三)Atom、緩存和性能  回復  更多評論   

          2010-07-17 15:23 by clojans
          高手真勤奮呀!^_^

          問問樓主怎樣才能學好這些工作中根本不用的東東呢?樓主學習能力真強,我學來學去只懂點皮毛呀!

          # re: Clojure的并發(三)Atom、緩存和性能  回復  更多評論   

          2010-07-18 00:19 by dennis
          @clojans
          clojure很符合我的胃口,對它熱情多一點。學習新技術的熱情要么純粹是好奇心,要么就給自己一個在工作中使用它的機會。

          # counterstrikeglobaloffensiveaimbot85705  回復  更多評論   

          2015-03-24 12:29 by I'm extremely impressed with your writing skills a
          I'm extremely impressed with your writing skills as well as with the layout on your blog. Is this a paid theme or did you customize it yourself? Either way keep up the nice quality writing, it's rare to see a great blog like this one these days.
          主站蜘蛛池模板: 合水县| 子洲县| 辽阳县| 湘潭县| 泰来县| 望江县| 琼海市| 邢台县| 嵊泗县| 怀集县| 康定县| 米脂县| 买车| 锡林郭勒盟| 泽库县| 法库县| 杭锦旗| 鸡泽县| 健康| 巍山| 蒙自县| 平顶山市| 余干县| 安国市| 开江县| 奉化市| 衡水市| 维西| 青阳县| 应用必备| 林州市| 浪卡子县| 诸城市| 通城县| 黎平县| 苗栗市| 乌兰察布市| 恩平市| 额敏县| 香港| 文水县|