Scala Collections Performance - Lightbenddownloads.typesafe.com/website/presentations/... ·...

69
Scala Collections Performance GS.com/Engineering March, 2015 Craig Motlin

Transcript of Scala Collections Performance - Lightbenddownloads.typesafe.com/website/presentations/... ·...

Page 1: Scala Collections Performance - Lightbenddownloads.typesafe.com/website/presentations/... · PersistentDataStructures$ val3scalaImmutable: immutable.TreeSet[Int]= 33immutable.TreeSet.empty[Int]++(

Scala Collections Performance GS.com/Engineering March, 2015 Craig Motlin

Page 2: Scala Collections Performance - Lightbenddownloads.typesafe.com/website/presentations/... · PersistentDataStructures$ val3scalaImmutable: immutable.TreeSet[Int]= 33immutable.TreeSet.empty[Int]++(

Who  am  I?  •  Engineer  at  Goldman  Sachs  •  Technical  lead  for  GS  Collec9ons  

– A  replacement  for  the  Java  Collec9ons  Framework  

Page 3: Scala Collections Performance - Lightbenddownloads.typesafe.com/website/presentations/... · PersistentDataStructures$ val3scalaImmutable: immutable.TreeSet[Int]= 33immutable.TreeSet.empty[Int]++(

Goals  •  Scala  programs  ought  to  perform  as  well  as  Java  programs,  but  Scala  is  a  liDle  slower  

Page 4: Scala Collections Performance - Lightbenddownloads.typesafe.com/website/presentations/... · PersistentDataStructures$ val3scalaImmutable: immutable.TreeSet[Int]= 33immutable.TreeSet.empty[Int]++(

Goals  From  RedBlackTree.scala  /*    *  Forcing  direct  fields  access  using  the  @inline  annotation    *  helps  speed  up  various  operations  (especially    *  smallest/greatest  and  update/delete).    *    *  Unfortunately  the  direct  field  access  is  not  guaranteed  to    *  work  (but  works  on  the  current  implementation  of  the  Scala    *  compiler).    *    *  An  alternative  is  to  implement  the  these  classes  using  plain    *  old  Java  code...    */  

Page 5: Scala Collections Performance - Lightbenddownloads.typesafe.com/website/presentations/... · PersistentDataStructures$ val3scalaImmutable: immutable.TreeSet[Int]= 33immutable.TreeSet.empty[Int]++(

Goals  •  Scala  programs  ought  to  perform  as  well  as  Java  programs,  but  Scala  is  a  liDle  slower  

•  Highlight  a  few  performance  problems  that  maDer  to  me  

•  Suggest  fixes,  borrowing  ideas  and  technology  from  GS  Collec9ons  

Page 6: Scala Collections Performance - Lightbenddownloads.typesafe.com/website/presentations/... · PersistentDataStructures$ val3scalaImmutable: immutable.TreeSet[Int]= 33immutable.TreeSet.empty[Int]++(

Goals  GS  Collec9ons  and  Scala’s  Collec9ons  are  similar  •  Mutable  and  immutable  interfaces  with  common  parent  

•  Similar  itera9on  paDerns  at  hierarchy  root  Traversable  and  RichIterable  

•  Lazy  evalua9on  (view,  asLazy)  •  Parallel-­‐lazy  evalua9on  (par,  asParallel)  

Page 7: Scala Collections Performance - Lightbenddownloads.typesafe.com/website/presentations/... · PersistentDataStructures$ val3scalaImmutable: immutable.TreeSet[Int]= 33immutable.TreeSet.empty[Int]++(

Agenda  GS  Collec9ons  and  Scala’s  Collec9ons  are  different  •  Persistent  data  structures  •  Hash  tables  •  Primi9ve  specializa9on  •  Fork-­‐join  

Page 8: Scala Collections Performance - Lightbenddownloads.typesafe.com/website/presentations/... · PersistentDataStructures$ val3scalaImmutable: immutable.TreeSet[Int]= 33immutable.TreeSet.empty[Int]++(

Persistent  Data  Structures  

Page 9: Scala Collections Performance - Lightbenddownloads.typesafe.com/website/presentations/... · PersistentDataStructures$ val3scalaImmutable: immutable.TreeSet[Int]= 33immutable.TreeSet.empty[Int]++(

Persistent  Data  Structures  •  Mutable  collec9ons  are  similar  in  GS  Collec9ons  and  Scala  –  though  we’ll  look  at  some  differences  

•  Immutable  collec9ons  are  quite  different  •  Scala’s  immutable  collec9ons  are  persistent  •  GS  Collec9ons’  immutable  collec9ons  are  not  

Page 10: Scala Collections Performance - Lightbenddownloads.typesafe.com/website/presentations/... · PersistentDataStructures$ val3scalaImmutable: immutable.TreeSet[Int]= 33immutable.TreeSet.empty[Int]++(

Persistent  Data  Structures  Wikipedia:    

In  compu9ng,  a  persistent  data  structure  is  a  data  structure  that  always  preserves  the  previous  version  of  itself  when  it  is  modified.  

Page 11: Scala Collections Performance - Lightbenddownloads.typesafe.com/website/presentations/... · PersistentDataStructures$ val3scalaImmutable: immutable.TreeSet[Int]= 33immutable.TreeSet.empty[Int]++(

Persistent  Data  Structures  Wikipedia:    

In  compu9ng,  a  persistent  data  structure  is  a  data  structure  that  always  preserves  the  previous  version  of  itself  when  it  is  modified.  

Opera9ons  yield  new  updated  structures  when  

they  are  “modified”  

Page 12: Scala Collections Performance - Lightbenddownloads.typesafe.com/website/presentations/... · PersistentDataStructures$ val3scalaImmutable: immutable.TreeSet[Int]= 33immutable.TreeSet.empty[Int]++(

Persistent  Data  Structures  •  Typical  examples:  List  and  Sorted  Set  

Page 13: Scala Collections Performance - Lightbenddownloads.typesafe.com/website/presentations/... · PersistentDataStructures$ val3scalaImmutable: immutable.TreeSet[Int]= 33immutable.TreeSet.empty[Int]++(

Persistent  Data  Structures  •  Typical  examples:  List  and  Sorted  Set  •  “Add”  by  construc9ng  O(log  n)  new  nodes  from  the  leaf  to  a  new  root.  

•  Scala  sorted  set  à  binary  tree  •  GSC  immutable  sorted  set  à  covered  later  

Page 14: Scala Collections Performance - Lightbenddownloads.typesafe.com/website/presentations/... · PersistentDataStructures$ val3scalaImmutable: immutable.TreeSet[Int]= 33immutable.TreeSet.empty[Int]++(

Persistent  Data  Structures  Wikipedia:  

Page 15: Scala Collections Performance - Lightbenddownloads.typesafe.com/website/presentations/... · PersistentDataStructures$ val3scalaImmutable: immutable.TreeSet[Int]= 33immutable.TreeSet.empty[Int]++(

Persistent  Data  Structures  •  Typical  examples:  List  and  Sorted  Set  •  “Prepend”  by  construc9ng  a  new  head  in  O(1)  9me.  LIFO  structure.  

•  Scala  immutable  list  à  linked  list  or  stack  •  GSC  immutable  list  à  array  backed  

Page 16: Scala Collections Performance - Lightbenddownloads.typesafe.com/website/presentations/... · PersistentDataStructures$ val3scalaImmutable: immutable.TreeSet[Int]= 33immutable.TreeSet.empty[Int]++(

Persistent  Data  Structures  •  Extremely  important  in  purely  func9onal  languages    

•  All  collec9ons  are  immutable  •  Must  have  good  run9me  complexity  •  No  one  seems  to  miss  them  in  GS  Collec9ons  

Page 17: Scala Collections Performance - Lightbenddownloads.typesafe.com/website/presentations/... · PersistentDataStructures$ val3scalaImmutable: immutable.TreeSet[Int]= 33immutable.TreeSet.empty[Int]++(

Persistent  Data  Structures  •  Proposal:  Mutable,  Persistent  Immutable,  and  Plain  Immutable  

•  Mutable:  Same  as  always  •  Persistent:  Use  when  you  want  structural  sharing  

•  (Plain)  Immutable:  Use  when  you’re  done  muta9ng  or  when  the  data  never  mutates  

Page 18: Scala Collections Performance - Lightbenddownloads.typesafe.com/website/presentations/... · PersistentDataStructures$ val3scalaImmutable: immutable.TreeSet[Int]= 33immutable.TreeSet.empty[Int]++(

Persistent  Data  Structures  Plain  old  immutable  data  structures  •  Not  persistent  (no  structural  sharing)  •  “Adding”  is  much  slower:  O(n)  •  Speed  benefits  for  everything  else  •  Huge  memory  benefits  

Page 19: Scala Collections Performance - Lightbenddownloads.typesafe.com/website/presentations/... · PersistentDataStructures$ val3scalaImmutable: immutable.TreeSet[Int]= 33immutable.TreeSet.empty[Int]++(

Persistent  Data  Structures  •  Immutable  array-­‐backed  list  •  Immutable    à  trimmed  array  •  No  nodes    à  1/6  memory  •  Array  backed    à  cache  locality,  should  parallelize  well  

Page 20: Scala Collections Performance - Lightbenddownloads.typesafe.com/website/presentations/... · PersistentDataStructures$ val3scalaImmutable: immutable.TreeSet[Int]= 33immutable.TreeSet.empty[Int]++(

Persistent  Data  Structures  

Measured  with  Java  8  64-­‐bit  and  compressed  oops  

0  

5  

10  

15  

20  

25  

30  

Size  (M

B)  

Size  (num  elements)  

Lists:  Size  in  MB  by  number  of  elements  

com.gs.collec9ons.api.list.ImmutableList   scala.collec9on.immutable.List   scala.collec9on.mutable.ArrayBuffer  

Page 21: Scala Collections Performance - Lightbenddownloads.typesafe.com/website/presentations/... · PersistentDataStructures$ val3scalaImmutable: immutable.TreeSet[Int]= 33immutable.TreeSet.empty[Int]++(

Persistent  Data  Structures  Performance  assump9ons:  •  Itera9ng  through  an  array  should  be  faster  than  itera9ng  through  a  linked  list  

•  Linked  lists  won’t  parallelize  well  with  .par  •  No  surprises  in  the  results  –  so  we’ll  skip  github.com/goldmansachs/gs-­‐collec9ons/tree/master/jmh-­‐tests  github.com/goldmansachs/gs-­‐collec9ons/tree/master/memory-­‐tests  

Page 22: Scala Collections Performance - Lightbenddownloads.typesafe.com/website/presentations/... · PersistentDataStructures$ val3scalaImmutable: immutable.TreeSet[Int]= 33immutable.TreeSet.empty[Int]++(

Persistent  Data  Structures  •  Immutable  array-­‐backed  sorted  set  •  Immutable    à  trimmed,  sorted  array  •  No  nodes    à  ~1/8  memory  •  Array  backed    à  cache  locality  

Page 23: Scala Collections Performance - Lightbenddownloads.typesafe.com/website/presentations/... · PersistentDataStructures$ val3scalaImmutable: immutable.TreeSet[Int]= 33immutable.TreeSet.empty[Int]++(

Persistent  Data  Structures  

0  5  10  15  20  25  30  35  40  45  

Size  (M

B)  

Size  (num  elements)  

Sorted  Sets:  Size  in  MB  by  number  of  elements  

com.gs.collec9ons.api.set.sorted.ImmutableSortedSet   com.gs.collec9ons.impl.set.sorted.mutable.TreeSortedSet  

scala.collec9on.immutable.TreeSet   scala.collec9on.mutable.TreeSet  

Page 24: Scala Collections Performance - Lightbenddownloads.typesafe.com/website/presentations/... · PersistentDataStructures$ val3scalaImmutable: immutable.TreeSet[Int]= 33immutable.TreeSet.empty[Int]++(

Persistent  Data  Structures  •  Assump9ons  about  contains:  •  May  be  faster  when  it’s  a  binary  search  in  an  array  (good  cache  locality)  

•  Will  be  about  the  same  in  mutable  /  immutable  tree  (all  nodes  have  same  structure)  

Page 25: Scala Collections Performance - Lightbenddownloads.typesafe.com/website/presentations/... · PersistentDataStructures$ val3scalaImmutable: immutable.TreeSet[Int]= 33immutable.TreeSet.empty[Int]++(

Persistent  Data  Structures  

0  

1  

2  

3  

4  

5  

6  

Immutable   Mutable  

Million  op

s/s  

Sorted  Set  contains()  throughput  (higher  is  beAer)  

GSC   Scala  

Page 26: Scala Collections Performance - Lightbenddownloads.typesafe.com/website/presentations/... · PersistentDataStructures$ val3scalaImmutable: immutable.TreeSet[Int]= 33immutable.TreeSet.empty[Int]++(

Itera9on  

Page 27: Scala Collections Performance - Lightbenddownloads.typesafe.com/website/presentations/... · PersistentDataStructures$ val3scalaImmutable: immutable.TreeSet[Int]= 33immutable.TreeSet.empty[Int]++(

Persistent  Data  Structures  val  scalaImmutable:  immutable.TreeSet[Int]  =      immutable.TreeSet.empty[Int]  ++  (0  to  SIZE)    

def  serial_immutable_scala():  Unit  =  {      val  count:  Int  =  this.scalaImmutable          .view          .filter(each  =>  each  %  10000  !=  0)          .map(String.valueOf)          .map(Integer.valueOf)          .count(each  =>  (each  +  1)  %  10000  !=  0)      if  (count  !=  999800)      {          throw  new  AssertionError      }  }  

Page 28: Scala Collections Performance - Lightbenddownloads.typesafe.com/website/presentations/... · PersistentDataStructures$ val3scalaImmutable: immutable.TreeSet[Int]= 33immutable.TreeSet.empty[Int]++(

Persistent  Data  Structures  val  scalaImmutable:  immutable.TreeSet[Int]  =      immutable.TreeSet.empty[Int]  ++  (0  to  SIZE)    

def  serial_immutable_scala():  Unit  =  {      val  count:  Int  =  this.scalaImmutable          .view          .filter(each  =>  each  %  10000  !=  0)          .map(String.valueOf)          .map(Integer.valueOf)          .count(each  =>  (each  +  1)  %  10000  !=  0)      if  (count  !=  999800)      {          throw  new  AssertionError      }  }  

Lazy  evalua9on  so  we  don’t  create  intermediate  collec9ons  

Page 29: Scala Collections Performance - Lightbenddownloads.typesafe.com/website/presentations/... · PersistentDataStructures$ val3scalaImmutable: immutable.TreeSet[Int]= 33immutable.TreeSet.empty[Int]++(

Persistent  Data  Structures  val  scalaImmutable:  immutable.TreeSet[Int]  =      immutable.TreeSet.empty[Int]  ++  (0  to  SIZE)    

def  serial_immutable_scala():  Unit  =  {      val  count:  Int  =  this.scalaImmutable          .view          .filter(each  =>  each  %  10000  !=  0)          .map(String.valueOf)          .map(Integer.valueOf)          .count(each  =>  (each  +  1)  %  10000  !=  0)      if  (count  !=  999800)      {          throw  new  AssertionError      }  }  

Lazy  evalua9on  so  we  don’t  create  intermediate  collec9ons  

Termina9ng  in  .toList  or  .toBuffer  would  be  

dominated  by  collec9on  crea9on  

Page 30: Scala Collections Performance - Lightbenddownloads.typesafe.com/website/presentations/... · PersistentDataStructures$ val3scalaImmutable: immutable.TreeSet[Int]= 33immutable.TreeSet.empty[Int]++(

Persistent  Data  Structures  private  final  ImmutableSortedSet<Integer>  gscImmutable  =      SortedSets.immutable.withAll(Interval.zeroTo(SIZE));  @Benchmark  public  void  serial_immutable_gsc()  {      int  count  =  this.gscImmutable          .asLazy()          .select(each  -­‐>  each  %  10_000  !=  0)          .collect(String::valueOf)          .collect(Integer::valueOf)          .count(each  -­‐>  (each  +  1)  %  10_000  !=  0);      if  (count  !=  999_800)      {          throw  new  AssertionError();      }  }    

Page 31: Scala Collections Performance - Lightbenddownloads.typesafe.com/website/presentations/... · PersistentDataStructures$ val3scalaImmutable: immutable.TreeSet[Int]= 33immutable.TreeSet.empty[Int]++(

Persistent  Data  Structures  •  Assump9on:  Itera9ng  over  an  array  is  somewhat  faster  

Page 32: Scala Collections Performance - Lightbenddownloads.typesafe.com/website/presentations/... · PersistentDataStructures$ val3scalaImmutable: immutable.TreeSet[Int]= 33immutable.TreeSet.empty[Int]++(

Persistent  Data  Structures  

0  

2  

4  

6  

8  

10  

12  

14  

Serial  /  Immutable   Serial  /  Mutable  

Million  op

s/s  

Sorted  Set  IteraBon  throughput  (higher  is  beAer)  

GSC   Scala  

Page 33: Scala Collections Performance - Lightbenddownloads.typesafe.com/website/presentations/... · PersistentDataStructures$ val3scalaImmutable: immutable.TreeSet[Int]= 33immutable.TreeSet.empty[Int]++(

Persistent  Data  Structures  Next  up,  parallel-­‐lazy  evalua9on    

this.scalaImmutable.view  à  this.scalaImmutable.par    

this.gscImmutable.asLazy()  à  this.gscImmutable.asParallel(this.executorService,  BATCH_SIZE)  

Page 34: Scala Collections Performance - Lightbenddownloads.typesafe.com/website/presentations/... · PersistentDataStructures$ val3scalaImmutable: immutable.TreeSet[Int]= 33immutable.TreeSet.empty[Int]++(

Persistent  Data  Structures  •  Assump9on:  Scala’s  tree  should  parallelize  moderately  well  

•  Assump9on:  GSC’s  array  should  parallelize  very  well  

Page 35: Scala Collections Performance - Lightbenddownloads.typesafe.com/website/presentations/... · PersistentDataStructures$ val3scalaImmutable: immutable.TreeSet[Int]= 33immutable.TreeSet.empty[Int]++(

Persistent  Data  Structures  

Measured  on  an  8  core  Linux  VM  Intel  Xeon  E5-­‐2697  v2  8x  

0  

10  

20  

30  

40  

50  

60  

70  

80  

90  

100  

Serial  /  Immutable   Serial  /  Mutable   Parallel  /  Immutable  

Million  op

s/s  

Sorted  Set  IteraBon  throughput  (higher  is  beAer)  

GSC   Scala  

Page 36: Scala Collections Performance - Lightbenddownloads.typesafe.com/website/presentations/... · PersistentDataStructures$ val3scalaImmutable: immutable.TreeSet[Int]= 33immutable.TreeSet.empty[Int]++(

Persistent  Data  Structures  •  Scala’s  immutable.TreeSet  doesn’t  override  .par,  so  parallel  is  slower  than  serial  

•  Some  tree  opera9ons  (like  filter)  are  hard  to  parallelize  

•  TreeSet.count()  should  be  easy  to  parallelize  using  fork/join  with  some  work  

•  New  assump9on:  Mutable  trees  won’t  parallelize  well  either  

Page 37: Scala Collections Performance - Lightbenddownloads.typesafe.com/website/presentations/... · PersistentDataStructures$ val3scalaImmutable: immutable.TreeSet[Int]= 33immutable.TreeSet.empty[Int]++(

Persistent  Data  Structures  

Measured  on  an  8  core  Linux  VM  Intel  Xeon  E5-­‐2697  v2  8x  

0  

10  

20  

30  

40  

50  

60  

70  

80  

90  

100  

Serial  /  Immutable   Serial  /  Mutable   Parallel  /  Immutable   Parallel  /  Mutable  

Million  op

s/s  

Sorted  Set  IteraBon  throughput  (higher  is  beAer)  

GSC   Scala  

Page 38: Scala Collections Performance - Lightbenddownloads.typesafe.com/website/presentations/... · PersistentDataStructures$ val3scalaImmutable: immutable.TreeSet[Int]= 33immutable.TreeSet.empty[Int]++(

Persistent  Data  Structures  •  GS  Collec9ons  follow  up:  

– TreeSortedSet  delegates  to  java.util.TreeSet.  Write  our  own  tree  and  override  .asParallel()  

•  Scala  follow  up:  – Override  .par  

Page 39: Scala Collections Performance - Lightbenddownloads.typesafe.com/website/presentations/... · PersistentDataStructures$ val3scalaImmutable: immutable.TreeSet[Int]= 33immutable.TreeSet.empty[Int]++(

Persistent  Data  Structures  •  Proposal:  Mutable,  Persistent,  and  Immutable  •  All  three  are  useful  in  different  cases  •  How  common  is  it  to  share  structure,  really?    

Page 40: Scala Collections Performance - Lightbenddownloads.typesafe.com/website/presentations/... · PersistentDataStructures$ val3scalaImmutable: immutable.TreeSet[Int]= 33immutable.TreeSet.empty[Int]++(

Hash  Tables  

Page 41: Scala Collections Performance - Lightbenddownloads.typesafe.com/website/presentations/... · PersistentDataStructures$ val3scalaImmutable: immutable.TreeSet[Int]= 33immutable.TreeSet.empty[Int]++(

Hash  Tables  •  Scala’s  immutable.HashMap  is  a  hash  array  mapped  trie  (persistent  structure)  

•  Wikipedia:  “achieves  almost  hash  table-­‐like  speed  while  using  memory  much  more  economically”  

•  Let’s  see  

Page 42: Scala Collections Performance - Lightbenddownloads.typesafe.com/website/presentations/... · PersistentDataStructures$ val3scalaImmutable: immutable.TreeSet[Int]= 33immutable.TreeSet.empty[Int]++(

Hash  Tables  •  Scala’s  mutable.HashMap  is  backed  by  an  array  of  Entry  pairs  

•  java.util.HashMap.Entry  caches  the  hashcode  •  UnifiedMap<K,  V>  is  backed  by  Object[],  flaDened,  alterna9ng  K  and  V  

•  ImmutableUnifiedMap  is  backed  by  UnifiedMap  

Page 43: Scala Collections Performance - Lightbenddownloads.typesafe.com/website/presentations/... · PersistentDataStructures$ val3scalaImmutable: immutable.TreeSet[Int]= 33immutable.TreeSet.empty[Int]++(

Hash  Tables  

0  10  20  30  40  50  60  70  80  

Size  (M

B)  

Size  (num  elements)  

Maps:  Size  in  MB  by  number  of  elements  

com.gs.collec9ons.api.map.ImmutableMap   com.gs.collec9ons.impl.map.mutable.UnifiedMap  

java.u9l.HashMap   scala.collec9on.immutable.HashMap  

scala.collec9on.mutable.HashMap  

Page 44: Scala Collections Performance - Lightbenddownloads.typesafe.com/website/presentations/... · PersistentDataStructures$ val3scalaImmutable: immutable.TreeSet[Int]= 33immutable.TreeSet.empty[Int]++(

Hash  Tables  @Benchmark  public  void  get()  {      int  localSize  =  this.size;      String[]  localElements  =  this.elements;      Map<String,  String>  localScalaMap  =  this.scalaMap;    

   for  (int  i  =  0;  i  <  localSize;  i++)      {          if  (!localScalaMap.get(localElements[i]).isDefined())          {              throw  new  AssertionError(i);          }      }  }    

Page 45: Scala Collections Performance - Lightbenddownloads.typesafe.com/website/presentations/... · PersistentDataStructures$ val3scalaImmutable: immutable.TreeSet[Int]= 33immutable.TreeSet.empty[Int]++(

Hash  Tables  

0  

5  

10  

15  

20  

25  

30  

35  

0   1000000   2000000   3000000   4000000   5000000   6000000   7000000   8000000   9000000   10000000  

Million  op

s/s  

Size  (num  elements)  

Map  get()  throughput  by  map  size  (higher  is  beAer)  

GscImmutableMapGetTest.get   GscMutableMapGetTest.get   ScalaImmutableMapGetTest.get   ScalaMutableMapGetTest.get  

Page 46: Scala Collections Performance - Lightbenddownloads.typesafe.com/website/presentations/... · PersistentDataStructures$ val3scalaImmutable: immutable.TreeSet[Int]= 33immutable.TreeSet.empty[Int]++(

Hash  Tables  

0  

5  

10  

15  

20  

25  

30  

0   1000000   2000000   3000000   4000000   5000000   6000000   7000000   8000000   9000000   10000000  

Millions  ops/s  

Size  (num  elements)  

Map  put()  throughtput  by  map  size  (higher  is  beAer)  

com.gs.collec9ons.impl.map.mutable.UnifiedMap   scala.collec9on.immutable.Map   scala.collec9on.mutable.HashMap  

Page 47: Scala Collections Performance - Lightbenddownloads.typesafe.com/website/presentations/... · PersistentDataStructures$ val3scalaImmutable: immutable.TreeSet[Int]= 33immutable.TreeSet.empty[Int]++(

Hash  Tables  @Benchmark  public  mutable.HashMap<String,  String>  mutableScalaPut()  {          int  localSize  =  this.size;          String[]  localElements  =  this.elements;    

             mutable.HashMap<String,  String>  map  =              new  PresizableHashMap<>(localSize);    

       for  (int  i  =  0;  i  <  localSize;  i++)          {                  map.put(localElements[i],  "dummy");          }          return  map;  }  

Page 48: Scala Collections Performance - Lightbenddownloads.typesafe.com/website/presentations/... · PersistentDataStructures$ val3scalaImmutable: immutable.TreeSet[Int]= 33immutable.TreeSet.empty[Int]++(

Hash  Tables  HashMap  ought  to  have  a  constructor  for  pre-­‐sizing    

class  PresizableHashMap[K,  V](val  _initialSize:  Int)      extends  scala.collection.mutable.HashMap[K,  V]  {      private  def  initialCapacity  =            if  (_initialSize  ==  0)  1          else  smallestPowerOfTwoGreaterThan(              (_initialSize.toLong  *  1000  /  _loadFactor).asInstanceOf[Int])    

       private  def  smallestPowerOfTwoGreaterThan(n:  Int):  Int  =              if  (n  >  1)  Integer.highestOneBit(n  -­‐  1)  <<  1  else  1    

       table  =  new  Array(initialCapacity)          threshold  =  ((initialCapacity.toLong  *  _loadFactor)  /  1000).toInt  }  

Page 49: Scala Collections Performance - Lightbenddownloads.typesafe.com/website/presentations/... · PersistentDataStructures$ val3scalaImmutable: immutable.TreeSet[Int]= 33immutable.TreeSet.empty[Int]++(

Hash  Tables  •  scala.collection.mutable.HashSet  is  backed  by  an  array  using  open-­‐addressing  

•  java.util.HashSet  is  implemented  by  delega9ng  to  a  HashMap.  

•  UnifiedSet  is  backed  by  Object[],  either  elements  or  arrays  of  collisions  

•  ImmutableUnifiedSet  is  backed  by  UnifiedSet  

Page 50: Scala Collections Performance - Lightbenddownloads.typesafe.com/website/presentations/... · PersistentDataStructures$ val3scalaImmutable: immutable.TreeSet[Int]= 33immutable.TreeSet.empty[Int]++(

Hash  Tables  

0  5  10  15  20  25  30  35  40  45  

Size  (M

B)  

Size  (num  elements)  

Sets:  Size  in  MB  by  number  of  elements  

com.gs.collec9ons.api.set.ImmutableSet   com.gs.collec9ons.impl.set.mutable.UnifiedSet  

java.u9l.HashSet   scala.collec9on.immutable.HashSet  

scala.collec9on.mutable.HashSet  

Page 51: Scala Collections Performance - Lightbenddownloads.typesafe.com/website/presentations/... · PersistentDataStructures$ val3scalaImmutable: immutable.TreeSet[Int]= 33immutable.TreeSet.empty[Int]++(

Primi9ve  Specializa9on  

Page 52: Scala Collections Performance - Lightbenddownloads.typesafe.com/website/presentations/... · PersistentDataStructures$ val3scalaImmutable: immutable.TreeSet[Int]= 33immutable.TreeSet.empty[Int]++(

Primi9ve  Specializa9on  •  Boxing  is  expensive  

– Reference  +  Header  +  alignment  •  Scala  has  specializa9on,  but  most  of  the  collec9ons  are  not  specialized  

•  If  you  cannot  afford  wrappers  you  can:  –  Use  primi9ve  arrays  (only  for  lists)  –  Use  a  Java  Collec9ons  library  

Page 53: Scala Collections Performance - Lightbenddownloads.typesafe.com/website/presentations/... · PersistentDataStructures$ val3scalaImmutable: immutable.TreeSet[Int]= 33immutable.TreeSet.empty[Int]++(

Primi9ve  Specializa9on  

0  

5  

10  

15  

20  

25  

30  

35  

Size  (M

B)  

Size  (num  elements)  

Sets:  Size  in  MB  by  number  of  elements  

com.gs.collec9ons.impl.set.mutable.primi9ve.IntHashSet   com.gs.collec9ons.impl.set.mutable.UnifiedSet  

scala.collec9on.mutable.HashSet  

Page 54: Scala Collections Performance - Lightbenddownloads.typesafe.com/website/presentations/... · PersistentDataStructures$ val3scalaImmutable: immutable.TreeSet[Int]= 33immutable.TreeSet.empty[Int]++(

Primi9ve  Specializa9on  •  Proposal:  Primi9ve  lists,  sets,  and  maps  in  Scala  

•  Not  Traversable  –  outside  the  collec9ons  hierarchy  

•  Fix  Specializa9on  on  Func9ons  (lambdas)  – Want  for-­‐comprehensions  to  work  well  

Page 55: Scala Collections Performance - Lightbenddownloads.typesafe.com/website/presentations/... · PersistentDataStructures$ val3scalaImmutable: immutable.TreeSet[Int]= 33immutable.TreeSet.empty[Int]++(

Fork/Join  

Page 56: Scala Collections Performance - Lightbenddownloads.typesafe.com/website/presentations/... · PersistentDataStructures$ val3scalaImmutable: immutable.TreeSet[Int]= 33immutable.TreeSet.empty[Int]++(

Parallel  /  Lazy  /  Scala  val  list  =  this.integers      .par      .filter(each  =>  each  %  10000  !=  0)      .map(String.valueOf)      .map(Integer.valueOf)      .filter(each  =>  (each  +  1)  %  10000  !=  0)      .toBuffer    Assert.assertEquals(999800,  list.size)    

Page 57: Scala Collections Performance - Lightbenddownloads.typesafe.com/website/presentations/... · PersistentDataStructures$ val3scalaImmutable: immutable.TreeSet[Int]= 33immutable.TreeSet.empty[Int]++(

Parallel  /  Lazy  /  GSC  MutableList<Integer>  list  =  this.integersGSC      .asParallel(this.executorService,  BATCH_SIZE)      .select(each  -­‐>  each  %  10_000  !=  0)      .collect(String::valueOf)      .collect(Integer::valueOf)      .select(each  -­‐>  (each  +  1)  %  10_000  !=  0)      .toList();    Verify.assertSize(999_800,  list);    

Page 58: Scala Collections Performance - Lightbenddownloads.typesafe.com/website/presentations/... · PersistentDataStructures$ val3scalaImmutable: immutable.TreeSet[Int]= 33immutable.TreeSet.empty[Int]++(

Parallel  /  Lazy  /  JDK  List<Integer>  list  =  this.integersJDK      .parallelStream()      .filter(each  -­‐>  each  %  10_000  !=  0)      .map(String::valueOf)      .map(Integer::valueOf)      .filter(each  -­‐>  (each  +  1)  %  10_000  !=  0)      .collect(Collectors.toList());    Verify.assertSize(999_800,  list);  

Page 59: Scala Collections Performance - Lightbenddownloads.typesafe.com/website/presentations/... · PersistentDataStructures$ val3scalaImmutable: immutable.TreeSet[Int]= 33immutable.TreeSet.empty[Int]++(

Stacked  computa9on  ops/s  (higher  is  beDer)  

0  

10  

20  

30  

40  

50  

60  

Serial  Lazy   Parallel  Lazy  

Java  8   GS  Collec9ons   Scala  8x  

Page 60: Scala Collections Performance - Lightbenddownloads.typesafe.com/website/presentations/... · PersistentDataStructures$ val3scalaImmutable: immutable.TreeSet[Int]= 33immutable.TreeSet.empty[Int]++(

Fork-­‐Join  Merge  •  Intermediate  results  are  merged  in  a  tree  •  Merging  is  O(n  log  n)  work  and  garbage  

Page 61: Scala Collections Performance - Lightbenddownloads.typesafe.com/website/presentations/... · PersistentDataStructures$ val3scalaImmutable: immutable.TreeSet[Int]= 33immutable.TreeSet.empty[Int]++(

Fork-­‐Join  Merge  •  Amount  of  work  done  by  last  thread  is  O(n)  

Page 62: Scala Collections Performance - Lightbenddownloads.typesafe.com/website/presentations/... · PersistentDataStructures$ val3scalaImmutable: immutable.TreeSet[Int]= 33immutable.TreeSet.empty[Int]++(

Parallel  /  Lazy  /  GSC  MutableList<Integer>  list  =  this.integersGSC      .asParallel(this.executorService,  BATCH_SIZE)      .select(each  -­‐>  each  %  10_000  !=  0)      .collect(String::valueOf)      .collect(Integer::valueOf)      .select(each  -­‐>  (each  +  1)  %  10_000  !=  0)      .toList();    Verify.assertSize(999_800,  list);    

ParallelIterable.toList()  returns  a  

CompositeFastList,  a  List  with  O(1)  implementa9on  

of  addAll()  

Page 63: Scala Collections Performance - Lightbenddownloads.typesafe.com/website/presentations/... · PersistentDataStructures$ val3scalaImmutable: immutable.TreeSet[Int]= 33immutable.TreeSet.empty[Int]++(

Parallel  /  Lazy  /  GSC  public  final  class  CompositeFastList<E>  {      private  final  FastList<FastList<E>>  lists  =          FastList.newList();      

   public  boolean  addAll(Collection<?  extends  E>  collection)  {          FastList<E>  collectionToAdd  =  collection  instanceof  FastList              ?  (FastList<E>)  collection              :  new  FastList<E>(collection);          this.lists.add(collectionToAdd);          return  true;      }      ...  

}  

Page 64: Scala Collections Performance - Lightbenddownloads.typesafe.com/website/presentations/... · PersistentDataStructures$ val3scalaImmutable: immutable.TreeSet[Int]= 33immutable.TreeSet.empty[Int]++(

CompositeFastList  Merge  •  Merging  is  O(1)  work  per  batch  

CFL  

Page 65: Scala Collections Performance - Lightbenddownloads.typesafe.com/website/presentations/... · PersistentDataStructures$ val3scalaImmutable: immutable.TreeSet[Int]= 33immutable.TreeSet.empty[Int]++(

Fork/Join  •  Fork-­‐join  is  general  purpose  but  always  requires  merge  work  

•  We  can  get  beDer  performance  through  specialized  data  structures  meant  for  combining  

Page 66: Scala Collections Performance - Lightbenddownloads.typesafe.com/website/presentations/... · PersistentDataStructures$ val3scalaImmutable: immutable.TreeSet[Int]= 33immutable.TreeSet.empty[Int]++(

Summary  GS  Collec9ons  and  Scala’s  Collec9ons  are  different  •  Persistent  data  structures  •  Hash  tables  •  Primi9ve  specializa9on  •  Fork-­‐join  

Page 67: Scala Collections Performance - Lightbenddownloads.typesafe.com/website/presentations/... · PersistentDataStructures$ val3scalaImmutable: immutable.TreeSet[Int]= 33immutable.TreeSet.empty[Int]++(

More  Informa9on  @motlin  @GoldmanSachs    

stackoverflow.com/ques9ons/tagged/gs-­‐collec9ons    

github.com/goldmansachs/gs-­‐collec9ons/tree/master/jmh-­‐tests  github.com/goldmansachs/gs-­‐collec9ons/tree/master/memory-­‐tests  github.com/goldmansachs/gs-­‐collec9ons-­‐kata    

infoq.com/presenta9ons/java-­‐streams-­‐scala-­‐parallel-­‐collec9ons  

Page 68: Scala Collections Performance - Lightbenddownloads.typesafe.com/website/presentations/... · PersistentDataStructures$ val3scalaImmutable: immutable.TreeSet[Int]= 33immutable.TreeSet.empty[Int]++(

Learn more at GS.com/Engineering

© 2015 Goldman Sachs. This presentation should not be relied upon or considered investment advice. Goldman Sachs does not warrant or guarantee to anyone the accuracy, completeness or efficacy of this presentation, and recipients should not rely on it except at their own risk. This presentation may not be forwarded or disclosed except with this disclaimer intact.

Page 69: Scala Collections Performance - Lightbenddownloads.typesafe.com/website/presentations/... · PersistentDataStructures$ val3scalaImmutable: immutable.TreeSet[Int]= 33immutable.TreeSet.empty[Int]++(

Q&A