Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Megarefs

 Megarefs

Slides from my talk at EuroClojure 2013

Christophe Grand

October 15, 2013
Tweet

More Decks by Christophe Grand

Other Decks in Programming

Transcript

  1. I have a problem • I can’t decide how to

    split the application essential state in discrete «boxes» • Changing the state split later on is not painless. • I just want to put the world in a single box!
  2. Hail the uniform update model! (def a (atom some-big-and-deep-map)) (swap!

    a update-in path f x y z) ; concatenative feeling...
  3. swap-in! v0.0.1 (defn swap-in! [a path f & args] (apply

    swap! a update-in path f args)) ; before (swap! a update-in path f x y z) ; after (swap-in! a path f x y z)
  4. Problems • Unrelated calls to swap-in! conflict • Everything (update-in

    and f) is retried • When unrelated only update-in should retry • f should not retry
  5. swap-in! v0.0.2 ; memoize makes retries cheap! (defn swap-in! [a

    path f & args] (apply swap! a update-in path (memoize f) args))
  6. swap-in! v0.0.3+ ; from here only optimization is better ;

    cheaper caching: 1-slot, identity, ; update-in steps, etc. (defn swap-in! [a path f & args] (swap! a update-in path (memoize #(apply f % args))))
  7. MegaAtom? • swap-in! allows: • bigger values • longer updates

    • Still a regular Atom • Let’s take the next step: multiple paths!
  8. Megalomania! ;; update many paths (defn update-ins [m f &

    paths] (reduce-kv assoc-in m (zipmap paths (apply f (map #(get-in m %) paths))))) (defn swap-ins! [a f & paths] (apply swap! a update-ins (memoize f) paths))
  9. (def accounts (atom {:lucy 100 :ethel 200 :fred 300 :ricky

    400})) (defn transfer [amount] (fn [from to] [(- from amount) (+ to amount)])) ;; No retry of the transfer fn! (swap-ins! accounts (transfer 50) [:ricky] [:lucy]) (swap-ins! accounts (transfer 20) [:ethel] [:fred])
  10. Megalomania!!! • swap-ins! is a poor man’s STM • paths

    as an alternative to discrete identities • intriguing idea • let’s try on the STM proper!
  11. Refs • The memoize trick doesn’t work • There’s no

    single pure function for the whole transaction • Take a page from j.u.concurrent’s book: • Lock striping
  12. Lock striping • Variable-sized mutable collection • Fixed number (N)

    of guards (locks) • To have exclusive access to the item ith • lock the (i mod N)th guard! 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2
  13. Ref striping • Variable-sized immutable collection • Stored in a

    single root ref • Fixed number (N) of guards (refs) • To perform actions (ensure, ref-set/alter, commute) on the item ith • perform dummy actions on the (i mod N)th guard! • deref or commute the root ref!
  14. Why not locks? • Locks don’t compose • Can’t do

    in user code • Hacking the STM itself • I failed/abandoned • Key insight: refs as the building blocks of more complex ref types.
  15. ref-striped alter-nth (defn alter-nth [r i f & args] (let

    [guards (-> r meta ::guards) guard (nth guards (mod i (count guards)))] (ref-set guard nil) (apply commute r update-in [i] f args)))
  16. declining -nth deref-nth ensure-nth alter-nth ref-set-nth commute- nth guard root

    f ø ensure ref-set ø deref deref commute commute get-in get-in update-in assoc-in update-in
  17. From -nth to -in • Maps? (and vectors, records...) •

    Just (mod (hash k) (count guards)) • Nested maps? • Glad you asked!
  18. Paths • You want independent updates for: • [:a :b]

    and [:a :c] • You don’t want independent updates for: • [:a] and [:a :b]
  19. Guards! Guards! • One guard by prefix (or path segment*)

    • (guard [:a]), (guard [:a :b]) etc. • prefix guards are ensured • path guard is ref-set *tends to generate more collisions
  20. declining -in deref-in ensure-in alter-in ref-set-in commute- in path guard

    prefix guard root f ø ensure ref-set ø ø ensure ensure ø deref deref commute commute get-in get-in update-in assoc-in update-in
  21. Subrefs • A mutable view on a part of a

    bigger ref • (subref parent-ref path) • Allow for easy migration of a codebase • from plenty-of-refs to some-megarefs
  22. Ants: ported (1/3) -(ns ants.core) +(ns ants.core + (:refer-clojure :exclude

    [alter commute ref-set ensure]) + (:use [net.cgrand.megaref + :only [alter commute ref-set ensure megaref subref]]))
  23. Ants: ported (2/3) -;world is a 2d vector of refs

    to cells -(def world - (apply vector - (map (fn [_] - (apply vector (map (fn [_] (ref (struct cell 0 0))) - (range dim)))) - (range dim)))) +;world is a megaref to a 2d vector of cells +(def world + (megaref (vec (repeat dim (vec (repeat dim (struct cell 0 0))))))) -(defn place [[x y]] - (-> world (nth x) (nth y))) +(defn place [xy] + (subref world xy))
  24. Ants: ported (3/3) (defn render [g] - (let [v (dosync

    (apply vector (for [x (range dim) y (range dim)] - @(place [x y])))) + (let [w @world img (new BufferedImage (* scale dim) (* scale dim) (. BufferedImage TYPE_INT_ARGB)) bg (. img (getGraphics))] @@ -274,7 +272,7 @@ (.fillRect 0 0 (. img (getWidth)) (. img (getHeight)))) (dorun (for [x (range dim) y (range dim)] - (render-place bg (v (+ (* x dim) y)) x y))) + (render-place bg (get-in w [x y]) x y))) (doto bg (.setColor (. Color blue)) (.drawRect (* scale home-off) (* scale home-off)
  25. Summary • Megaref • No upfront decision • Comparable to

    plenty-of-refs (when not CPU bound) • Cheap snapshot without memory footprint
  26. Further ideas • Allocation profiling • Tuning the path hash

    function • more guards for hot paths • self-tuning? • Applying the same principle to agents
  27. MegaAgents • Relax the serial constraint: • two actions are

    serialized only when their paths are related • [:a :b] blocks [:a :b :c] (and vice-versa) • [:a :b] and [:a :c] and [:c :d] occurs concurrently
  28. Agent striping • Variable-sized immutable collection • Stored in a

    single root atom • Fixed number (N) of guards (agents) • To perform send f on the item ith • send (fn [_] (swap! root assoc-in [i] (f (get-in path @root)))) to the (i mod N)th guard!
  29. From -nth to -in • Breakthrough by Meikel Brandmeyer: •

    chain sends through guards • Use guards’ values to hold kind of a counter and a queue • It even seems to work!
  30. Agent Guards! :a :b :c :d :e :f Not busy

    Busy [2 []] [2 []] [2 [X]] [0 []] [1 []] [0 []] X Pending update to [:a :b :c]