Skip to content

Commit 9dd46f3

Browse files
committed
Unboxed Steppers for WrappedArray
- `WrappedArray` now produces the same primitive, non-boxing `Stepper` that the underlying `Array` would. - Plus some improvements to the unit tests and benchmarks. An interesting observation from the benchmarks: `IntStream.sum` is horribly slow when used on a seqStream built from an `IntStepper`. It is still slow when used on a seqStream built directly from an `Array` with Java’s own stream support. Using a `while` loop on an `IntIterator` produced by the stream beats both hands down and has the same performance independent of the stream source.
1 parent 466e8d4 commit 9dd46f3

File tree

7 files changed

+75
-40
lines changed

7 files changed

+75
-40
lines changed

benchmark/README.md

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,14 @@ Because the benchmarking is **very computationally expensive** it should be done
1414

1515
1. Make sure your terminal has plenty of lines of scrollback. (A couple thousand should do.)
1616

17-
2. Run `sbt`
17+
2. Run `sbt "jmh:run -i 5 -wi 3 -f 5"`. Wait overnight.
1818

19-
3. Enter `jmh:run -i 5 -wi 3 -f5`. Wait overnight.
19+
3. Clip off the last set of lines from the terminal window starting before the line that contains `[info] # Run complete. Total time:` and including that line until the end.
2020

21-
4. Clip off the last set of lines from the terminal window starting before the line that contains `[info] # Run complete. Total time:` and including that line until the end.
22-
23-
5. Save that in the file `results/jmhbench.log`
21+
4. Save that in the file `results/jmhbench.log`
2422

2523
## Comparison step
2624

27-
1. Run `sbt console`
28-
29-
2. Enter `bench.examine.SpeedReports()`
25+
1. Run `sbt parseJmh`
3026

31-
3. Look at the ASCII art results showing speed comparisons.
27+
2. Look at the ASCII art results showing speed comparisons.

benchmark/build.sbt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
enablePlugins(JmhPlugin)
22

33
val generateJmh = TaskKey[Unit]("generateJmh", "Generates JMH benchmark sources.")
4+
val parseJmh = TaskKey[Unit]("parseJmh", "Parses JMH benchmark logs in results/jmhbench.log.")
45

