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 from
Feb 16, 2012
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
Optimize foreach and iterators.
  • Loading branch information
erikrozendaal committed Jan 4, 2012
commit 72ec0ac869a29fca9ea0d45a3f70f1e9e1babaaf
108 changes: 66 additions & 42 deletions src/library/scala/collection/immutable/RedBlack.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
package scala.collection
package immutable

import annotation.tailrec
import annotation.meta.getter

/** An object containing the RedBlack tree implementation used by for `TreeMaps` and `TreeSets`.
Expand All @@ -37,7 +38,7 @@ object RedBlack {
case tree => Some(tree.value)
}

@annotation.tailrec
@tailrec
def lookup[A, B](tree: Tree[A, B], x: A)(implicit ordering: Ordering[A]): Tree[A, B] = if (tree eq null) null else {
val cmp = ordering.compare(x, tree.key)
if (cmp < 0) lookup(tree.left, x)
Expand All @@ -64,18 +65,19 @@ object RedBlack {
}

def foreach[A, B, U](tree: Tree[A, B], f: ((A, B)) => U): Unit = if (tree ne null) {
foreach(tree.left, f)
if (tree.left ne null) foreach(tree.left, f)
f((tree.key, tree.value))
foreach(tree.right, f)
if (tree.right ne null) foreach(tree.right, f)
}
def foreachKey[A, U](tree: Tree[A, _], f: A => U): Unit = if (tree ne null) {
foreachKey(tree.left, f)
if (tree.left ne null) foreachKey(tree.left, f)
f(tree.key)
foreachKey(tree.right, f)
if (tree.right ne null) foreachKey(tree.right, f)
}

def iterator[A, B](tree: Tree[A, B]): Iterator[(A, B)] = if (tree eq null) Iterator.empty else new TreeIterator(tree)
def keyIterator[A, _](tree: Tree[A, _]): Iterator[A] = if (tree eq null) Iterator.empty else new TreeKeyIterator(tree)
def iterator[A, B](tree: Tree[A, B]): Iterator[(A, B)] = new EntriesIterator(tree)
def keysIterator[A, _](tree: Tree[A, _]): Iterator[A] = new KeysIterator(tree)
def valuesIterator[_, B](tree: Tree[_, B]): Iterator[B] = new ValuesIterator(tree)

private[this] def balanceLeft[A, B, B1 >: B](isBlack: Boolean, z: A, zv: B, l: Tree[A, B1], d: Tree[A, B1]): Tree[A, B1] = {
if (isRedTree(l) && isRedTree(l.left))
Expand Down Expand Up @@ -283,7 +285,7 @@ object RedBlack {
@(inline @getter) final val left: Tree[A, B],
@(inline @getter) final val right: Tree[A, B])
extends Serializable {
@(inline @getter) final val count: Int = 1 + RedBlack.count(left) + RedBlack.count(right)
final val count: Int = 1 + RedBlack.count(left) + RedBlack.count(right)
def isBlack: Boolean
def nth(n: Int): Tree[A, B] = {
val count = RedBlack.count(left)
Expand Down Expand Up @@ -322,53 +324,75 @@ object RedBlack {
def unapply[A, B](t: BlackTree[A, B]) = Some((t.key, t.value, t.left, t.right))
}

private[this] class TreeIterator[A, B](tree: Tree[A, B]) extends Iterator[(A, B)] {
private[this] abstract class TreeIterator[A, B, R](tree: Tree[A, B]) extends Iterator[R] {
protected[this] def nextResult(tree: Tree[A, B]): R

override def hasNext: Boolean = next ne null

override def next: (A, B) = next match {
override def next: R = next match {
case null =>
throw new NoSuchElementException("next on empty iterator")
case tree =>
addLeftMostBranchToPath(tree.right)
next = if (path.isEmpty) null else path.pop()
(tree.key, tree.value)
next = findNext(tree.right)
nextResult(tree)
}

@annotation.tailrec
private[this] def addLeftMostBranchToPath(tree: Tree[A, B]) {
if (tree ne null) {
path.push(tree)
addLeftMostBranchToPath(tree.left)
@tailrec
private[this] def findNext(tree: Tree[A, B]): Tree[A, B] = {
if (tree eq null) popPath()
else if (tree.left eq null) tree
else {
pushPath(tree)
findNext(tree.left)
}
}

private[this] val path = mutable.ArrayStack.empty[Tree[A, B]]
addLeftMostBranchToPath(tree)
private[this] var next: Tree[A, B] = path.pop()
}

private[this] class TreeKeyIterator[A](tree: Tree[A, _]) extends Iterator[A] {
override def hasNext: Boolean = next ne null

override def next: A = next match {
case null =>
throw new NoSuchElementException("next on empty iterator")
case tree =>
addLeftMostBranchToPath(tree.right)
next = if (path.isEmpty) null else path.pop()
tree.key
private[this] def pushPath(tree: Tree[A, B]) {
try {
path(index) = tree
index += 1
} catch {
case _: ArrayIndexOutOfBoundsException =>
// Either the tree became unbalanced or we calculated the maximum height incorrectly.
// To avoid crashing the iterator we expand the path array. Obviously this should never
// happen...
//
// An exception handler is used instead of an if-condition to optimize the normal path.
assert(index >= path.length)
path :+= null
pushPath(tree)
}
}
private[this] def popPath(): Tree[A, B] = if (index == 0) null else {
index -= 1
path(index)
}

@annotation.tailrec
private[this] def addLeftMostBranchToPath(tree: Tree[A, _]) {
if (tree ne null) {
path.push(tree)
addLeftMostBranchToPath(tree.left)
}
private[this] var path = if (tree eq null) null else {
/*
* According to "Ralf Hinze. Constructing red-black trees" [http://www.cs.ox.ac.uk/ralf.hinze/publications/#P5]
* the maximum height of a red-black tree is 2*log_2(n + 2) - 2.
*
* According to {@see Integer#numberOfLeadingZeros} ceil(log_2(n)) = (32 - Integer.numberOfLeadingZeros(n - 1))
*
* We also don't store the deepest nodes in the path so the maximum path length is further reduced by one.
*/
val maximumHeight = 2 * (32 - Integer.numberOfLeadingZeros(tree.count + 2 - 1)) - 2 - 1
new Array[Tree[A, B]](maximumHeight)
}
private[this] var index = 0
private[this] var next: Tree[A, B] = findNext(tree)
}

