scala-miniboxing.org @miniboxing The Story of a Parallel Scala Library The constructs used by miniboxing to optimize your code 12th of September 2016 scala.world Penrith, UK
scala-miniboxing.org @miniboxing Miniboxing Miniboxing ● Compiler transformation ● Improves performance of generics – When instantiated by primitive types ● Introduces optimized constructs – Automatically – Or with programmer help
scala-miniboxing.org @miniboxing Miniboxing Miniboxing ● Compiler transformation ● Improves performance of generics – When instantiated by primitive types ● Introduces optimized constructs – Automatically – Or with programmer help
scala-miniboxing.org @miniboxing Motivation Motivation ● We build cool new constructs and paradigms for { conn getConnection \/> "Could not connect" ← ids conn.get("id") \/> "Coult not get ids" ← } yield user
scala-miniboxing.org @miniboxing Motivation Motivation ● We build cool new constructs and paradigms ● But some constructs are deeply integrated ● Functions ● Tuples ● Arrays ● Ordering ● Numeric for { conn getConnection \/> "Could not connect" ← ids conn.get("id") \/> "Coult not get ids" ← } yield user
scala-miniboxing.org @miniboxing Motivation Motivation val x = (x: Int) => x Function syntax is sugar for anonymous classes extending FunctionX traits (or for Java8 lambdas)
scala-miniboxing.org @miniboxing Motivation Motivation val x = (x: Int) => x Extending Function1 behavior? Sure, add an implicit. Function syntax is sugar for anonymous classes extending FunctionX traits (or for Java8 lambdas)
scala-miniboxing.org @miniboxing Motivation Motivation val x = (x: Int) => x Extending Function1 behavior? Sure, add an implicit. Changing behavior (e.g. PartialFunction)? This is what we'll talk about today. Function syntax is sugar for anonymous classes extending FunctionX traits (or for Java8 lambdas)
scala-miniboxing.org @miniboxing Extending vs Changing Behavior Extending vs Changing Behavior Adding a new method to the trait/class Making it conform to an interface
scala-miniboxing.org @miniboxing Extending vs Changing Behavior Extending vs Changing Behavior Adding a new method to the trait/class Making it conform to an interface Changing a method's signature
scala-miniboxing.org @miniboxing Extending vs Changing Behavior Extending vs Changing Behavior Adding a new method to the trait/class Making it conform to an interface Changing a method's signature Updating the body of a method
scala-miniboxing.org @miniboxing Extending vs Changing Behavior Extending vs Changing Behavior Adding a new method to the trait/class Making it conform to an interface Changing a method's signature Updating the body of a method Changing the fields of a class
scala-miniboxing.org @miniboxing Extending vs Changing Behavior Extending vs Changing Behavior Adding a new method to the trait/class Making it conform to an interface Changing a method's signature Updating the body of a method Changing the fields of a class Extending behavior Changing behavior
scala-miniboxing.org @miniboxing Extending vs Changing Behavior Extending vs Changing Behavior Why would you need that? Adding a new method to the trait/class Making it conform to an interface Changing a method's signature Updating the body of a method Changing the fields of a class Extending behavior Changing behavior
scala-miniboxing.org @miniboxing Erased Generics Erased Generics java.lang.Integer.valueOf(5) boxing is not great ... boxing is not great ... inflates heap requirements produces garbage value is accessed indirectly (slow) breaks locality guarantees
scala-miniboxing.org @miniboxing Specialization Specialization identity(5) identity_I(5) specialization No boxing, it automatically calls the specialized variant for integers
scala-miniboxing.org @miniboxing The idea behind The idea behind ... LONG DOUBLE INT FLOAT SHORT ... LONG DOUBLE INT FLOAT SHORT a long integer miniboxing miniboxing
scala-miniboxing.org @miniboxing Miniboxing Miniboxing def identity[T](t: T): T = t def identity(t: Object): Object = t miniboxing def identity_M(..., t: long): long = t
scala-miniboxing.org @miniboxing Miniboxing Miniboxing def identity[T](t: T): T = t def identity(t: Object): Object = t miniboxing def identity_M(..., t: long): long = t long encodes all primitive types
scala-miniboxing.org @miniboxing Miniboxing Miniboxing def identity[T](t: T): T = t def identity(t: Object): Object = t miniboxing def identity_M(..., t: long): long = t Only 2n variants long encodes all primitive types
scala-miniboxing.org @miniboxing Miniboxing Miniboxing identity(3) identity_M(..., int2minibox(3)) miniboxing The miniboxed variant of identity Unlike boxing, these conversions do not affect performance
scala-miniboxing.org @miniboxing Miniboxed Call Graph Miniboxed Call Graph foo foo_M bar bar_M baz baz_M Data is passed in the miniboxed encoding no boxing necessary!
scala-miniboxing.org @miniboxing Suboptimal Call Graph Suboptimal Call Graph foo foo_M bar baz baz_M no bar_M to call box → not enough info to return to the optimized trace
scala-miniboxing.org @miniboxing Suboptimal Call Graph Suboptimal Call Graph scala> object Test { | def foo[@miniboxed T](t: T): T = bar(t) | def bar[ T](t: T): T = baz(t) | def baz[@miniboxed T](t: T): T = t | } :10: warning: The method Test.bar would benefit from miniboxing type parameter T, since it is instantiated by miniboxed type parameter T of method foo: def foo[@miniboxed T](t: T): T = bar(t) ^ :11: warning: The following code could benefit from miniboxing specialization if the type parameter T of method bar would be marked as "@miniboxed T": def bar[T](t: T): T = baz(t) ^ defined object Test
scala-miniboxing.org @miniboxing Library Interoperation Library Interoperation scala> def identityFun[T] = (t: T) => t defined method identityFun scala> identityFun(3) :11: warning: The method identityFun would benefit from miniboxing type parameter T, since it is instantiated by a primitive type. identityFun(3) ^ res1: Int = 3
scala-miniboxing.org @miniboxing scala> def identityFun[@miniboxed T] = (t: T) => t :12: warning: The type parameter T1 of trait scala.Function1 is specialized but to interoperate efficiently with miniboxing, it would have to be miniboxed. def identityFun[@miniboxed T] = (t: T) => t ^ :12: warning: The type parameter R of trait scala.Function1 is specialized but to interoperate efficiently with miniboxing, it would have to be miniboxed. def identityFun[@miniboxed T] = (t: T) => t ^ defined method identityFun Library Interoperation Library Interoperation
scala-miniboxing.org @miniboxing scala> def identityFun[@miniboxed T] = (t: T) => t :12: warning: The type parameter T1 of trait scala.Function1 is specialized but to interoperate efficiently with miniboxing, it would have to be miniboxed. def identityFun[@miniboxed T] = (t: T) => t ^ :12: warning: The type parameter R of trait scala.Function1 is specialized but to interoperate efficiently with miniboxing, it would have to be miniboxed. def identityFun[@miniboxed T] = (t: T) => t ^ defined method identityFun Library Interoperation Library Interoperation Not actionable
scala-miniboxing.org @miniboxing scala> def identityFun[@miniboxed T] = (t: T) => t :12: warning: The type parameter T1 of trait scala.Function1 is specialized but to interoperate efficiently with miniboxing, it would have to be miniboxed. def identityFun[@miniboxed T] = (t: T) => t ^ :12: warning: The type parameter R of trait scala.Function1 is specialized but to interoperate efficiently with miniboxing, it would have to be miniboxed. def identityFun[@miniboxed T] = (t: T) => t ^ defined method identityFun Library Interoperation Library Interoperation Not actionable Need to fix it
scala-miniboxing.org @miniboxing scala> def identityFun[@miniboxed T] = (t: T) => t :12: warning: The type parameter T1 of trait scala.Function1 is specialized but to interoperate efficiently with miniboxing, it would have to be miniboxed. def identityFun[@miniboxed T] = (t: T) => t ^ :12: warning: The type parameter R of trait scala.Function1 is specialized but to interoperate efficiently with miniboxing, it would have to be miniboxed. def identityFun[@miniboxed T] = (t: T) => t ^ defined method identityFun Library Interoperation Library Interoperation Not actionable Need to fix it
scala-miniboxing.org @miniboxing Library Interoperation Library Interoperation ● Multiple such constructs – Functions automatic, most interesting → – Arrays need user intervention, warnings → – Tuples automatic → – Numeric automatic, but I was lazy →
scala-miniboxing.org @miniboxing MbFunctionX MbFunctionX ● Miniboxed replacement for FunctionX ● Assumptions – Function created once – Called multiple times optimize for calling → overhead
scala-miniboxing.org @miniboxing MbFunctionX MbFunctionX ● Miniboxed replacement for FunctionX ● Assumptions – Function created once – Called multiple times optimize for calling → overhead Let's see how we transform
scala-miniboxing.org @miniboxing Library Interoperation Library Interoperation scala> def identityFun[@miniboxed T] = (t: T) => t defined method identityFun def identityFun[@miniboxed T]: MbFunction1[T, T] = ... Once this is done, miniboxing does its magic
scala-miniboxing.org @miniboxing Library Interoperation Library Interoperation ● Multiple such constructs – Functions automatic, most interesting → – Arrays need user intervention, warnings → – Tuples automatic → – Numeric automatic, but I was lazy →
scala-miniboxing.org @miniboxing Extending vs Changing Behavior Extending vs Changing Behavior Adding a new method to the trait/class Making it conform to an interface Changing a method's signature Updating the body of a method Changing the fields of a class Extending behavior Changing behavior
scala-miniboxing.org @miniboxing Extending vs Changing Behavior Extending vs Changing Behavior Adding a new method to the trait/class Making it conform to an interface Changing a method's signature Updating the body of a method Changing the fields of a class Extending behavior Changing behavior Completely replacing a library class
scala-miniboxing.org @miniboxing Dependencies Dependencies Your library Scala stdlib Other library Other library dependencies Each library is free to use its desired FunctionX version
scala-miniboxing.org @miniboxing Dependencies Dependencies Your library Scala stdlib Other library Other library dependencies Each library is free to use its desired FunctionX version Talk to each in its own "language"
scala-miniboxing.org @miniboxing Dependencies Dependencies Your library Scala stdlib Other library Other library dependencies Each library is free to use its desired FunctionX version Talk to each in its own "language" Have to track of the transformations
scala-miniboxing.org @miniboxing Marking Changes Marking Changes ● Keep track of changes to – Methods – Fields – Classes – Traits ● Carry transformation info with types – Automatically annotate the code – The pickler phase stores them in signatures – Introduce conversions where necessary
scala-miniboxing.org @miniboxing Conversions Conversions ● What do to if – We have an MbFunction1 and – Callee expects Function1? ● Conversions – Between the two cases – Functions defined in miniboxed code ● Miniboxing-friendly and specialization-friendly – Functions defined in non-miniboxed code ● Specialization-friendly ● Need conversion Miniboxing friendly →
scala-miniboxing.org @miniboxing Reverse Dependencies Reverse Dependencies Your library Scala stdlib Other library Other library Other lib 1 Other lib 2 Other lib 3 dependencies reverse dependencies
scala-miniboxing.org @miniboxing Reverse Dependencies Reverse Dependencies Your library Scala stdlib Other library Other library Other lib 1 Other lib 2 Other lib 3 dependencies reverse dependencies
scala-miniboxing.org @miniboxing Reverse Dependencies Reverse Dependencies Your library Scala stdlib Other library Other library Other lib 1 Other lib 2 Other lib 3 dependencies reverse dependencies Libraries not compiled with the miniboxing plugin are not aware of the transformation → incorrect bytecode
scala-miniboxing.org @miniboxing Reverse Dependencies Reverse Dependencies $ scala Welcome to Scala version 2.11.7 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_101). Type in expressions to have them evaluated. Type :help for more information. scala> Test.identityFun(2) :11: error: This method was compiled with the miniboxing plugin and can only be called by code also compiled with the miniboxing plugin (see scala-miniboxing.org/compatibility) [mbox=0.4-M8,scala=2.11.7] Test.identityFun(2) ^
scala-miniboxing.org @miniboxing Reverse Dependencies Reverse Dependencies miniboxing introduces annotation here ... and removes it just before refchecks
scala-miniboxing.org @miniboxing Object Model Object Model ● Allows "hiding" miniboxed code – Under an erased interface (example) – Under a specialized interface ● Pro: no dependency on the plugin downstream ● Cons: have to write some glue code
scala-miniboxing.org @miniboxing RRBVector Experiment RRBVector Experiment ● RRBVector is an immutable vector data structure – With constant-time split and merge (for parallel ops) ● Idea by Phil Bagwell and Tiark Rompf ● Implementation by Nicolas Stucki – 4 developer-weeks of work – Hand-tuned for maximum performance – Scaling almost linearly with the number of cores – Summing a vector is 2x as slow as summing an array ● Presented at ICFP '15
scala-miniboxing.org @miniboxing RRBVector Experiment RRBVector Experiment ● Questions: – How difficult is it to specialize RRBVector with miniboxing? – What are the speedups obtained by miniboxing? ● Preparation: – Copied the Scala stdlib suppor necessary for RRBVector (3k LOC) – Eliminated the parallel execution support (3k 2k LOC) → – Prepared benchmarks ● Subject: Milos Stojanovic – Master student working in the lab – Experienced with miniboxing – Never saw the RRBVector code base before
scala-miniboxing.org @miniboxing RRBVector Experiment RRBVector Experiment ● It took 30 minutes to optimize the code base by following the miniboxing warnings → 0.3% of the development time Benchmark Microbenchmarks (5M elements) Macrobenchmark Builder map fold reverse* Least squares linear regression (5M points) Erased (seconds) 43.2 103.0 94.1 31.4 4864.6 Miniboxed (seconds) 22.9 60.4 42.1 35.3 1818.1 Speedup (ratio) 1.88x 1.71x 2.24x 0.89x 2.68x * reversing a vector does not perform any operations that could benefit from miniboxing.
scala-miniboxing.org @miniboxing Miniboxed Library Miniboxed Library ● Replaces deeply integrated Scala constructs – Preserve the syntactic sugar – No source code changes ● Scalac/miniboxing does a lot of the work ● Use cases – Miniboxing: performance –
scala-miniboxing.org @miniboxing scala-miniboxing.org Special thanks: Alexandru Nedelcu Milos Stojanovic Romain Beguet Nicolas Stucki Scala team @ EPFL