Skip to content

Commit b4f2e58

Browse files
committed
Avoid allocating new BitmapIndexedMapNodes in HashMap#transform if possible
1 parent a826497 commit b4f2e58

File tree

1 file changed

+37
-10
lines changed

1 file changed

+37
-10
lines changed

library/src/scala/collection/immutable/ChampHashMap.scala

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,11 @@ final class HashMap[K, +V] private[immutable] (private[immutable] val rootNode:
154154
}
155155
}
156156

157-
override def transform[W](f: (K, V) => W) = new HashMap(rootNode.transform(f), cachedJavaKeySetHashCode)
157+
override def transform[W](f: (K, V) => W) = {
158+
val transformed = rootNode.transform(f)
159+
if (transformed eq rootNode) this.asInstanceOf[HashMap[K, W]]
160+
else new HashMap(transformed, cachedJavaKeySetHashCode)
161+
}
158162

159163
override def filterImpl(pred: ((K, V)) => Boolean, flipped: Boolean): HashMap[K, V] = {
160164
// This method has been preemptively overridden in order to ensure that an optimizing implementation may be included
@@ -652,23 +656,41 @@ private final class BitmapIndexedMapNode[K, +V](
652656
}
653657

654658
override def transform[W](f: (K, V) => W): BitmapIndexedMapNode[K, W] = {
655-
val newContent = content.clone()
659+
var newContent: Array[Any] = null
656660
val _payloadArity = payloadArity
657661
val _nodeArity = nodeArity
658-
val newContentLength = newContent.length
662+
val newContentLength = content.length
659663
var i = 0
660664
while (i < _payloadArity) {
661-
newContent(TupleLength * i + 1) = f(getKey(i), getValue(i))
665+
val key = getKey(i)
666+
val value = getValue(i)
667+
val newValue = f(key, value)
668+
if (newContent eq null) {
669+
if (newValue.asInstanceOf[AnyRef] ne value.asInstanceOf[AnyRef]) {
670+
newContent = content.clone()
671+
newContent(TupleLength * i + 1) = newValue
672+
}
673+
} else {
674+
newContent(TupleLength * i + 1) = newValue
675+
}
662676
i += 1
663677
}
664678

665679
var j = 0
666680
while (j < _nodeArity) {
667-
newContent(newContentLength - j - 1) = getNode(j).transform(f)
681+
val node = getNode(j)
682+
val newNode = node.transform(f)
683+
if (newContent eq null) {
684+
if (newNode ne node) {
685+
newContent = content.clone()
686+
newContent(newContentLength - j - 1) = newNode
687+
}
688+
} else
689+
newContent(newContentLength - j - 1) = newNode
668690
j += 1
669691
}
670-
671-
new BitmapIndexedMapNode[K, W](dataMap, nodeMap, newContent, originalHashes, size)
692+
if (newContent eq null) this.asInstanceOf[BitmapIndexedMapNode[K, W]]
693+
else new BitmapIndexedMapNode[K, W](dataMap, nodeMap, newContent, originalHashes, size)
672694
}
673695

674696
override def equals(that: Any): Boolean =
@@ -818,13 +840,18 @@ private final class HashCollisionMapNode[K, +V ](
818840
def foreach[U](f: ((K, V)) => U): Unit = content.foreach(f)
819841

820842
override def transform[W](f: (K, V) => W): HashCollisionMapNode[K, W] = {
821-
val newContent = Vector.newBuilder[(K, W)]
843+
var newContent = Vector.newBuilder[(K, W)]
822844
val contentIter = content.iterator
845+
// true if any values have been transformed to a different value via `f`
846+
var anyChanges = false
823847
while(contentIter.hasNext) {
824848
val (k, v) = contentIter.next()
825-
newContent.addOne((k, f(k, v)))
849+
val newValue = f(k, v)
850+
newContent.addOne((k, newValue))
851+
anyChanges ||= (v.asInstanceOf[AnyRef] ne newValue.asInstanceOf[AnyRef])
826852
}
827-
new HashCollisionMapNode(originalHash, hash, newContent.result())
853+
if (anyChanges) new HashCollisionMapNode(originalHash, hash, newContent.result())
854+
else this.asInstanceOf[HashCollisionMapNode[K, W]]
828855
}
829856

830857
override def equals(that: Any): Boolean =

0 commit comments

Comments
 (0)