Self-Adjusting Stack Machines Matthew A. Hammer Georg Neis Yan Chen Umut A. Acar Max Planck Institute for Software Systems OOPSLA 2011 — October 27, 2011 Portland, Oregon, USA Static Computation Versus Dynamic Computation Static Computation: Fixed Input Compute Fixed Output Dynamic Computation: Changing Input Read Changes Matthew A. Hammer Compute Update Self-Adjusting Stack Machines Changing Output Write Updates 2 Dynamic Data is Everywhere Software systems often consume/produce dynamic data Reactive Systems Scientific Simulation Matthew A. Hammer Analysis of Internet data Self-Adjusting Stack Machines 3 Tractability Requires Dynamic Computations Changing Input Compute Changing Output Static Case (Re-evaluation “from scratch”) compute # of changes Total time Matthew A. Hammer 1 sec 1 million 11.6 days Self-Adjusting Stack Machines 4 Tractability Requires Dynamic Computations Changing Input Read Changes Compute compute # of changes Total time Matthew A. Hammer Write Updates Update Static Case (Re-evaluation “from scratch”) 1 sec 1 million 11.6 days Changing Output Dynamic Case (Uses update mechanism) compute update # of changes Total time Speedup Self-Adjusting Stack Machines 10 sec 1 × 10−3 sec 1 million 16.7 minutes 1000x 4 Dynamic Computations can be Hand-Crafted As an input sequence changes, maintain a sorted output. Changing Input Changing Output 1,7,3,6,5,2,4 compute 1,2,3,4,5,6,7 Remove 6 1,7,3,6/,5,2,4 update 1,2,3,4,5,6/,7 Reinsert 6, Remove 2 1,7,3,6,5,2/,4 update 1,2/,3,4,5,6,7 A binary search tree would suffice here (e.g., a splay tree) What about more exotic/complex computations? Matthew A. Hammer Self-Adjusting Stack Machines 5 How to Program Dynamic Computations? Can this programming be systematic? What are the right abstractions? 1. How to describe dynamic computations? I I Usability: Are these descriptions easy to write? Generality: How much can they describe? 2. How to implement these descriptions? I I Efficiency: Are updates faster than re-evaluation? Consistency: Do updates provide the correct result? Matthew A. Hammer Self-Adjusting Stack Machines 6 In Self-Adjusting Computation, Ordinary programs describe dynamic computations. C Source Compiler C Target + Run-time Self-Adjusting Program The self-adjusting program: 1. Computes initial output from initial input 2. Automatically updates output when input changes Matthew A. Hammer Self-Adjusting Stack Machines 7 Self-Adjusting Programs Input Read Changes Read Compute Trace Write Output Write Updates Update I I Self-adjusting program maintains dynamic dependencies in an execution trace. Key Idea: Reusing traces efficient update Matthew A. Hammer Self-Adjusting Stack Machines 8 Challenges Existing work targets functional languages: I Library support for SML and Haskell I DeltaML extends MLton SML compiler Our work targets low-level languages (e.g., C) I stack-based I imperative I no strong type system I no automatic memory management Matthew A. Hammer Self-Adjusting Stack Machines 9 Challenges Low-Level Self-Adj. Computation Efficient update complex resource interactions: I execution trace, call stack, memory manager Input Read Changes Read Compute Trace Write Output Write Updates Update Matthew A. Hammer Self-Adjusting Stack Machines 10 Challenges Low-Level Self-Adj. Computation Efficient update complex resource interactions: I execution trace, call stack, memory manager Input Read Changes Read Compute Trace Write Output Write Updates Update Matthew A. Hammer Self-Adjusting Stack Machines 10 Challenges Low-Level Self-Adj. Computation Efficient update complex resource interactions: I execution trace, call stack, memory manager make new trace, search old trace code revaluation found change found match change propagation Matthew A. Hammer repair + edit old trace Self-Adjusting Stack Machines 10 Example: Dynamic Expression Trees Objective: As tree changes, maintain its valuation + + − + 3 − 0 5 − + 6 4 3 ((3 + 4) − 0) + (5 − 6) = 6 Matthew A. Hammer + − 0 4 5 5 6 ((3+4)−0)+((5−6)+5) = 11 Self-Adjusting Stack Machines 11 Example: Dynamic Expression Trees Objective: As tree changes, maintain its valuation + + − + 3 − 0 5 − + 6 4 3 ((3 + 4) − 0) + (5 − 6) = 6 + − 0 4 5 5 6 ((3+4)−0)+((5−6)+5) = 11 Consistency: Output is correct valuation Efficiency: Update time is O(#affected intermediate results) Matthew A. Hammer Self-Adjusting Stack Machines 11 Expression Tree Evaluation in C 1 2 3 4 5 6 7 8 typedef struct node s* node t; struct node s { enum { LEAF, BINOP } tag; union { int leaf; struct { enum { PLUS, MINUS } op; node t left, right; } binop; } u; } 1 2 3 4 5 6 7 8 9 int eval (node t root) { if (root->tag == LEAF) return root->u.leaf; else { int l = eval (root->u.binop.left); int r = eval (root->u.binop.right); if (root->u.binop.op == PLUS) return (l + r); else return (l - r); } } Matthew A. Hammer Self-Adjusting Stack Machines 12 The Stack “Shapes” the Computation int eval (node t root) { if (root->tag == LEAF) return root->u.leaf; else { int l = eval (root->u.binop.left); int r = eval (root->u.binop.right); if (root->u.binop.op == PLUS) return (l + r); else return (l - r); } } Stack usage breaks computation into three parts: Matthew A. Hammer Self-Adjusting Stack Machines 13 The Stack “Shapes” the Computation int eval (node t root) { if (root->tag == LEAF) return root->u.leaf; else { int l = eval (root->u.binop.left); int r = eval (root->u.binop.right); if (root->u.binop.op == PLUS) return (l + r); else return (l - r); } } Stack usage breaks computation into three parts: I Part A: Return value if LEAF Otherwise, evaluate BINOP, starting with left child Matthew A. Hammer Self-Adjusting Stack Machines 13 The Stack “Shapes” the Computation int eval (node t root) { if (root->tag == LEAF) return root->u.leaf; else { int l = eval (root->u.binop.left); int r = eval (root->u.binop.right); if (root->u.binop.op == PLUS) return (l + r); else return (l - r); } } Stack usage breaks computation into three parts: I Part A: Return value if LEAF Otherwise, evaluate BINOP, starting with left child I Part B: Evaluate the right child Matthew A. Hammer Self-Adjusting Stack Machines 13 The Stack “Shapes” the Computation int eval (node t root) { if (root->tag == LEAF) return root->u.leaf; else { int l = eval (root->u.binop.left); int r = eval (root->u.binop.right); if (root->u.binop.op == PLUS) return (l + r); else return (l - r); } } Stack usage breaks computation into three parts: I Part A: Return value if LEAF Otherwise, evaluate BINOP, starting with left child I Part B: Evaluate the right child I Part C: Apply BINOP to intermediate results; return Matthew A. Hammer Self-Adjusting Stack Machines 13 Dynamic Execution Traces Input Tree + − + 3 − 0 5 6 4 Execution Trace A+ B+ A− B− A+ B+ A3 A4 Matthew A. Hammer C+ C− A0 Self-Adjusting Stack Machines C+ A− B− A5 A6 C− 14 How to Update the Output? Original Input + − + 3 Changed Input + − 0 5 − + 6 4 3 + − 0 4 5 5 6 Goals: I Consistency: Respect the (static) program’s meaning I Efficiency: Reuse original computation when possible Matthew A. Hammer Self-Adjusting Stack Machines 15 How to Update the Output? Original Input + − + 3 Changed Input + − 0 5 − + 6 4 3 + − 0 4 5 5 6 Goals: I Consistency: Respect the (static) program’s meaning I Efficiency: Reuse original computation when possible Idea: Transform the first trace into second trace Matthew A. Hammer Self-Adjusting Stack Machines 15 + − + 3 + − 0 4 5 5 6 Affected/Re-eval Affected/Re-eval A+ B+ A− B− A+ B+ A3 A4 C+ A0 Unaffected/Reuse Matthew A. Hammer C− C+ A+ B+ A− B− A5 A6 C− C+ A5 New Evaluation Unaffected/Reuse Self-Adjusting Stack Machines 16 Before Update A+ A− B+ B− A+ B+ A3 A4 C+ C− A0 After Update A+ A− B+ A3 A4 C+ Matthew A. Hammer A− B− A5 A6 C− B+ B− A+ C+ A0 C− C+ A+ B+ A− B− A5 A6 Self-Adjusting Stack Machines C− C+ A5 17 How to Program Dynamic Computations? 1. How to describe dynamic computations? X Usability: Are these descriptions easy to write? X Generality: How much can they describe? 2. How to implement this description? ? Correctness: Do updates provide the correct result? ? Efficiency: Are updates faster than re-evaluation? Matthew A. Hammer Self-Adjusting Stack Machines 18 Overview of Formal Semantics I I IL: Intermediate language for C-like programs IL has instructions for: I I I Mutable memory: alloc, read, write Managing local state via a stack: push, pop Saving/restoring local state: memo, update Matthew A. Hammer Self-Adjusting Stack Machines 19 Overview of Formal Semantics I I IL: Intermediate language for C-like programs IL has instructions for: I I I I Transition semantics: two abstract stack machines: I I I Mutable memory: alloc, read, write Managing local state via a stack: push, pop Saving/restoring local state: memo, update Reference machine: defines “normal” semantics Tracing machine: defines self-adjusting semantics Can compute an output and a trace Can update output/trace when memory changes Automatically marks garbage in memory We prove that these stack machines are consistent Matthew A. Hammer Self-Adjusting Stack Machines 19 Consistency theorem, Part 1: No Reuse Trace Input Tracing Machine Run (P) q Input Output q Reference Machine Run (P) Output Tracing machine is consistent with reference machine (when tracing machine runs “from-scratch”, with no reuse) Matthew A. Hammer Self-Adjusting Stack Machines 20 Consistency theorem, Part 2: Reuse vs No Reuse Trace0 Input Tracing Machine Run (P) Trace Output q Input q Tracing Machine Run (P) Trace Output Tracing machine is consistent with from-scratch runs (When it reuses some existing trace Trace0 ) Matthew A. Hammer Self-Adjusting Stack Machines 21 Consistency theorem: Main result Trace0 Input Trace Tracing Machine Run (P) q Input Output q Reference Machine Run (P) Output Main result uses Part 1 and Part 2 together: Tracing machine is consistent with reference machine Matthew A. Hammer Self-Adjusting Stack Machines 22 How to Program Dynamic Computations? 1. How to describe dynamic computations? X Usability: Are these descriptions easy to write? X Generality: How much can they describe? 2. How to implement this description? X Correctness: Do updates provide the correct result? ? Efficiency: Are updates faster than re-evaluation? Matthew A. Hammer Self-Adjusting Stack Machines 23 Overview of Our Implementation Translate C Transform IL Translate IL C + RT CEAL Compiler I I Compiler: produces C targets from C-like source code Run-time: maintains traces, performs efficient updates Matthew A. Hammer Self-Adjusting Stack Machines 24 Dynamic Expression Trees: From-Scratch Time Time (s) Exptrees From-Scratch 1.6 1.4 1.2 1.0 0.8 0.6 0.4 0.2 0.0 Self-Adj Static 0 Matthew A. Hammer 250K 500K Input Size Self-Adjusting Stack Machines 750K 25 Dynamic Expression Trees: Ave Update Time Time (ms) Exptrees Ave Update 0.022 0.021 0.020 0.019 0.018 0.017 0.016 0.015 0.014 0.013 0.012 0.011 Self-Adj 250K 500K Input Size Matthew A. Hammer Self-Adjusting Stack Machines 750K 26 Dynamic Expression Trees: Speed up Exptrees Speedup 2.5 x 104 Self-Adj 4 Speedup 2.0 x 10 4 1.5 x 10 4 1.0 x 10 3 5.0 x 10 0 0.0 x 10 Matthew A. Hammer 0 250K 500K 750K Input Size Self-Adjusting Stack Machines 27 Summary of Empirical Results Benchmark N Initial Overhead (Compute / Static) Speed-up (Static / Update) exptrees map reverse filter sum minimum quicksort mergesort quickhull diameter distance 106 106 106 106 106 106 105 105 105 105 105 8.5 18.4 18.4 10.7 9.6 7.7 8.2 7.2 3.7 3.4 3.4 1.4 × 104 3.0 × 104 3.8 × 104 4.9 × 104 1.5 × 103 1.4 × 104 6.9 × 102 7.8 × 102 2.2 × 103 1.8 × 103 7.9 × 102 Matthew A. Hammer Self-Adjusting Stack Machines 28 Our Contributions A consistent self-adjusting semantics for low-level programs Matthew A. Hammer Self-Adjusting Stack Machines 29 Our Contributions A consistent self-adjusting semantics for low-level programs Our abstract machine semantics I Describes trace editing & memory management implementation of run-time system I But requires programs with a particular structure implementation of compiler transformations Matthew A. Hammer Self-Adjusting Stack Machines 29 Our Contributions A consistent self-adjusting semantics for low-level programs Our abstract machine semantics I Describes trace editing & memory management implementation of run-time system I But requires programs with a particular structure implementation of compiler transformations Our intermediate language is low-level, yet abstract I orthogonal annotations for self-adjusting behavior I no type system needed implementation of C front end Matthew A. Hammer Self-Adjusting Stack Machines 29 Thank You! Questions? Self-adjusting computation is a language-based technique to derive dynamic programs from static programs. Summary of contributions: I A self-adjusting semantics for low-level programs. This semantics defines self-adjusting stack machines. I I A compiler and run-time that implement the semantics. A front end that embeds much of C. Matthew A. Hammer Self-Adjusting Stack Machines 30