Automatic Fine-Grain Locking using Shape Properties Guy Golan Gueta Nathan Bronson Alex Aiken G. Ramalingam Mooly Sagiv Eran Yahav Tel-Aviv University Stanford University Stanford University Microsoft Research Tel-Aviv University Technion 1 Concurrent Data Structures • Widely used in many software systems • Coarse-grain locking – e.g.: a single lock – easy to implement and understand – often not efficient enough • limited concurrency • Fine-grain locking – high-degree of concurrency – extremely hard to implement and understand 2 Challenge: Automatic Fine-Grain Locking • Automatically add fine-grain locking to sequential code – recursive data structures (e.g. trees, lists) Sequential Code Concurrent Code int GetMax(Node p) { Node c; c = p.right; while (c!=null) { p=c; c=c.right; } int t = p.value; return t; } int GetMax(Node p) { Node c; lock(p); c = p.right; lock(c); while (c!=null) { unlock(p); p=c; c=c.right; if(c!=null) lock(c); } int t = p.value; unlock(p); return t; } 3 Fine-Grain Locking • Each heap object has its own lock Data Structure n1 n2 n4 n3 n5 n6 … … … 4 Fine-Grain Locking • Each heap object has its own lock • A lock is only held while necessary Thread T’ Data Structure n1 n2 Thread T early unlock n4 n3 n5 n6 … … … 5 Adding fine-grain locking is hard • Adding fine-grain locking to pointer based data structures is hard • Need to understand complicated properties • Hard in sequential programs • Harder in the presence of concurrency 6 This paper provides two ideas • Locking protocol for fine-grain locking, Domination Locking – – – – Conditions that guarantees atomicity and deadlock-freedom Can be enforced by programmers Requires only sequential reasoning Generalization of several known locking protocols • Examples: hand-over-hand locking, dynamic DAG locking • Automatic method to enforce the protocol – Assumes that the heap is a forest at the beginning of each operation • Can be checked dynamically – No use of shape analysis – Static + Dynamic: adds conditional lock and unlock statements 7 Domination Locking Protocol 8 Restricted Objects Access • Leverage the restricted way well-typed programs access heap objects – Cannot access n3 without accessing n2 Local Stack Data Structure n1 n2 X n4 n3 n5 n6 … … … 9 Two Types of Objects • Distinguishes between two types of objects: – Exposed objects: “roots” of data structures – Hidden objects: reachable only via exposed objects exposed Data Structure n2 n3 … hidden Local Stack n1 hidden n5 … X n4 hidden hidden n6 … hidden 10 Domination Thread T dominates object u: Paths from exposed objects to u include an object locked by T exposed exposed e1 e2 T dominates h4, h5 T does not dominate h3 Thread T h3 h4 h5 11 Domination Locking Protocol 1. Thread can access object, only when holding its lock exposed e1 Thread T h2 h3 12 Domination Locking Protocol 1. Thread can access object, only when holding its lock 2. Thread can lock hidden object u, only when it dominates u Early unlock is legal Cycle exposed e1 Thread T h2 h3 13 Domination Locking Protocol 1. Thread can access object, only when holding its lock 2. Thread can lock hidden object u, only when it dominates u Early unlock is legal Cycle exposed e1 Thread T h2 h3 h4 Heap graph can be dynamically changed14 Domination Locking Protocol 1. Thread can access object, only when holding its lock 2. Thread can lock hidden object u, only when it dominates u 3. For exposed objects, use variant of two-phase locking that avoids deadlocks Early unlock is legal Cycle exposed e1 Thread T h2 h3 h4 Heap graph can be dynamically changed15 Domination Locking Protocol 1. Thread can access object, only when holding its lock 2. Thread can lock hidden object u, only when it dominates u 3. For exposed objects, use variant of two-phase locking that avoids deadlocks Several exposed objects exposed e1 h2 h3 h4 exposed e5 16 Concurrent Correctness from Sequential Conditions • If every sequential execution satisfies the protocol and is able to terminate → all concurrent executions are linearizable and are able to terminate • Simplifies reasoning • But automatic enforcement in real code with complicated data structures is still not obvious 17 Sequential code boolean remove(Nd par, int key) { Nd n = null; n = par.right; while (n != null && key != n.key) { par = n; n = (key < n.key) ? n.left : n.right; } if (n == null) return false; Nd nL = n.left; Nd nR = n.right; while (true) { Nd bestChild = (nL == null || (nR != null && nR.prio > nL.prio)) ? nR : nL; if (n == par.left) par.left = bestChild; else par.right = bestChild; if (bestChild == null) break; if (bestChild == nL) { // ROTATION n.left = nL.right; nL.right = n; nL = n.left; } else {// ROTATION n.right = nR.left; nR.left = n; nR = n.right; } par = bestChild; } return true; } ? 18 Automatic Locking for Dynamic Forests 19 Dynamic Forest Data Structure • In every sequential execution, shape is a forest at the beginning and end of operations • Example: ListA e1 h2 h3 h4 h6 h7 h8 ListB e5 20 Dynamic Forest Data Structure • In every sequential execution, shape is a forest at the beginning and end of operations • Example: ListA e1 Forest violation h2 h3 Move h3 from ListA to ListB ListB e5 h4 h6 h7 h8 21 How does forestness help? • Guarantees unique ownership at the beginning of every operation • Helps identifying where to safely release locks 22 Automatic Method • Adds code that collects runtime information • Adds code that uses this information for locking and unlocking 23 Runtime Information Two reference counters to every heap object • Stack reference counter – counts number of incoming pointers from local stack (private memory) • Heap reference counter – counts number of incoming pointers from heap objects 24 Runtime Information h4 Local Stack Heap Ref = 0 X Stack Ref = 1 Y exposed Heap Ref = 0 Heap Ref = 2 Heap Ref = 1 Stack Ref = 0 Stack Ref = 0 Stack Ref = 1 e1 h2 h3 25 Locking and Unlocking Locking\Unlocking by using the reference counters • Lock object when – its stack counter becomes positive • Unlock object when – its stack counter becomes 0 – not part of a forest violation (according the heap counter) 26 Multiple Trees void Move(Tree X, Tree Y) { … void Move(Tree X, Tree Y) { { if( address(X) <= address(Y) ) { lock(X); lock(Y); } else { lock(Y); lock(X); } … Code that locks pointed objects in a fixed order 27 Performance Evaluation • Add fine grain locking to several data structures – 2 balanced search trees (Treap, Red-Black Tree) – Self Adjusting Heap (Skew Heap) – 2 specialized data structures (Apriori, and Barnes-Hut) • Runtime experiments show good scalability – No optimization were used (a lot of room for optimizations) 28 Automatic FGL vs. Manual FGL Treap Throughput (ops/msec) Single Manual hand-over-hand Automatic 1800 1600 1400 1200 1000 800 600 400 200 0 1 2 4 Threads 6 8 29 Automatic FGL vs. Manual FGL Apriori Original hand-over-hand (manual) Automatic Normalized Time 120% 100% 80% 60% 40% 20% 0% 1 2 4 Threads 8 30 Summary • A new fine-grain locking protocol for dynamic heaps – Domination Locking • Automatic realization for dynamic forests – Adds fine-grain locking for red-black trees and others – Preliminary Performance Evaluation • scales similarly to hand crafted locking 31 Thank You 32