private[this] class EntriesIterator[A, B](tree: Tree[A, B]) extends TreeIterator[A, B, (A, B)](tree) {
override def nextResult(tree: Tree[A, B]) = (tree.key, tree.value)
}

private[this] class KeysIterator[A, B](tree: Tree[A, B]) extends TreeIterator[A, B, A](tree) {
override def nextResult(tree: Tree[A, B]) = tree.key
}

private[this] val path = mutable.ArrayStack.empty[Tree[A, _]]
addLeftMostBranchToPath(tree)
private[this] var next: Tree[A, _] = path.pop()
private[this] class ValuesIterator[A, B](tree: Tree[A, B]) extends TreeIterator[A, B, B](tree) {
override def nextResult(tree: Tree[A, B]) = tree.value
}
}
5 changes: 4 additions & 1 deletion src/library/scala/collection/immutable/TreeMap.scala
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,10 @@ class TreeMap[A, +B] private (tree: RedBlack.Tree[A, B])(implicit val ordering:
*
* @return the new iterator
*/
def iterator: Iterator[(A, B)] = RB.iterator(tree)
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 contains(key: A): Boolean = RB.contains(tree, key)
override def isDefinedAt(key: A): Boolean = RB.contains(tree, key)
Expand Down
2 changes: 1 addition & 1 deletion src/library/scala/collection/immutable/TreeSet.scala
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ class TreeSet[A] private (tree: RedBlack.Tree[A, Unit])(implicit val ordering: O
*
* @return the new iterator
*/
def iterator: Iterator[A] = RB.keyIterator(tree)
def iterator: Iterator[A] = RB.keysIterator(tree)

override def foreach[U](f: A => U) = RB.foreachKey(tree, f)

Expand Down
16 changes: 16 additions & 0 deletions test/files/scalacheck/treemap.scala
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,22 @@ object Test extends Properties("TreeMap") {
consistent
}

property("worst-case tree height is iterable") = forAll(choose(0, 10), arbitrary[Boolean]) { (n: Int, even: Boolean) =>
/*
* According to "Ralf Hinze. Constructing red-black trees" [http://www.cs.ox.ac.uk/ralf.hinze/publications/#P5]
* you can construct a skinny tree of height 2n by inserting the elements [1 .. 2^(n+1) - 2] and a tree of height
* 2n+1 by inserting the elements [1 .. 3 * 2^n - 2], both in reverse order.
*
* Since we allocate a fixed size buffer in the iterator (based on the tree size) we need to ensure
* it is big enough for these worst-case trees.
*/
val highest = if (even) (1 << (n+1)) - 2 else 3*(1 << n) - 2
val values = (1 to highest).reverse
val subject = TreeMap(values zip values: _*)
val it = subject.iterator
try { while (it.hasNext) it.next; true } catch { case _ => false }
}

property("sorted") = forAll { (subject: TreeMap[Int, String]) => (subject.size >= 3) ==> {
subject.zip(subject.tail).forall { case (x, y) => x._1 < y._1 }
}}
Expand Down
16 changes: 16 additions & 0 deletions test/files/scalacheck/treeset.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,22 @@ object Test extends Properties("TreeSet") {
consistent
}

property("worst-case tree height is iterable") = forAll(choose(0, 10), arbitrary[Boolean]) { (n: Int, even: Boolean) =>
/*
* According to "Ralf Hinze. Constructing red-black trees" [http://www.cs.ox.ac.uk/ralf.hinze/publications/#P5]
* you can construct a skinny tree of height 2n by inserting the elements [1 .. 2^(n+1) - 2] and a tree of height
* 2n+1 by inserting the elements [1 .. 3 * 2^n - 2], both in reverse order.
*
* Since we allocate a fixed size buffer in the iterator (based on the tree size) we need to ensure
* it is big enough for these worst-case trees.
*/
val highest = if (even) (1 << (n+1)) - 2 else 3*(1 << n) - 2
val values = (1 to highest).reverse
val subject = TreeSet(values: _*)
val it = subject.iterator
try { while (it.hasNext) it.next; true } catch { case _ => false }
}

property("sorted") = forAll { (subject: TreeSet[Int]) => (subject.size >= 3) ==> {
subject.zip(subject.tail).forall { case (x, y) => x < y }
}}
Expand Down
0