Slide 1

Slide 1 text

Megarefs And other associative ref types hacks @cgrand http://lambdanext.eu

Slide 2

Slide 2 text

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!

Slide 3

Slide 3 text

A world in an atom

Slide 4

Slide 4 text

Hail the uniform update model! (def a (atom some-big-and-deep-map)) (swap! a update-in path f x y z) ; concatenative feeling...

Slide 5

Slide 5 text

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)

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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))

Slide 8

Slide 8 text

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))))

Slide 9

Slide 9 text

MegaAtom? • swap-in! allows: • bigger values • longer updates • Still a regular Atom • Let’s take the next step: multiple paths!

Slide 10

Slide 10 text

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))

Slide 11

Slide 11 text

(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])

Slide 12

Slide 12 text

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!

Slide 13

Slide 13 text

A world in a ref

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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!

Slide 17

Slide 17 text

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.

Slide 18

Slide 18 text

naive alter-nth (defn alter-nth [r i f & args] (apply alter r update-in [i] f args))

Slide 19

Slide 19 text

indexed-ref (defn indexed-ref [v] (doto (ref v) (alter-meta! assoc ::guards (vec (repeatedly 16 #(ref nil))))))

Slide 20

Slide 20 text

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)))

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

From -nth to -in • Maps? (and vectors, records...) • Just (mod (hash k) (count guards)) • Nested maps? • Glad you asked!

Slide 23

Slide 23 text

Paths • You want independent updates for: • [:a :b] and [:a :c] • You don’t want independent updates for: • [:a] and [:a :b]

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

Ref Guards! :a :b :c :d :e :f Ensured Ref-set

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

Semantics deref-in/ensure-in/commute-in/alter-in/ref- set-in have the same semantics as deref/ensure/commute/alter/ref-set combined with get-in/update-in/assoc-in but they allow for more concurrency

Slide 28

Slide 28 text

Compatibility • megaref provides drop-in replacements for ensure/commute/alter/ref-set • -in variants works both on refs and megarefs • subrefs

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

Knobs • get-options/set-option! • Dynamically tune :guards-count, :check- prefixes (and :min/max-history and :validator)

Slide 31

Slide 31 text

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]]))

Slide 32

Slide 32 text

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))

Slide 33

Slide 33 text

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)

Slide 34

Slide 34 text

Ants brawl!

Slide 35

Slide 35 text

Summary • Megaref • No upfront decision • Comparable to plenty-of-refs (when not CPU bound) • Cheap snapshot without memory footprint

Slide 36

Slide 36 text

Further ideas • Allocation profiling • Tuning the path hash function • more guards for hot paths • self-tuning? • Applying the same principle to agents

Slide 37

Slide 37 text

A world in an agent Caution: wet paint!

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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!

Slide 40

Slide 40 text

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!

Slide 41

Slide 41 text

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]

Slide 42

Slide 42 text

Thanks! https://github.com/cgrand/megaref (MegaAgents not there yet) @cgrand http://lambdanext.eu