Skip to content

Commit c66e1d5

Browse files
mkeskellsretronym
authored andcommitted
Dont create tuples when generating hashcodes for immutable maps
- add allocation tests for immutable maps - add pre-calculated values for empty maps, with test For immutable maps the max allocation is 32 bytes
1 parent 61701c2 commit c66e1d5

File tree

9 files changed

+429
-3
lines changed

9 files changed

+429
-3
lines changed

src/library/mima-filters/2.12.0.forwards.excludes

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,15 @@ ProblemFilters.exclude[MissingFieldProblem]("scala.collection.immutable.Map#Map1
4545
ProblemFilters.exclude[MissingFieldProblem]("scala.collection.immutable.Map#Map2.serialVersionUID")
4646
ProblemFilters.exclude[MissingFieldProblem]("scala.collection.immutable.Map#Map3.serialVersionUID")
4747
ProblemFilters.exclude[MissingFieldProblem]("scala.collection.immutable.Map#Map4.serialVersionUID")
48-
ProblemFilters.exclude[DirectAbstractMethodProblem]("scala.collection.GenTraversableOnce.toList")
48+
ProblemFilters.exclude[DirectAbstractMethodProblem]("scala.collection.GenTraversableOnce.toList")
49+
50+
51+
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.collection.immutable.HashMap.foreachEntry")
52+
ProblemFilters.exclude[MissingClassProblem]("scala.collection.immutable.Map$HashCodeAccumulator")
53+
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.collection.immutable.RedBlackTree.foreachEntry")
54+
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.collection.immutable.HashMap#HashTrieMap.foreachEntry")
55+
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.collection.immutable.ListMap.foreachEntry")
56+
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.collection.immutable.HashMap#HashMap1.foreachEntry")
57+
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.collection.immutable.HashMap#HashMapCollision1.foreachEntry")
58+
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.util.hashing.MurmurHash3.product2Hash")
59+
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.util.hashing.MurmurHash3.emptyMapHash")

src/library/scala/collection/immutable/HashMap.scala

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@ package collection
1515
package immutable
1616

1717
import generic._
18-
import scala.annotation.unchecked.{ uncheckedVariance=> uV }
18+
import scala.annotation.unchecked.{uncheckedVariance => uV}
1919
import parallel.immutable.ParHashMap
20+
import scala.util.hashing.MurmurHash3
2021

2122
/** This class implements immutable maps using a hash trie.
2223
*
@@ -51,6 +52,15 @@ sealed class HashMap[A, +B] extends AbstractMap[A, B]
5152
def iterator: Iterator[(A,B)] = Iterator.empty
5253

5354
override def foreach[U](f: ((A, B)) => U): Unit = ()
55+
private[immutable] def foreachEntry[U](f: (A, B) => U): Unit = ()
56+
override def hashCode(): Int = {
57+
if (isEmpty) MurmurHash3.emptyMapHash
58+
else {
59+
val hasher = new Map.HashCodeAccumulator()
60+
foreachEntry(hasher)
61+
hasher.finalizeHash
62+
}
63+
}
5464

5565
def get(key: A): Option[B] =
5666
get0(key, computeHash(key), 0)
@@ -232,6 +242,7 @@ object HashMap extends ImmutableMapFactory[HashMap] with BitOperations.Int {
232242

233243
override def iterator: Iterator[(A,B)] = Iterator(ensurePair)
234244
override def foreach[U](f: ((A, B)) => U): Unit = f(ensurePair)
245+
override private[immutable] def foreachEntry[U](f: (A, B) => U): Unit = f(key, value)
235246
// this method may be called multiple times in a multithreaded environment, but that's ok
236247
private[HashMap] def ensurePair: (A,B) = if (kv ne null) kv else { kv = (key, value); kv }
237248
protected override def merge0[B1 >: B](that: HashMap[A, B1], level: Int, merger: Merger[A, B1]): HashMap[A, B1] = {
@@ -293,6 +304,7 @@ object HashMap extends ImmutableMapFactory[HashMap] with BitOperations.Int {
293304

294305
override def iterator: Iterator[(A,B)] = kvs.iterator
295306
override def foreach[U](f: ((A, B)) => U): Unit = kvs.foreach(f)
307+
override private[immutable] def foreachEntry[U](f: (A, B) => U): Unit = kvs.foreachEntry(f)
296308
override def split: Seq[HashMap[A, B]] = {
297309
val (x, y) = kvs.splitAt(kvs.size / 2)
298310
def newhm(lm: ListMap[A, B @uV]) = {
@@ -469,6 +481,13 @@ object HashMap extends ImmutableMapFactory[HashMap] with BitOperations.Int {
469481
i += 1
470482
}
471483
}
484+
override private[immutable] def foreachEntry[U](f: (A, B) => U): Unit = {
485+
var i = 0
486+
while (i < elems.length) {
487+
elems(i).foreachEntry(f)
488+
i += 1
489+
}
490+
}
472491

473492
private def posOf(n: Int, bm: Int) = {
474493
var left = n

src/library/scala/collection/immutable/ListMap.scala

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ package immutable
1616

1717
import generic._
1818
import scala.annotation.tailrec
19+
import scala.util.hashing.MurmurHash3
1920

2021
/**
2122
* $factoryInfo
@@ -80,6 +81,24 @@ sealed class ListMap[A, +B] extends AbstractMap[A, B]
8081

8182
def get(key: A): Option[B] = None
8283

84+
private[immutable] def foreachEntry[U](f: (A, B) => U): Unit = {
85+
var current = this
86+
while (!current.isEmpty) {
87+
f(current.key, current.value)
88+
current = current.next
89+
}
90+
}
91+
92+
override def hashCode(): Int = {
93+
if (isEmpty) {
94+
MurmurHash3.emptyMapHash
95+
} else {
96+
val hasher = new Map.HashCodeAccumulator()
97+
foreachEntry(hasher)
98+
hasher.finalizeHash
99+
}
100+
}
101+
83102
override def updated[B1 >: B](key: A, value: B1): ListMap[A, B1] = new Node[B1](key, value)
84103

85104
def +[B1 >: B](kv: (A, B1)): ListMap[A, B1] = new Node[B1](kv._1, kv._2)

src/library/scala/collection/immutable/Map.scala

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ package collection
1515
package immutable
1616

1717
import generic._
18+
import scala.util.hashing.MurmurHash3
1819

1920
/**
2021
* A generic trait for immutable maps. Concrete classes have to provide
@@ -106,6 +107,7 @@ object Map extends ImmutableMapFactory[Map] {
106107
override def updated [V1] (key: Any, value: V1): Map[Any, V1] = new Map1(key, value)
107108
def + [V1](kv: (Any, V1)): Map[Any, V1] = updated(kv._1, kv._2)
108109
def - (key: Any): Map[Any, Nothing] = this
110+
override def hashCode: Int = MurmurHash3.emptyMapHash
109111
}
110112

111113
@SerialVersionUID(-9131943191104946031L)
@@ -127,6 +129,23 @@ object Map extends ImmutableMapFactory[Map] {
127129
override def foreach[U](f: ((K, V)) => U): Unit = {
128130
f((key1, value1))
129131
}
132+
override def hashCode(): Int = {
133+
import scala.util.hashing.MurmurHash3
134+
var a, b = 0
135+
val N = 1
136+
var c = 1
137+
138+
var h = MurmurHash3.product2Hash(key1, value1)
139+
a += h
140+
b ^= h
141+
if (h != 0) c *= h
142+
143+
h = MurmurHash3.mapSeed
144+
h = MurmurHash3.mix(h, a)
145+
h = MurmurHash3.mix(h, b)
146+
h = MurmurHash3.mixLast(h, c)
147+
MurmurHash3.finalizeHash(h, N)
148+
}
130149
}
131150

132151
@SerialVersionUID(-85684685400398742L)
@@ -158,6 +177,28 @@ object Map extends ImmutableMapFactory[Map] {
158177
override def foreach[U](f: ((K, V)) => U): Unit = {
159178
f((key1, value1)); f((key2, value2))
160179
}
180+
override def hashCode(): Int = {
181+
import scala.util.hashing.MurmurHash3
182+
var a, b = 0
183+
val N = 2
184+
var c = 1
185+
186+
var h = MurmurHash3.product2Hash(key1, value1)
187+
a += h
188+
b ^= h
189+
if (h != 0) c *= h
190+
191+
h = MurmurHash3.product2Hash(key2, value2)
192+
a += h
193+
b ^= h
194+
if (h != 0) c *= h
195+
196+
h = MurmurHash3.mapSeed
197+
h = MurmurHash3.mix(h, a)
198+
h = MurmurHash3.mix(h, b)
199+
h = MurmurHash3.mixLast(h, c)
200+
MurmurHash3.finalizeHash(h, N)
201+
}
161202
}
162203

163204
@SerialVersionUID(-6400718707310517135L)
@@ -194,6 +235,33 @@ object Map extends ImmutableMapFactory[Map] {
194235
override def foreach[U](f: ((K, V)) => U): Unit = {
195236
f((key1, value1)); f((key2, value2)); f((key3, value3))
196237
}
238+
override def hashCode(): Int = {
239+
import scala.util.hashing.MurmurHash3
240+
var a, b = 0
241+
val N = 3
242+
var c = 1
243+
244+
var h = MurmurHash3.product2Hash(key1, value1)
245+
a += h
246+
b ^= h
247+
if (h != 0) c *= h
248+
249+
h = MurmurHash3.product2Hash(key2, value2)
250+
a += h
251+
b ^= h
252+
if (h != 0) c *= h
253+
254+
h = MurmurHash3.product2Hash(key3, value3)
255+
a += h
256+
b ^= h
257+
if (h != 0) c *= h
258+
259+
h = MurmurHash3.mapSeed
260+
h = MurmurHash3.mix(h, a)
261+
h = MurmurHash3.mix(h, b)
262+
h = MurmurHash3.mixLast(h, c)
263+
MurmurHash3.finalizeHash(h, N)
264+
}
197265
}
198266

199267
@SerialVersionUID(-7992135791595275193L)
@@ -235,6 +303,58 @@ object Map extends ImmutableMapFactory[Map] {
235303
override def foreach[U](f: ((K, V)) => U): Unit = {
236304
f((key1, value1)); f((key2, value2)); f((key3, value3)); f((key4, value4))
237305
}
306+
override def hashCode(): Int = {
307+
import scala.util.hashing.MurmurHash3
308+
var a, b = 0
309+
val N = 4
310+
var c = 1
311+
312+
var h = MurmurHash3.product2Hash(key1, value1)
313+
a += h
314+
b ^= h
315+
if (h != 0) c *= h
316+
317+
h = MurmurHash3.product2Hash(key2, value2)
318+
a += h
319+
b ^= h
320+
if (h != 0) c *= h
321+
322+
h = MurmurHash3.product2Hash(key3, value3)
323+
a += h
324+
b ^= h
325+
if (h != 0) c *= h
326+
327+
h = MurmurHash3.product2Hash(key4, value4)
328+
a += h
329+
b ^= h
330+
if (h != 0) c *= h
331+
332+
h = MurmurHash3.mapSeed
333+
h = MurmurHash3.mix(h, a)
334+
h = MurmurHash3.mix(h, b)
335+
h = MurmurHash3.mixLast(h, c)
336+
MurmurHash3.finalizeHash(h, N)
337+
}
338+
}
339+
private [immutable] final class HashCodeAccumulator extends scala.runtime.AbstractFunction2[Any, Any, Unit] {
340+
import scala.util.hashing.MurmurHash3
341+
private var a, b, n = 0
342+
private var c = 1
343+
def apply(key: Any, value: Any): Unit = {
344+
val h = MurmurHash3.product2Hash(key, value)
345+
a += h
346+
b ^= h
347+
if (h != 0) c *= h
348+
n += 1
349+
}
350+
351+
def finalizeHash: Int = {
352+
var h = MurmurHash3.mapSeed
353+
h = MurmurHash3.mix(h, a)
354+
h = MurmurHash3.mix(h, b)
355+
h = MurmurHash3.mixLast(h, c)
356+
MurmurHash3.finalizeHash(h, n)
357+
}
238358
}
239359
}
240360

src/library/scala/collection/immutable/RedBlackTree.scala

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,14 +95,19 @@ object RedBlackTree {
9595
result
9696
}
9797

98-
9998
def foreach[A,B,U](tree:Tree[A,B], f:((A,B)) => U):Unit = if (tree ne null) _foreach(tree,f)
99+
def foreachEntry[A,B,U](tree:Tree[A,B], f:(A,B) => U):Unit = if (tree ne null) _foreachEntry(tree,f)
100100

101101
private[this] def _foreach[A, B, U](tree: Tree[A, B], f: ((A, B)) => U) {
102102
if (tree.left ne null) _foreach(tree.left, f)
103103
f((tree.key, tree.value))
104104
if (tree.right ne null) _foreach(tree.right, f)
105105
}
106+
private[this] def _foreachEntry[A, B, U](tree: Tree[A, B], f: (A, B) => U): Unit = {
107+
if (tree.left ne null) _foreachEntry(tree.left, f)
108+
f(tree.key, tree.value)
109+
if (tree.right ne null) _foreachEntry(tree.right, f)
110+
}
106111

107112
def foreachKey[A, U](tree:Tree[A,_], f: A => U):Unit = if (tree ne null) _foreachKey(tree,f)
108113

src/library/scala/collection/immutable/TreeMap.scala

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ package immutable
1717
import generic._
1818
import immutable.{RedBlackTree => RB}
1919
import mutable.Builder
20+
import scala.util.hashing.MurmurHash3
2021

2122
/** $factoryInfo
2223
* @define Coll immutable.TreeMap
@@ -203,4 +204,15 @@ final class TreeMap[A, +B] private (tree: RB.Tree[A, B])(implicit val ordering:
203204
override def isDefinedAt(key: A): Boolean = RB.contains(tree, key)
204205

205206
override def foreach[U](f : ((A,B)) => U) = RB.foreach(tree, f)
207+
208+
override def hashCode(): Int = {
209+
if (isEmpty) {
210+
MurmurHash3.emptyMapHash
211+
} else {
212+
val hasher = new Map.HashCodeAccumulator()
213+
RB.foreachEntry(tree, hasher)
214+
hasher.finalizeHash
215+
}
216+
}
217+
206218
}

src/library/scala/util/hashing/MurmurHash3.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,12 @@ private[hashing] class MurmurHash3 {
5252
h
5353
}
5454

55+
private[scala] def product2Hash(x: Any, y: Any, seed: Int): Int = {
56+
var h = seed
57+
h = mix(h, x.##)
58+
h = mix(h, y.##)
59+
finalizeHash(h, 2)
60+
}
5561
/** Compute the hash of a product */
5662
final def productHash(x: Product, seed: Int): Int = {
5763
val arr = x.productArity
@@ -212,6 +218,7 @@ object MurmurHash3 extends MurmurHash3 {
212218
def arrayHash[@specialized T](a: Array[T]): Int = arrayHash(a, arraySeed)
213219
def bytesHash(data: Array[Byte]): Int = arrayHash(data, arraySeed)
214220
def orderedHash(xs: TraversableOnce[Any]): Int = orderedHash(xs, symmetricSeed)
221+
private [scala] def product2Hash(x: Any, y: Any): Int = product2Hash(x, y, productSeed)
215222
def productHash(x: Product): Int = productHash(x, productSeed)
216223
def stringHash(x: String): Int = stringHash(x, stringSeed)
217224
def unorderedHash(xs: TraversableOnce[Any]): Int = unorderedHash(xs, traversableSeed)
@@ -228,6 +235,7 @@ object MurmurHash3 extends MurmurHash3 {
228235

229236
def mapHash(xs: scala.collection.Map[_, _]): Int = unorderedHash(xs, mapSeed)
230237
def setHash(xs: scala.collection.Set[_]): Int = unorderedHash(xs, setSeed)
238+
private[scala] val emptyMapHash = unorderedHash(Nil, mapSeed)
231239

232240
class ArrayHashing[@specialized T] extends Hashing[Array[T]] {
233241
def hash(a: Array[T]) = arrayHash(a)

0 commit comments

Comments
 (0)