56
lazy val root = (project in file(".")).settings(
67
name := "java8-compat-bench",
@@ -11,5 +12,6 @@ lazy val root = (project in file(".")).settings(
1112
unmanagedJars in Compile ++= Seq(baseDirectory.value / "../target/scala-2.11/scala-java8-compat_2.11-0.8.0-SNAPSHOT.jar"),
1213
// This would be nicer but sbt-jmh doesn't like it:
1314
//unmanagedClasspath in Compile += Attributed.blank(baseDirectory.value / "../target/scala-2.11/classes"),
14-
generateJmh := (runMain in Compile).toTask(" bench.codegen.GenJmhBench").value
15+
generateJmh := (runMain in Compile).toTask(" bench.codegen.GenJmhBench").value,
16+
parseJmh := (runMain in Compile).toTask(" bench.examine.ParseJmhLog").value
1517
)

benchmark/src/main/scala/bench/Operations.scala

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,13 @@ object OnInt {
2727
def sum(t: Traversable[Int]): Int = t.sum
2828
def sum(i: Iterator[Int]): Int = i.sum
2929
def sum(s: IntStepper): Int = s.fold(0)(_ + _)
30-
def sum(s: IntStream): Int = s.sum
30+
def sum(s: IntStream): Int = {
31+
s.sum
32+
/*var r = 0
33+
val it = s.iterator()
34+
while(it.hasNext) r += it.nextInt()
35+
r*/
36+
}
3137
def psum(i: ParIterable[Int]): Int = i.sum
3238
def psum(s: IntStream): Int = s.sum
3339

benchmark/src/main/scala/bench/ParseJmhLog.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,4 +143,6 @@ object ParseJmhLog {
143143
println("-"*79)
144144
println
145145
}
146+
147+
def main(args: Array[String]): Unit = apply()
146148
}

src/main/scala/scala/compat/java8/converterImpl/StepConverters.scala

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,13 @@ trait Priority2StepConverters extends Priority3StepConverters {
8484
implicit def richArrayCharCanStep(underlying: Array[Char]) = new RichArrayCharCanStep(underlying)
8585
implicit def richArrayShortCanStep(underlying: Array[Short]) = new RichArrayShortCanStep(underlying)
8686
implicit def richArrayFloatCanStep(underlying: Array[Float]) = new RichArrayFloatCanStep(underlying)
87+
88+
// No special cases for AnyStepper-producing WrappedArrays; they are treated as IndexedSeqs
89+
implicit def richWrappedArrayByteCanStep(underlying: collection.mutable.WrappedArray[Byte]) = new RichArrayByteCanStep(underlying.array)
90+
implicit def richWrappedArrayCharCanStep(underlying: collection.mutable.WrappedArray[Char]) = new RichArrayCharCanStep(underlying.array)
91+
implicit def richWrappedArrayShortCanStep(underlying: collection.mutable.WrappedArray[Short]) = new RichArrayShortCanStep(underlying.array)
92+
implicit def richWrappedArrayFloatCanStep(underlying: collection.mutable.WrappedArray[Float]) = new RichArrayFloatCanStep(underlying.array)
93+
8794
implicit def richDoubleIndexedSeqCanStep[CC <: collection.IndexedSeqLike[Double, _]](underlying: CC) =
8895
new RichDoubleIndexedSeqCanStep[CC](underlying)
8996
implicit def richIntIndexedSeqCanStep[CC <: collection.IndexedSeqLike[Int, _]](underlying: CC) =
@@ -113,7 +120,11 @@ trait Priority1StepConverters extends Priority2StepConverters {
113120
implicit def richArrayDoubleCanStep(underlying: Array[Double]) = new RichArrayDoubleCanStep(underlying)
114121
implicit def richArrayIntCanStep(underlying: Array[Int]) = new RichArrayIntCanStep(underlying)
115122
implicit def richArrayLongCanStep(underlying: Array[Long]) = new RichArrayLongCanStep(underlying)
116-
123+
124+
implicit def richWrappedArrayDoubleCanStep(underlying: collection.mutable.WrappedArray[Double]) = new RichArrayDoubleCanStep(underlying.array)
125+
implicit def richWrappedArrayIntCanStep(underlying: collection.mutable.WrappedArray[Int]) = new RichArrayIntCanStep(underlying.array)
126+
implicit def richWrappedArrayLongCanStep(underlying: collection.mutable.WrappedArray[Long]) = new RichArrayLongCanStep(underlying.array)
127+
117128
implicit def richIntNumericRangeCanStep(underlying: collection.immutable.NumericRange[Int]) = new RichIntNumericRangeCanStep(underlying)
118129
implicit def richLongNumericRangeCanStep(underlying: collection.immutable.NumericRange[Long]) = new RichLongNumericRangeCanStep(underlying)
119130
implicit def richRangeCanStep(underlying: Range) = new RichRangeCanStep(underlying)

src/test/scala/scala/compat/java8/StepConvertersTest.scala

Lines changed: 31 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -25,46 +25,54 @@ class StepConvertersTest {
2525
case _ => false
2626
}
2727

28-
trait SpecCheck { def apply[X](x: X): Boolean }
28+
trait SpecCheck {
29+
def check[X](x: X): Boolean
30+
def msg[X](x: X): String
31+
def assert(x: Any): Unit =
32+
if(!check(x)) assertTrue(msg(x), false)
33+
}
2934
object SpecCheck {
30-
def apply(f: Any => Boolean) = new SpecCheck { def apply[X](x: X): Boolean = f(x) }
35+
def apply(f: Any => Boolean, err: Any => String = (_ => "SpecCheck failed")) = new SpecCheck {
36+
def check[X](x: X): Boolean = f(x)
37+
def msg[X](x: X): String = err(x)
38+
}
3139
}
3240

3341
def _eh_[X](x: => X)(implicit correctSpec: SpecCheck) {
34-
assert(x.isInstanceOf[Stepper[_]])
35-
assert(correctSpec(x))
42+
assertTrue(x.isInstanceOf[Stepper[_]])
43+
correctSpec.assert(x)
3644
}
3745

3846
def IFFY[X](x: => X)(implicit correctSpec: SpecCheck) {
39-
assert(x.isInstanceOf[Stepper[_]])
40-
assert(correctSpec(x))
41-
assert(isAcc(x))
47+
assertTrue(x.isInstanceOf[Stepper[_]])
48+
correctSpec.assert(x)
49+
assertTrue(isAcc(x))
4250
}
4351

4452
def Okay[X](x: => X)(implicit correctSpec: SpecCheck) {
45-
assert(x.isInstanceOf[Stepper[_]])
46-
assert(correctSpec(x))
47-
assert(!isAcc(x))
48-
assert(isLin(x))
53+
assertTrue(x.isInstanceOf[Stepper[_]])
54+
correctSpec.assert(x)
55+
assertTrue(!isAcc(x))
56+
assertTrue(isLin(x))
4957
}
5058

5159
def Fine[X](x: => X)(implicit correctSpec: SpecCheck) {
52-
assert(x.isInstanceOf[Stepper[_]])
53-
assert(correctSpec(x))
54-
assert(!isAcc(x))
60+
assertTrue(x.isInstanceOf[Stepper[_]])
61+
correctSpec.assert(x)
62+
assertTrue(!isAcc(x))
5563
}
5664

5765
def good[X](x: => X)(implicit correctSpec: SpecCheck) {
58-
assert(x.isInstanceOf[Stepper[_]])
59-
assert(correctSpec(x))
60-
assert(!isAcc(x))
61-
assert(!isLin(x))
66+
assertTrue(x.isInstanceOf[Stepper[_]])
67+
correctSpec.assert(x)
68+
assertTrue(!isAcc(x))
69+
assertTrue(!isLin(x))
6270
}
6371

6472
def Tell[X](x: => X)(implicit correctSpec: SpecCheck) {
6573
println(x.getClass.getName + " -> " + isAcc(x))
66-
assert(x.isInstanceOf[Stepper[_]])
67-
assert(correctSpec(x))
74+
assertTrue(x.isInstanceOf[Stepper[_]])
75+
correctSpec.assert(x)
6876
}
6977

7078
@Test
@@ -439,19 +447,17 @@ class StepConvertersTest {
439447

440448
@Test
441449
def shortWidening() {
442-
implicit val spec = SpecCheck(_.isInstanceOf[IntStepper])
450+
implicit val spec = SpecCheck(_.isInstanceOf[IntStepper], x => s"$x should be an IntStepper")
443451

444452
good( Array[Short](654321.toShort).stepper )
453+
good( (Array[Short](654321.toShort): cm.WrappedArray[Short]).stepper )
445454

446-
//TODO: None of these currently work because there are no native Stepper implementations. This does not only
447-
// affect widening conversions though. While you can get, for example, an IntStepper for a WrappedArray[Int],
448-
// all values have to go through a boxing/unboxing step!
455+
//TODO: None of these currently work because there are no native Stepper implementations:
449456

450457
//good( ci.NumericRange(123456.toShort, 123458.toShort, 1.toShort).stepper )
451458
//good( ((Array[Short](654321.toShort): cm.WrappedArray[Short]): cm.ArrayLike[Short, cm.WrappedArray[Short]]).stepper )
452459
//good( (Array[Short](654321.toShort): cm.ArrayOps[Short]).stepper )
453460
//good( cm.ResizableArray[Short](654321.toShort).stepper )
454-
//good( (Array[Short](654321.toShort): cm.WrappedArray[Short]).stepper )
455461
}
456462

457463
@Test

src/test/scala/scala/compat/java8/StreamConvertersTest.scala

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,13 @@ package scala.compat.java8
33
import org.junit.Test
44
import org.junit.Assert._
55

6+
import java.util.stream._
7+
import StreamConverters._
8+
import scala.compat.java8.collectionImpl.IntStepper
9+
import scala.compat.java8.converterImpl.{MakesStepper, MakesSequentialStream}
10+
611
class StreamConvertersTest {
7-
import java.util.stream._
8-
import StreamConverters._
9-
12+
1013
def assertEq[A](a1: A, a2: A, s: String) { assertEquals(s, a1, a2) } // Weird order normally!
1114
def assertEq[A](a1: A, a2: A) { assertEq(a1, a2, "not equal") }
1215

@@ -247,4 +250,13 @@ class StreamConvertersTest {
247250
assertEquals(Vector[Short](1.toShort, 2.toShort, 3.toShort), (Vector[Short](1.toShort, 2.toShort, 3.toShort).seqStream: IntStream).toScala[Vector])
248251
assertEquals(Vector[String]("a", "b"), (Vector[String]("a", "b").seqStream: Stream[String]).toScala[Vector])
249252
}
253+
254+
@Test
255+
def streamMaterialization(): Unit = {
256+
val coll = collection.mutable.WrappedArray.make[Int](Array(1,2,3))
257+
val streamize = implicitly[collection.mutable.WrappedArray[Int] => MakesSequentialStream[java.lang.Integer, IntStream]]
258+
assertTrue(streamize(coll).getClass.getName.contains("EnrichScalaCollectionWithSeqIntStream"))
259+
val steppize = implicitly[collection.mutable.WrappedArray[Int] => MakesStepper[IntStepper]]
260+
assertTrue(steppize(coll).getClass.getName.contains("RichArrayIntCanStep"))
261+
}
250262
}

0 commit comments

Comments
 (0)