8000 Immutable TreeMap/TreeSet performance (SI-5331) by erikrozendaal · Pull Request #82 · scala/scala · GitHub
[go: up one dir, main page]

Skip to content

Immutable TreeMap/TreeSet performance (SI-5331) #82

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 37 commits into 8000 from
Feb 16, 2012
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
540ad02
Use RedBlack.iterator to create iterators for TreeSet/TreeMap.
erikrozendaal Dec 17, 2011
88ed930
Use custom implementation for iterating over RedBlack trees. Raw
erikrozendaal Dec 17, 2011
edcec03
Optimized implementations of head/headOption/last/lastOption for
erikrozendaal Dec 17, 2011
9cdede8
Optimized implementation of init/tail for TreeSet/TreeMap.
erikrozendaal Dec 17, 2011
b7e6714
RedBlack.scala: Change count from 'def' to 'val' in NonEmpty tree
erikrozendaal Dec 17, 2011
95cb7bc
Implemented drop/take/slice/splitAt/dropRight/takeRight for
erikrozendaal Dec 18, 2011
8d67823
Implemented takeWhile/dropWhile/span to use tree splitting. This
erikrozendaal Dec 18, 2011
3f66061
Switched from isSmaller to ordering.
erikrozendaal Dec 19, 2011
7ec9b0b
Moved from implicit ordering value to implicit parameter.
erikrozendaal Dec 19, 2011
a02a815
Moved from Empty case object to case class in preparation of moving
erikrozendaal Dec 19, 2011
418adc6
Moved type parameter A from RedBlack to Tree.
erikrozendaal Dec 19, 2011
6c0e036
Changed abstract class RedBlack to singleton object.
erikrozendaal Dec 19, 2011
d2706db
Use single shared Empty instance across all RedBlack trees.
erikrozendaal Dec 19, 2011
6b95074
Make sure the redblack test compiles and runs.
erikrozendaal Dec 21, 2011
b9699f9
Made RedBlack private to the scala.collection.immutable package.
erikrozendaal Dec 27, 2011
32171c2
TreeMap/TreeSet no longer keep track of the size (the RedBlack tree
erikrozendaal Dec 27, 2011
b421bba
Performance improvements for iteration (foreach and iterator).
erikrozendaal Dec 27, 2011
4a0c4bb
Improved performance of RedBlack.NonEmpty.nth (helps take/drop/split/…
erikrozendaal Dec 27, 2011
ad0b09c
Added some tests for TreeMap/TreeSet.
erikrozendaal Dec 28, 2011
c51bdea
Minimize number of calls to ordering.
erikrozendaal Dec 28, 2011
6d8dca7
Moved key/value/left/right fields up to NonEmpty class. Don't rely
erikrozendaal Jan 2, 2012
82374ad
Implemented deletes without pattern matching.
erikrozendaal Jan 2, 2012
3dea251
Implemented range without using pattern matching.
erikrozendaal Jan 2, 2012
5c05f66
Use null to represent empty trees. Removed Empty/NonEmpty classes.
erikrozendaal Jan 2, 2012
72ec0ac
Optimize foreach and iterators.
erikrozendaal Jan 4, 2012
d735d0f
Move nth method to RedBlack. Inline factories for tree nodes.
erikrozendaal Jan 5, 2012
388ff47
Add implementation notes. Consistently use eq/ne to compare with null.
erikrozendaal Jan 5, 2012
7e92b3c
Deprecate TreeMap.isSmaller and TreeSet.isSmaller.
erikrozendaal Jan 6, 2012
f656142
Restore old RedBlack class to maintain backwards compatibility.
erikrozendaal Jan 6, 2012
288874d
Renamed object RedBlack to RedBlackTree.
erikrozendaal Jan 7, 2012
e61075c
Tests for takeWhile/dropWhile/span.
erikrozendaal Jan 7, 2012
8b3f984
Fix silly copy-paste error.
erikrozendaal Jan 7, 2012
f26f610
Test for maximum height of red-black tree.
erikrozendaal Jan 8, 2012
00b5cb8
Optimized implementation of TreeMap/TreeSet#to method.
erikrozendaal Jan 15, 2012
7824dbd
Custom coded version of range/from/to/until.
erikrozendaal Jan 21, 2012
78374f3
Custom implementations of drop/take/slice.
erikrozendaal Jan 22, 2012
51667dc
Removed TODOs.
erikrozendaal Jan 24, 2012
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions src/library/scala/collection/immutable/RedBlack.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,13 @@
package scala.collection
package immutable

/** A base class containing the implementations for `TreeMaps` and `TreeSets`.
/** Old base class that was used by previous implementations of `TreeMaps` and `TreeSets`.
*
* Deprecated due to various performance bugs (see [[https://issues.scala-lang.org/browse/SI-5331 SI-5331]] for more information).
*
* @since 2.3
*/
@deprecated("use `TreeMap` or `TreeSet` instead", "2.10")
@SerialVersionUID(8691885935445612921L)
abstract class RedBlack[A] extends Serializable {

Expand Down Expand Up @@ -287,5 +290,3 @@ abstract class RedBlack[A] extends Serializable {
def isBlack = true
}
}


485 changes: 485 additions & 0 deletions src/library/scala/collection/immutable/RedBlackTree.scala

Large diffs are not rendered by default.

101 changes: 73 additions & 28 deletions src/library/scala/collection/immutable/TreeMap.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ package scala.collection
package immutable

import generic._
import immutable.{RedBlackTree => RB}
import mutable.Builder
import annotation.bridge

Expand All @@ -23,7 +24,6 @@ object TreeMap extends ImmutableSortedMapFactory[TreeMap] {
def empty[A, B](implicit ord: Ordering[A]) = new TreeMap[A, B]()(ord)
/** $sortedMapCanBuildFromInfo */
implicit def canBuildFrom[A, B](implicit ord: Ordering[A]): CanBuildFrom[Coll, (A, B), TreeMap[A, B]] = new SortedMapCanBuildFrom[A, B]
private def make[A, B](s: Int, t: RedBlack[A]#Tree[B])(implicit ord: Ordering[A]) = new TreeMap[A, B](s, t)(ord)
}

/** This class implements immutable maps using a tree.
Expand All @@ -46,31 +46,79 @@ object TreeMap extends ImmutableSortedMapFactory[TreeMap] {
* @define mayNotTerminateInf
* @define willNotTerminateInf
*/
class TreeMap[A, +B](override val size: Int, t: RedBlack[A]#Tree[B])(implicit val ordering: Ordering[A])
extends RedBlack[A]
with SortedMap[A, B]
class TreeMap[A, +B] private (tree: RB.Tree[A, B])(implicit val ordering: Ordering[A])
extends SortedMap[A, B]
with SortedMapLike[A, B, TreeMap[A, B]]
with MapLike[A, B, TreeMap[A, B]]
with Serializable {

@deprecated("use `ordering.lt` instead", "2.10")
def isSmaller(x: A, y: A) = ordering.lt(x, y)

override protected[this] def newBuilder : Builder[(A, B), TreeMap[A, B]] =
TreeMap.newBuilder[A, B]

def this()(implicit ordering: Ordering[A]) = this(0, null)(ordering)
override def size = RB.count(tree)

protected val tree: RedBlack[A]#Tree[B] = if (size == 0) Empty else t
def this()(implicit ordering: Ordering[A]) = this(null)(ordering)

override def rangeImpl(from : Option[A], until : Option[A]): TreeMap[A,B] = {
val ntree = tree.range(from,until)
new TreeMap[A,B](ntree.count, ntree)
}
override def rangeImpl(from: Option[A], until: Option[A]): TreeMap[A, B] = new TreeMap[A, B](RB.rangeImpl(tree, from, until))
override def range(from: A, until: A): TreeMap[A, B] = new TreeMap[A, B](RB.range(tree, from, until))
override def from(from: A): TreeMap[A, B] = new TreeMap[A, B](RB.from(tree, from))
override def to(to: A): TreeMap[A, B] = new TreeMap[A, B](RB.to(tree, to))
override def until(until: A): TreeMap[A, B] = new TreeMap[A, B](RB.until(tree, until))

override def firstKey = t.first
override def lastKey = t.last
override def firstKey = RB.smallest(tree).key
override def lastKey = RB.greatest(tree).key
override def compare(k0: A, k1: A): Int = ordering.compare(k0, k1)

override def head = {
val smallest = RB.smallest(tree)
(smallest.key, smallest.value)
}
override def headOption = if (RB.isEmpty(tree)) None else Some(head)
override def last = {
val greatest = RB.greatest(tree)
(greatest.key, greatest.value)
}
override def lastOption = if (RB.isEmpty(tree)) None else Some(last)

override def tail = new TreeMap(RB.delete(tree, firstKey))
override def init = new TreeMap(RB.delete(tree, lastKey))

override def drop(n: Int) = {
if (n <= 0) this
else if (n >= size) empty
else new TreeMap(RB.drop(tree, n))
}

override def take(n: Int) = {
if (n <= 0) empty
else if (n >= size) this
else new TreeMap(RB.take(tree, n))
}

override def slice(from: Int, until: Int) = {
if (until <= from) empty
else if (from <= 0) take(until)
else if (until >= size) drop(from)
else new TreeMap(RB.slice(tree, from, until))
}

override def dropRight(n: Int) = take(size - n)
override def takeRight(n: Int) = drop(size - n)
override def splitAt(n: Int) = (take(n), drop(n))

private[this] def countWhile(p: ((A, B)) => Boolean): Int = {
var result = 0
val it = iterator
while (it.hasNext && p(it.next)) result += 1
result
}
override def dropWhile(p: ((A, B)) => Boolean) = drop(countWhile(p))
override def takeWhile(p: ((A, B)) => Boolean) = take(countWhile(p))
override def span(p: ((A, B)) => Boolean) = splitAt(countWhile(p))

/** A factory to create empty maps of the same type of keys.
*/
override def empty: TreeMap[A, B] = TreeMap.empty[A, B](ordering)
Expand All @@ -84,10 +132,7 @@ class TreeMap[A, +B](override val size: Int, t: RedBlack[A]#Tree[B])(implicit va
* @param value the value to be associated with `key`
* @return a new $coll with the updated binding
*/
override def updated [B1 >: B](key: A, value: B1): TreeMap[A, B1] = {
val newsize = if (tree.lookup(key).isEmpty) size + 1 else size
TreeMap.make(newsize, tree.update(key, value))
}
override def updated [B1 >: B](key: A, value: B1): TreeMap[A, B1] = new TreeMap(RB.update(tree, key, value))

/** Add a key/value pair to this map.
* @tparam B1 type of the value of the new binding, a supertype of `B`
Expand Down Expand Up @@ -128,36 +173,36 @@ class TreeMap[A, +B](override val size: Int, t: RedBlack[A]#Tree[B])(implicit va
* @return a new $coll with the inserted binding, if it wasn't present in the map
*/
def insert [B1 >: B](key: A, value: B1): TreeMap[A, B1] = {
assert(tree.lookup(key).isEmpty)
TreeMap.make(size + 1, tree.update(key, value))
assert(!RB.contains(tree, key))
new TreeMap(RB.update(tree, key, value))
}

def - (key:A): TreeMap[A, B] =
if (tree.lookup(key).isEmpty) this
else if (size == 1) empty
else TreeMap.make(size - 1, tree.delete(key))
if (!RB.contains(tree, key)) this
else new TreeMap(RB.delete(tree, key))

/** Check if this map maps `key` to a value and return the
* value if it exists.
*
* @param key the key of the mapping of interest
* @return the value of the mapping, if it exists
*/
override def get(key: A): Option[B] = tree.lookup(key) match {
case n: NonEmpty[b] => Some(n.value)
case _ => None
}
override def get(key: A): Option[B] = RB.get(tree, key)

/** Creates a new iterator over all elements contained in this
* object.
*
* @return the new iterator
*/
def iterator: Iterator[(A, B)] = tree.toStream.iterator
override def iterator: Iterator[(A, B)] = RB.iterator(tree)

override def keysIterator: Iterator[A] = RB.keysIterator(tree)
override def valuesIterator: Iterator[B] = RB.valuesIterator(tree)

override def toStream: Stream[(A, B)] = tree.toStream
override def contains(key: A): Boolean = RB.contains(tree, key)
override def isDefinedAt(key: A): Boolean = RB.contains(tree, key)

override def foreach[U](f : ((A,B)) => U) = tree foreach { case (x, y) => f(x, y) }
override def foreach[U](f : ((A,B)) => U) = RB.foreach(tree, f)
}


Expand Down
91 changes: 65 additions & 26 deletions src/library/scala/collection/immutable/TreeSet.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ package scala.collection
package immutable

import generic._
import immutable.{RedBlackTree => RB}
import mutable.{ Builder, SetBuilder }

/** $factoryInfo
Expand Down Expand Up @@ -46,20 +47,61 @@ object TreeSet extends ImmutableSortedSetFactory[TreeSet] {
* @define mayNotTerminateInf
* @define willNotTerminateInf
*/
@SerialVersionUID(-234066569443569402L)
class TreeSet[A](override val size: Int, t: RedBlack[A]#Tree[Unit])
(implicit val ordering: Ordering[A])
extends RedBlack[A] with SortedSet[A] with SortedSetLike[A, TreeSet[A]] with Serializable {
@SerialVersionUID(-5685982407650748405L)
class TreeSet[A] private (tree: RB.Tree[A, Unit])(implicit val ordering: Ordering[A])
extends SortedSet[A] with SortedSetLike[A, TreeSet[A]] with Serializable {

override def stringPrefix = "TreeSet"

F438 def isSmaller(x: A, y: A) = compare(x,y) < 0
override def size = RB.count(tree)

override def head = RB.smallest(tree).key
override def headOption = if (RB.isEmpty(tree)) None else Some(head)
override def last = RB.greatest(tree).key
override def lastOption = if (RB.isEmpty(tree)) None else Some(last)

override def tail = new TreeSet(RB.delete(tree, firstKey))
override def init = new TreeSet(RB.delete(tree, lastKey))

override def drop(n: Int) = {
if (n <= 0) this
else if (n >= size) empty
else newSet(RB.drop(tree, n))
}

def this()(implicit ordering: Ordering[A]) = this(0, null)(ordering)
override def take(n: Int) = {
if (n <= 0) empty
else if (n >= size) this
else newSet(RB.take(tree, n))
}

protected val tree: RedBlack[A]#Tree[Unit] = if (size == 0) Empty else t
override def slice(from: Int, until: Int) = {
if (until <= from) empty
else if (from <= 0) take(until)
else if (until >= size) drop(from)
else newSet(RB.slice(tree, from, until))
}

private def newSet(s: Int, t: RedBlack[A]#Tree[Unit]) = new TreeSet[A](s, t)
override def dropRight(n: Int) = take(size - n)
override def takeRight(n: Int) = drop(size - n)
override def splitAt(n: Int) = (take(n), drop(n))

private[this] def countWhile(p: A => Boolean): Int = {
var result = 0
val it = iterator
while (it.hasNext && p(it.next)) result += 1
result
}
override def dropWhile(p: A => Boolean) = drop(countWhile(p))
override def takeWhile(p: A => Boolean) = take(countWhile(p))
override def span(p: A => Boolean) = splitAt(countWhile(p))

@deprecated("use `ordering.lt` instead", "2.10")
def isSmaller(x: A, y: A) = compare(x,y) < 0

def this()(implicit ordering: Ordering[A]) = this(null)(ordering)

private def newSet(t: RB.Tree[A, Unit]) = new TreeSet[A](t)

/** A factory to create empty sets of the same type of keys.
*/
Expand All @@ -70,10 +112,7 @@ class TreeSet[A](override val size: Int, t: RedBlack[A]#Tree[Unit])
* @param elem a new element to add.
* @return a new $coll containing `elem` and all the elements of this $coll.
*/
def + (elem: A): TreeSet[A] = {
val newsize = if (tree.lookup(elem).isEmpty) size + 1 else size
newSet(newsize, tree.update(elem, ()))
}
def + (elem: A): TreeSet[A] = newSet(RB.update(tree, elem, ()))

/** A new `TreeSet` with the entry added is returned,
* assuming that elem is <em>not</em> in the TreeSet.
Expand All @@ -82,8 +121,8 @@ class TreeSet[A](override val size: Int, t: RedBlack[A]#Tree[Unit])
* @return a new $coll containing `elem` and all the elements of this $coll.
*/
def insert(elem: A): TreeSet[A] = {
assert(tree.lookup(elem).isEmpty)
newSet(size + 1, tree.update(elem, ()))
assert(!RB.contains(tree, elem))
newSet(RB.update(tree, elem, ()))
}

/** Creates a new `TreeSet` with the entry removed.
Expand All @@ -92,31 +131,31 @@ class TreeSet[A](override val size: Int, t: RedBlack[A]#Tree[Unit])
* @return a new $coll containing all the elements of this $coll except `elem`.
*/
def - (elem:A): TreeSet[A] =
if (tree.lookup(elem).isEmpty) this
else newSet(size - 1, tree delete elem)
if (!RB.contains(tree, elem)) this
else newSet(RB.delete(tree, elem))

/** Checks if this set contains element `elem`.
*
* @param elem the element to check for membership.
* @return true, iff `elem` is contained in this set.
*/
def contains(elem: A): Boolean = !tree.lookup(elem).isEmpty
def contains(elem: A): Boolean = RB.contains(tree, elem)

/** Creates a new iterator over all elements contained in this
* object.
*
* @return the new iterator
*/
def iterator: Iterator[A] = tree.toStream.iterator map (_._1)
def iterator: Iterator[A] = RB.keysIterator(tree)

override def toStream: Stream[A] = tree.toStream map (_._1)
override def foreach[U](f: A => U) = RB.foreachKey(tree, f)

override def foreach[U](f: A => U) = tree foreach { (x, y) => f(x) }
override def rangeImpl(from: Option[A], until: Option[A]): TreeSet[A] = newSet(RB.rangeImpl(tree, from, until))
override def range(from: A, until: A): TreeSet[A] = newSet(RB.range(tree, from, until))
override def from(from: A): TreeSet[A] = newSet(RB.from(tree, from))
override def to(to: A): TreeSet[A] = newSet(RB.to(tree, to))
override def until(until: A): TreeSet[A] = newSet(RB.until(tree, until))

override def rangeImpl(from: Option[A], until: Option[A]): TreeSet[A] = {
val tree = this.tree.range(from, until)
newSet(tree.count, tree)
}
override def firstKey = tree.first
override def lastKey = tree.last
override def firstKey = head
override def lastKey = last
}
Loading
0