Skip to content

Commit 7da8ce2

Browse files
authored
Merge pull request scala#8763 from retronym/merge/2.12.x-to-2.13.x-20200227
Merge 2.12.x to 2.13.x [ci: last-only]
2 parents 4aad5dc + 9b92d58 commit 7da8ce2

File tree

2 files changed

+285
-14
lines changed

2 files changed

+285
-14
lines changed

src/library/scala/runtime/ScalaRunTime.scala

Lines changed: 37 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -108,24 +108,47 @@ object ScalaRunTime {
108108
* Needed to deal with vararg arguments of primitive types that are passed
109109
* to a generic Java vararg parameter T ...
110110
*/
111-
def toObjectArray(src: AnyRef): Array[Object] = src match {
112-
case x: Array[AnyRef] => x
113-
case _ =>
114-
val length = array_length(src)
115-
val dest = new Array[Object](length)
116-
for (i <- 0 until length)
117-
array_update(dest, i, array_apply(src, i))
118-
dest
111+
def toObjectArray(src: AnyRef): Array[Object] = {
112+
def copy[@specialized T <: AnyVal](src: Array[T]): Array[Object] = {
113+
val length = src.length
114+
if (length == 0) Array.emptyObjectArray
115+
else {
116+
val dest = new Array[Object](length)
117+
var i = 0
118+
while (i < length) {
119+
dest(i) = src(i).asInstanceOf[AnyRef]
120+
i += 1
121+
}
122+
dest
123+
}
124+
}
125+
src match {
126+
case x: Array[AnyRef] => x
127+
case x: Array[Int] => copy(x)
128+
case x: Array[Double] => copy(x)
129+
case x: Array[Long] => copy(x)
130+
case x: Array[Float] => copy(x)
131+
case x: Array[Char] => copy(x)
132+
case x: Array[Byte] => copy(x)
133+
case x: Array[Short] => copy(x)
134+
case x: Array[Boolean] => copy(x)
135+
case x: Array[Unit] => copy(x)
136+
case null => throw new NullPointerException
137+
}
119138
}
120139

121140
def toArray[T](xs: scala.collection.Seq[T]) = {
122-
val arr = new Array[AnyRef](xs.length)
123-
var i = 0
124-
for (x <- xs) {
125-
arr(i) = x.asInstanceOf[AnyRef]
126-
i += 1
141+
if (xs.isEmpty) Array.emptyObjectArray
142+
else {
143+
val arr = new Array[AnyRef](xs.length)
144+
val it = xs.iterator
145+
var i = 0
146+
while (it.hasNext) {
147+
arr(i) = it.next().asInstanceOf[AnyRef]
148+
i += 1
149+
}
150+
arr
127151
}
128-
arr
129152
}
130153

131154
// Java bug: https://bugs.java.com/view_bug.do?bug_id=4071957
Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
package scala.collection.immutable
2+
3+
import java.util.concurrent.TimeUnit
4+
5+
import org.openjdk.jmh.annotations._
6+
import org.openjdk.jmh.infra._
7+
8+
class HashMapBulkUnsharedBenchmark extends HashMapBaseBulkBenchmark {
9+
10+
@OperationsPerInvocation(30)
11+
@Benchmark def opDataWithEmpty(bh: Blackhole): Unit = {
12+
var i = 0;
13+
while (i < 30) {
14+
operation(bh, baseData(i), HashMap.empty)
15+
i += 1
16+
}
17+
}
18+
19+
@OperationsPerInvocation(30)
20+
@Benchmark def opEmptyWithData(bh: Blackhole): Unit = {
21+
var i = 0;
22+
while (i < 30) {
23+
operation(bh, HashMap.empty, baseData(i))
24+
i += 1
25+
}
26+
}
27+
@OperationsPerInvocation(30)
28+
@Benchmark def opDataWithMapEmpty(bh: Blackhole): Unit = {
29+
var i = 0;
30+
while (i < 30) {
31+
operation(bh, baseData(i), Map.empty)
32+
i += 1
33+
}
34+
}
35+
36+
@OperationsPerInvocation(30)
37+
@Benchmark def opMapEmptyWithData(bh: Blackhole): Unit = {
38+
var i = 0;
39+
while (i < 30) {
40+
operation(bh, Map.empty, baseData(i))
41+
i += 1
42+
}
43+
}
44+
45+
@OperationsPerInvocation(29)
46+
@Benchmark def opWithDistinct(bh: Blackhole): Unit = {
47+
var i = 0;
48+
while (i < 29) {
49+
operation(bh, baseData(i), baseData(i+1))
50+
i += 1
51+
}
52+
}
53+
54+
@OperationsPerInvocation(20)
55+
@Benchmark def opDataWithContainedUnshared(bh: Blackhole): Unit = {
56+
var i = 0;
57+
while (i < 20) {
58+
operation(bh, overlap(i), baseData(i))
59+
i += 1
60+
}
61+
}
62+
63+
@OperationsPerInvocation(20)
64+
@Benchmark def opDataWithContainedShared(bh: Blackhole): Unit = {
65+
var i = 0;
66+
while (i < 20) {
67+
operation(bh, shared(i), baseData(i))
68+
i += 1
69+
}
70+
}
71+
72+
@OperationsPerInvocation(20)
73+
@Benchmark def opContainedUnsharedWithData(bh: Blackhole): Unit = {
74+
var i = 0;
75+
while (i < 20) {
76+
operation(bh, baseData(i), overlap(i))
77+
i += 1
78+
}
79+
}
80+
81+
@OperationsPerInvocation(20)
82+
@Benchmark def opContainedSharedWithData(bh: Blackhole): Unit = {
83+
var i = 0;
84+
while (i < 20) {
85+
operation(bh, baseData(i), shared(i))
86+
i += 1
87+
}
88+
}
89+
}
90+
class HashMapBulkSharedBenchmark extends HashMapBaseBulkBenchmark {
91+
@Param(Array("0", "20", "40", "60", "80", "90", "100"))
92+
var sharing: Int = _
93+
94+
@OperationsPerInvocation(10)
95+
@Benchmark def opWithOverlapUnshared(bh: Blackhole): Unit = {
96+
var i = 10;
97+
while (i < 20) {
98+
operation(bh, overlap(i - (10 - sharing / 10)), overlap2(i))
99+
i += 1
100+
}
101+
}
102+
103+
@OperationsPerInvocation(10)
104+
@Benchmark def opWithOverlapShared(bh: Blackhole): Unit = {
105+
var i = 10;
106+
while (i < 20) {
107+
operation(bh, shared(i - (10 - sharing / 10)), shared(i))
108+
i += 1
109+
}
110+
}
111+
}
112+
113+
@BenchmarkMode(Array(Mode.AverageTime))
114+
@Fork(2)
115+
@Threads(1)
116+
@Warmup(iterations = 10)
117+
@Measurement(iterations = 10)
118+
@OutputTimeUnit(TimeUnit.NANOSECONDS)
119+
@State(Scope.Benchmark)
120+
abstract class HashMapBaseBulkBenchmark {
121+
@Param(Array(
122+
"10",
123+
"100",
124+
"1000",
125+
"10000"
126+
))
127+
var size: Int = _
128+
@Param(Array("true", "false"))
129+
var colliding: Boolean = _
130+
131+
@Param(Array("+", "-", "++", "--", "merge", "mergeA", "mergeB", "sameElements", "filter"))
132+
var op: String = _
133+
var operation: (Blackhole, Map[HashMapBenchmarkData, String], Map[HashMapBenchmarkData, String]) => Any = _
134+
135+
// base data of specified size. All values are distinct
136+
var baseData: Array[HashMap[HashMapBenchmarkData, String]] = _
137+
// overlap(i) contains baseData(i) .. baseData(i+9) but with no structural sharing
138+
var overlap: Array[HashMap[HashMapBenchmarkData, String]] = _
139+
// overlap2(i) contains the same data as overlap(i) but with no structural sharing
140+
var overlap2: Array[HashMap[HashMapBenchmarkData, String]] = _
141+
// shared(i) contains baseData(i) .. baseData(i+9) but with structural sharing, both to the base data and preceding/subsequent entries
142+
var shared: Array[HashMap[HashMapBenchmarkData, String]] = _
143+
144+
@Setup(Level.Trial) def initKeys(): Unit = {
145+
operation = op match {
146+
case "+" => operationPlus
147+
case "-" => operationMinus
148+
case "++" => operationPlusPlus
149+
case "--" => operationMinusMinus
150+
case "merge" => operationMerge
151+
case "mergeA" => operationMergeA
152+
case "mergeB" => operationMergeB
153+
case "sameElements" => operationSameElements
154+
case "filter" => operationFilter
155+
}
156+
157+
def generate(prefix: String, size: Int) = {
158+
Array.tabulate(30) { i =>
159+
val tuples = (0 until size).map { k =>
160+
val data = s"key $i $k"
161+
val hash = if (colliding) (k >> 2) * i else data.hashCode
162+
HashMapBenchmarkData(hash, data) -> s"value $i $k"
163+
}
164+
HashMap.from(tuples)
165+
}
166+
}
167+
168+
baseData = generate("", size)
169+
170+
overlap = new Array[HashMap[HashMapBenchmarkData, String]](baseData.length - 10)
171+
overlap2 = new Array[HashMap[HashMapBenchmarkData, String]](baseData.length - 10)
172+
shared = new Array[HashMap[HashMapBenchmarkData, String]](baseData.length - 10)
173+
for (i <- 0 until baseData.length - 10) {
174+
var s1 = HashMap.empty[HashMapBenchmarkData, String]
175+
var s2 = HashMap.empty[HashMapBenchmarkData, String];
176+
for (j <- 0 until 10) {
177+
baseData(j) foreach {
178+
x =>
179+
s1 += x
180+
s2 += x
181+
}
182+
}
183+
overlap(i) = s1
184+
overlap2(i) = s2
185+
186+
}
187+
def base (i:Int) = {
188+
baseData(if (i < 0) baseData.length+i else i)
189+
}
190+
shared(0) = (-10 to (0, 1)).foldLeft (base(-10)) {case (a, b) => a ++ base(b)}
191+
for (i <- 1 until baseData.length - 10) {
192+
shared(i) = shared(i - 1) -- base(i - 10).keys ++ base(i)
193+
}
194+
}
195+
def operationPlus(bh: Blackhole, map1: Map[HashMapBenchmarkData, String], map2: Map[HashMapBenchmarkData, String]) = {
196+
var res = map1
197+
map2 foreach {
198+
res += _
199+
}
200+
bh.consume(res)
201+
}
202+
def operationMinus(bh: Blackhole, map1: Map[HashMapBenchmarkData, String], map2: Map[HashMapBenchmarkData, String]) = {
203+
var res = map1
204+
map2.keys foreach {
205+
res -= _
206+
}
207+
bh.consume(res)
208+
}
209+
def operationPlusPlus(bh: Blackhole, map1: Map[HashMapBenchmarkData, String], map2: Map[HashMapBenchmarkData, String]) = {
210+
bh.consume(map1 ++ map2)
211+
}
212+
def operationMinusMinus(bh: Blackhole, map1: Map[HashMapBenchmarkData, String], map2: Map[HashMapBenchmarkData, String]) = {
213+
bh.consume(map1 -- map2.keySet)
214+
}
215+
def operationMerge(bh: Blackhole, map1: Map[HashMapBenchmarkData, String], map2: Map[HashMapBenchmarkData, String]) = {
216+
bh.consume(map1.asInstanceOf[HashMap[HashMapBenchmarkData, String]].merged(map2.asInstanceOf[HashMap[HashMapBenchmarkData, String]])(null))
217+
}
218+
def operationMergeA(bh: Blackhole, map1: Map[HashMapBenchmarkData, String], map2: Map[HashMapBenchmarkData, String]) = {
219+
def merger(a: (HashMapBenchmarkData, String), b: (HashMapBenchmarkData, String)) = {
220+
a
221+
}
222+
bh.consume(map1.asInstanceOf[HashMap[HashMapBenchmarkData, String]].merged(map2.asInstanceOf[HashMap[HashMapBenchmarkData, String]]){merger})
223+
}
224+
def operationMergeB(bh: Blackhole, map1: Map[HashMapBenchmarkData, String], map2: Map[HashMapBenchmarkData, String]) = {
225+
def merger(a: (HashMapBenchmarkData, String), b: (HashMapBenchmarkData, String)) = {
226+
b
227+
}
228+
bh.consume(map1.asInstanceOf[HashMap[HashMapBenchmarkData, String]].merged(map2.asInstanceOf[HashMap[HashMapBenchmarkData, String]]){merger})
229+
}
230+
def operationSameElements(bh: Blackhole, map1: Map[HashMapBenchmarkData, String], map2: Map[HashMapBenchmarkData, String]) = {
231+
bh.consume(map1.sameElements(map2))
232+
}
233+
def operationFilter(bh: Blackhole, map1: Map[HashMapBenchmarkData, String], map2: Map[HashMapBenchmarkData, String]) = {
234+
bh.consume(map1.filterKeys(map2.keySet))
235+
}
236+
}
237+
object HashMapBenchmarkData {
238+
def apply(hashCode: Int, data: String) = new HashMapBenchmarkData(hashCode, data.intern())
239+
}
240+
class HashMapBenchmarkData private (override val hashCode: Int, val data: String) {
241+
override def equals(obj: Any): Boolean = obj match {
242+
case that: HashMapBenchmarkData => this.hashCode == that.hashCode && (this.data eq that.data)
243+
case _ => false
244+
}
245+
246+
override def toString: String = s"$hashCode-$data"
247+
}
248+

0 commit comments

Comments
 (0)