From 2d9c1ece23408bb62b1dc856ba697f11211f17b3 Mon Sep 17 00:00:00 2001 From: Antoine Brunner Date: Mon, 18 Nov 2019 08:13:53 +0100 Subject: [PATCH 01/13] Create a definition of the Split[T, N] type --- library/src/scala/Tuple.scala | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/library/src/scala/Tuple.scala b/library/src/scala/Tuple.scala index a3e3e4911a3f..0b3f0a44f5a4 100644 --- a/library/src/scala/Tuple.scala +++ b/library/src/scala/Tuple.scala @@ -65,7 +65,7 @@ object Tuple { } /** Type of the concatenation of two tuples */ - type Concat[X <: Tuple, +Y <: Tuple] <: Tuple = X match { + type Concat[+X <: Tuple, +Y <: Tuple] <: Tuple = X match { case Unit => Y case x1 *: xs1 => x1 *: Concat[xs1, Y] } @@ -116,6 +116,29 @@ object Tuple { */ type IsMappedBy[F[_]] = [X <: Tuple] =>> X =:= Map[InverseMap[X, F], F] + /** Transforms a tuple `(T1, ..., Tn)` into `(T1, ..., Ti)`. */ + type Take[+T <: Tuple, N <: Int] = N match { + case 0 => Unit + case S[n1] => T match { + case Unit => Unit + case x *: xs => Concat[x *: Unit, Take[xs, n1]] + } + } + + /** Transforms a tuple `(T1, ..., Tn)` into `(Ti+1, ..., Tn)`. */ + type Drop[+T <: Tuple, N <: Int] = N match { + case 0 => T + case S[n1] => T match { + case Unit => Unit + case x *: xs => Drop[xs, n1] + } + } + + /** Splits a tuple (T1, ..., Tn) into a pair of two tuples `(T1, ..., Ti)` and + * `(Ti+1, ..., Tn)`. + */ + type Split[+T <: Tuple, N <: Int] = (Take[T, N], Drop[T, N]) = (Take[T, N], Drop[T, N]) + /** Convert an array into a tuple of unknown arity and types */ def fromArray[T](xs: Array[T]): Tuple = { val xs2 = xs match { From 34adaad0700d0aa745dd3740337be9daa3f66291 Mon Sep 17 00:00:00 2001 From: Antoine Brunner Date: Tue, 19 Nov 2019 22:05:58 +0100 Subject: [PATCH 02/13] Finish implementing tuple.splitAt --- library/src/scala/Tuple.scala | 13 +++++++----- library/src/scala/runtime/DynamicTuple.scala | 22 +++++++++++++++++++- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/library/src/scala/Tuple.scala b/library/src/scala/Tuple.scala index 0b3f0a44f5a4..55fd8beed119 100644 --- a/library/src/scala/Tuple.scala +++ b/library/src/scala/Tuple.scala @@ -50,6 +50,9 @@ sealed trait Tuple extends Any { */ inline def map[F[_]](f: [t] => t => F[t]): Map[this.type, F] = DynamicTuple.dynamicMap(this, f) + + inline def splitAt[This >: this.type <: Tuple](n: Int): Split[This, n.type] = + DynamicTuple.dynamicSplitAt[This, n.type](this, n) } object Tuple { @@ -65,7 +68,7 @@ object Tuple { } /** Type of the concatenation of two tuples */ - type Concat[+X <: Tuple, +Y <: Tuple] <: Tuple = X match { + type Concat[X <: Tuple, +Y <: Tuple] <: Tuple = X match { case Unit => Y case x1 *: xs1 => x1 *: Concat[xs1, Y] } @@ -117,16 +120,16 @@ object Tuple { type IsMappedBy[F[_]] = [X <: Tuple] =>> X =:= Map[InverseMap[X, F], F] /** Transforms a tuple `(T1, ..., Tn)` into `(T1, ..., Ti)`. */ - type Take[+T <: Tuple, N <: Int] = N match { + type Take[T <: Tuple, N <: Int] <: Tuple = N match { case 0 => Unit case S[n1] => T match { case Unit => Unit - case x *: xs => Concat[x *: Unit, Take[xs, n1]] + case x *: xs => x *: Take[xs, n1] } } /** Transforms a tuple `(T1, ..., Tn)` into `(Ti+1, ..., Tn)`. */ - type Drop[+T <: Tuple, N <: Int] = N match { + type Drop[T <: Tuple, N <: Int] <: Tuple = N match { case 0 => T case S[n1] => T match { case Unit => Unit @@ -137,7 +140,7 @@ object Tuple { /** Splits a tuple (T1, ..., Tn) into a pair of two tuples `(T1, ..., Ti)` and * `(Ti+1, ..., Tn)`. */ - type Split[+T <: Tuple, N <: Int] = (Take[T, N], Drop[T, N]) = (Take[T, N], Drop[T, N]) + type Split[T <: Tuple, N <: Int] = (Take[T, N], Drop[T, N]) /** Convert an array into a tuple of unknown arity and types */ def fromArray[T](xs: Array[T]): Tuple = { diff --git a/library/src/scala/runtime/DynamicTuple.scala b/library/src/scala/runtime/DynamicTuple.scala index 53d44f75d449..0f3f5ba03543 100644 --- a/library/src/scala/runtime/DynamicTuple.scala +++ b/library/src/scala/runtime/DynamicTuple.scala @@ -1,6 +1,6 @@ package scala.runtime -import scala.Tuple.{ Concat, Size, Head, Tail, Elem, Zip, Map } +import scala.Tuple.{ Concat, Size, Head, Tail, Elem, Zip, Map, Split } object DynamicTuple { inline val MaxSpecialized = 22 @@ -264,6 +264,26 @@ object DynamicTuple { .asInstanceOf[Map[This, F]] } + def dynamicSplitAt[This <: Tuple, N <: Int](self: This, n: N): Split[This, N] = { + type Result = Split[This, N] + val (arr1, arr2) = (self: Any) match { + case xxl: TupleXXL => + xxl.elems.asInstanceOf[Array[Object]].splitAt(n) + case _ => + val size = self.asInstanceOf[Product].productArity + val arr1 = new Array[Object](n) + val arr2 = new Array[Object](size - n) + val it = self.asInstanceOf[Product].productIterator + itToArray(it, n, arr1, 0) + itToArray(it, size - n, arr2, 0) + (arr1, arr2) + } + ( + dynamicFromIArray(arr1.asInstanceOf[IArray[Object]]), + dynamicFromIArray(arr2.asInstanceOf[IArray[Object]]) + ).asInstanceOf[Result] + } + def consIterator(head: Any, tail: Tuple): Iterator[Any] = Iterator.single(head) ++ tail.asInstanceOf[Product].productIterator From 4fad817c5e056a9932e91badaa68a6447222144c Mon Sep 17 00:00:00 2001 From: Antoine Brunner Date: Wed, 20 Nov 2019 12:07:50 +0100 Subject: [PATCH 03/13] Add mergeSort benchmark to tupleOps.scala --- .../tools/benchmarks/tuples/TupleOps.scala | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/bench-run/src/main/scala/dotty/tools/benchmarks/tuples/TupleOps.scala b/bench-run/src/main/scala/dotty/tools/benchmarks/tuples/TupleOps.scala index a4c1d2d08ee3..907dc53cf0f5 100644 --- a/bench-run/src/main/scala/dotty/tools/benchmarks/tuples/TupleOps.scala +++ b/bench-run/src/main/scala/dotty/tools/benchmarks/tuples/TupleOps.scala @@ -1,11 +1,13 @@ package dotty.tools.benchmarks.tuples import org.openjdk.jmh.annotations._ +import scala.util.Random @State(Scope.Thread) class TupleOps { var tuple1: Tuple = _ var tuple2: Tuple = _ + var tuple3: Tuple = _ @Setup def setup(): Unit = { @@ -16,6 +18,8 @@ class TupleOps { tuple2 = () for (i <- 1 until 10) tuple2 = s"elem$i" *: tuple2 + + tuple3 = Tuple.fromArray(Random.shuffle(1 to 15).toArray) } def tupleFlatMap(tuple: Tuple, f: [A] => A => Tuple): Tuple = { @@ -34,6 +38,22 @@ class TupleOps { tailRecReverse(tuple, ()) } + def tupleMerge(tuple1: Tuple, tuple2: Tuple): Tuple = (tuple1, tuple2) match { + case (_, ()) => tuple1 + case ((), _) => tuple2 + case (x *: xs, y *: ys) => + if (x.asInstanceOf[Int] <= y.asInstanceOf[Int]) x *: tupleMerge(xs, tuple2) + else y *: tupleMerge(tuple1, ys) + } + + def tupleMergeSort(tuple: Tuple): Tuple = + if (tuple.size <= 1) tuple + else { + val (tuple1, tuple2) = tuple.splitAt(tuple.size / 2) + val (sorted1, sorted2) = (tupleMergeSort(tuple1), tupleMergeSort(tuple2)) + tupleMerge(sorted1, sorted2) + } + @Benchmark def reverse(): Tuple = { tupleReverse(tuple1) @@ -43,4 +63,9 @@ class TupleOps { def flatMap(): Tuple = { tupleFlatMap(tuple2, [A] => (x: A) => (x, x)) } + + @Benchmark + def mergeSort(): Tuple = { + tupleMergeSort(tuple3) + } } From e4dfcf5f3f7cf87c75f525ff07d83a08b473fe6c Mon Sep 17 00:00:00 2001 From: Antoine Brunner Date: Wed, 20 Nov 2019 20:37:00 +0100 Subject: [PATCH 04/13] Seed random generator for more constant results --- .../main/scala/dotty/tools/benchmarks/tuples/TupleOps.scala | 3 ++- library/src/scala/runtime/DynamicTuple.scala | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/bench-run/src/main/scala/dotty/tools/benchmarks/tuples/TupleOps.scala b/bench-run/src/main/scala/dotty/tools/benchmarks/tuples/TupleOps.scala index 907dc53cf0f5..1acec8691165 100644 --- a/bench-run/src/main/scala/dotty/tools/benchmarks/tuples/TupleOps.scala +++ b/bench-run/src/main/scala/dotty/tools/benchmarks/tuples/TupleOps.scala @@ -19,7 +19,8 @@ class TupleOps { for (i <- 1 until 10) tuple2 = s"elem$i" *: tuple2 - tuple3 = Tuple.fromArray(Random.shuffle(1 to 15).toArray) + val rand = new Random(12345) + tuple3 = Tuple.fromArray(rand.shuffle(1 to 15).toArray) } def tupleFlatMap(tuple: Tuple, f: [A] => A => Tuple): Tuple = { diff --git a/library/src/scala/runtime/DynamicTuple.scala b/library/src/scala/runtime/DynamicTuple.scala index 0f3f5ba03543..a483d4b2dc94 100644 --- a/library/src/scala/runtime/DynamicTuple.scala +++ b/library/src/scala/runtime/DynamicTuple.scala @@ -240,7 +240,7 @@ object DynamicTuple { res.asInstanceOf[Result] } - def dynamicApply[This <: NonEmptyTuple, N <: Int] (self: This, n: Int): Elem[This, N] = { + def dynamicApply[This <: NonEmptyTuple, N <: Int] (self: This, n: N): Elem[This, N] = { type Result = Elem[This, N] val res = (self: Any) match { case self: Unit => throw new IndexOutOfBoundsException(n.toString) From 769cd8a380686cb5a675334a69f129d00b27d72c Mon Sep 17 00:00:00 2001 From: Antoine Brunner Date: Thu, 21 Nov 2019 20:01:58 +0100 Subject: [PATCH 05/13] Add take and drop methods on Tuples --- library/src/scala/Tuple.scala | 27 ++++++++++++++---- library/src/scala/runtime/DynamicTuple.scala | 29 +++++++++++++++++++- 2 files changed, 50 insertions(+), 6 deletions(-) diff --git a/library/src/scala/Tuple.scala b/library/src/scala/Tuple.scala index 55fd8beed119..0785f370c43e 100644 --- a/library/src/scala/Tuple.scala +++ b/library/src/scala/Tuple.scala @@ -43,14 +43,31 @@ sealed trait Tuple extends Any { inline def zip[This >: this.type <: Tuple, T2 <: Tuple](t2: T2): Zip[This, T2] = DynamicTuple.dynamicZip(this, t2) - /** Called on a tuple `(a1, ..., an)`, returns a new tuple `(f(a1), ..., f(an))`. - * The result is typed as `(F[A1], ..., F[An])` if the tuple type is fully known. - * If the tuple is of the form `a1 *: ... *: Tuple` (that is, the tail is not known - * to be the cons type. - */ + /** Called on a tuple `(a1, ..., an)`, returns a new tuple `(f(a1), ..., f(an))`. + * The result is typed as `(F[A1], ..., F[An])` if the tuple type is fully known. + * If the tuple is of the form `a1 *: ... *: Tuple` (that is, the tail is not known + * to be the cons type. + */ inline def map[F[_]](f: [t] => t => F[t]): Map[this.type, F] = DynamicTuple.dynamicMap(this, f) + /** Given a tuple `(a1, ..., am)`, returns the tuple `(a1, ..., an)` consisting + * of its first n elements. + */ + inline def take[This >: this.type <: Tuple](n: Int): Take[This, n.type] = + DynamicTuple.dynamicTake[This, n.type](this, n) + + + /** Given a tuple `(a1, ..., am)`, returns the tuple `(an+1, ..., am)` consisting + * all its elements except the first n ones. + */ + inline def drop[This >: this.type <: Tuple](n: Int): Drop[This, n.type] = + DynamicTuple.dynamicDrop[This, n.type](this, n) + + /** Given a tuple `(a1, ..., am)`, returns a pair of the tuple `(a1, ..., an)` + * consisting of the first n elements, and the tuple `(an+1, ..., am)` consisting + * of the remaining elements. + */ inline def splitAt[This >: this.type <: Tuple](n: Int): Split[This, n.type] = DynamicTuple.dynamicSplitAt[This, n.type](this, n) } diff --git a/library/src/scala/runtime/DynamicTuple.scala b/library/src/scala/runtime/DynamicTuple.scala index a483d4b2dc94..81a34a78c6df 100644 --- a/library/src/scala/runtime/DynamicTuple.scala +++ b/library/src/scala/runtime/DynamicTuple.scala @@ -1,6 +1,6 @@ package scala.runtime -import scala.Tuple.{ Concat, Size, Head, Tail, Elem, Zip, Map, Split } +import scala.Tuple.{ Concat, Size, Head, Tail, Elem, Zip, Map, Take, Drop, Split } object DynamicTuple { inline val MaxSpecialized = 22 @@ -264,6 +264,33 @@ object DynamicTuple { .asInstanceOf[Map[This, F]] } + def dynamicTake[This <: Tuple, N <: Int](self: This, n: N): Take[This, N] = { + type Result = Take[This, N] + val arr = (self: Any) match { + case xxl: TupleXXL => + xxl.elems.asInstanceOf[Array[Object]].take(n) + case _ => + val arr = new Array[Object](n) + itToArray(self.asInstanceOf[Product].productIterator, n, arr, 0) + arr + } + dynamicFromIArray(arr.asInstanceOf[IArray[Object]]).asInstanceOf[Result] + } + + def dynamicDrop[This <: Tuple, N <: Int](self: This, n: N): Drop[This, N] = { + type Result = Drop[This, N] + val arr = (self: Any) match { + case xxl: TupleXXL => + xxl.elems.asInstanceOf[Array[Object]].drop(n) + case _ => + val size = self.asInstanceOf[Product].productArity - n + val arr = new Array[Object](size) + itToArray(self.asInstanceOf[Product].productIterator.drop(n), size, arr, 0) + arr + } + dynamicFromIArray(arr.asInstanceOf[IArray[Object]]).asInstanceOf[Result] + } + def dynamicSplitAt[This <: Tuple, N <: Int](self: This, n: N): Split[This, N] = { type Result = Split[This, N] val (arr1, arr2) = (self: Any) match { From e9d601f589cf243f5685521f9323dcbdd10e169e Mon Sep 17 00:00:00 2001 From: Antoine Brunner Date: Sat, 23 Nov 2019 11:10:19 +0100 Subject: [PATCH 06/13] Wrote tests for take and drop --- library/src/scala/runtime/DynamicTuple.scala | 24 ++++++++++++++------ tests/run/tuple-drop.check | 14 ++++++++++++ tests/run/tuple-drop.scala | 23 +++++++++++++++++++ tests/run/tuple-take.check | 15 ++++++++++++ tests/run/tuple-take.scala | 24 ++++++++++++++++++++ 5 files changed, 93 insertions(+), 7 deletions(-) create mode 100644 tests/run/tuple-drop.check create mode 100644 tests/run/tuple-drop.scala create mode 100644 tests/run/tuple-take.check create mode 100644 tests/run/tuple-take.scala diff --git a/library/src/scala/runtime/DynamicTuple.scala b/library/src/scala/runtime/DynamicTuple.scala index 81a34a78c6df..1b9599e2cfec 100644 --- a/library/src/scala/runtime/DynamicTuple.scala +++ b/library/src/scala/runtime/DynamicTuple.scala @@ -265,27 +265,36 @@ object DynamicTuple { } def dynamicTake[This <: Tuple, N <: Int](self: This, n: N): Take[This, N] = { + if (n < 0) throw new IndexOutOfBoundsException(n.toString) + val actualN = Math.min(n, self.size) + type Result = Take[This, N] val arr = (self: Any) match { + case () => Array.empty[Object] case xxl: TupleXXL => - xxl.elems.asInstanceOf[Array[Object]].take(n) + xxl.elems.asInstanceOf[Array[Object]].take(actualN) case _ => - val arr = new Array[Object](n) - itToArray(self.asInstanceOf[Product].productIterator, n, arr, 0) + val arr = new Array[Object](actualN) + itToArray(self.asInstanceOf[Product].productIterator, actualN, arr, 0) arr } dynamicFromIArray(arr.asInstanceOf[IArray[Object]]).asInstanceOf[Result] } def dynamicDrop[This <: Tuple, N <: Int](self: This, n: N): Drop[This, N] = { + if (n < 0) throw new IndexOutOfBoundsException(n.toString) + val size = self.size + val actualN = Math.min(n, size) + type Result = Drop[This, N] val arr = (self: Any) match { + case () => Array.empty[Object] case xxl: TupleXXL => - xxl.elems.asInstanceOf[Array[Object]].drop(n) + xxl.elems.asInstanceOf[Array[Object]].drop(actualN) case _ => - val size = self.asInstanceOf[Product].productArity - n - val arr = new Array[Object](size) - itToArray(self.asInstanceOf[Product].productIterator.drop(n), size, arr, 0) + val rem = size - actualN + val arr = new Array[Object](rem) + itToArray(self.asInstanceOf[Product].productIterator.drop(actualN), rem, arr, 0) arr } dynamicFromIArray(arr.asInstanceOf[IArray[Object]]).asInstanceOf[Result] @@ -294,6 +303,7 @@ object DynamicTuple { def dynamicSplitAt[This <: Tuple, N <: Int](self: This, n: N): Split[This, N] = { type Result = Split[This, N] val (arr1, arr2) = (self: Any) match { + case () => (Array.empty[Object], Array.empty[Object]) case xxl: TupleXXL => xxl.elems.asInstanceOf[Array[Object]].splitAt(n) case _ => diff --git a/tests/run/tuple-drop.check b/tests/run/tuple-drop.check new file mode 100644 index 000000000000..a902683c941b --- /dev/null +++ b/tests/run/tuple-drop.check @@ -0,0 +1,14 @@ +() +() +() +(1,2,3,4,5) +(2,3,4,5) +(4,5) +() +() +(11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35) +(12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35) +(21,22,23,24,25,26,27,28,29,30,31,32,33,34,35) +(31,32,33,34,35) +() +() \ No newline at end of file diff --git a/tests/run/tuple-drop.scala b/tests/run/tuple-drop.scala new file mode 100644 index 000000000000..e995f9a76296 --- /dev/null +++ b/tests/run/tuple-drop.scala @@ -0,0 +1,23 @@ + +object Test extends App { + val emptyTuple: Tuple = () + val tuple: Tuple = ("1", "2", "3", "4", "5") + val tupleXXL: Tuple = ("11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31", "32", "33", "34", "35") + + println(emptyTuple.drop(0)) + println(emptyTuple.drop(1)) + println(emptyTuple.drop(10)) + + println(tuple.drop(0)) + println(tuple.drop(1)) + println(tuple.drop(3)) + println(tuple.drop(5)) + println(tuple.drop(10)) + + println(tupleXXL.drop(0)) + println(tupleXXL.drop(1)) + println(tupleXXL.drop(10)) + println(tupleXXL.drop(20)) + println(tupleXXL.drop(25)) + println(tupleXXL.drop(30)) +} diff --git a/tests/run/tuple-take.check b/tests/run/tuple-take.check new file mode 100644 index 000000000000..a9c17e7d9480 --- /dev/null +++ b/tests/run/tuple-take.check @@ -0,0 +1,15 @@ +() +() +() +() +() +(1) +(1,2,3) +(1,2,3,4,5) +(1,2,3,4,5) +() +(11) +(11,12,13,14,15,16,17,18,19,20) +(11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30) +(11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35) +(11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35) \ No newline at end of file diff --git a/tests/run/tuple-take.scala b/tests/run/tuple-take.scala new file mode 100644 index 000000000000..54328b38937c --- /dev/null +++ b/tests/run/tuple-take.scala @@ -0,0 +1,24 @@ + +object Test extends App { + val emptyTuple: Tuple = () + val tuple: Tuple = ("1", "2", "3", "4", "5") + val tupleXXL: Tuple = ("11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31", "32", "33", "34", "35") + + println(emptyTuple.take(0)) + println(emptyTuple.take(0)) + println(emptyTuple.take(1)) + println(emptyTuple.take(10)) + + println(tuple.take(0)) + println(tuple.take(1)) + println(tuple.take(3)) + println(tuple.take(5)) + println(tuple.take(10)) + + println(tupleXXL.take(0)) + println(tupleXXL.take(1)) + println(tupleXXL.take(10)) + println(tupleXXL.take(20)) + println(tupleXXL.take(25)) + println(tupleXXL.take(30)) +} From 87e58194784c67e26359a2d3f5fce08597b88173 Mon Sep 17 00:00:00 2001 From: Antoine Brunner Date: Sat, 23 Nov 2019 11:20:29 +0100 Subject: [PATCH 07/13] Add benchmarks for take and drop --- bench-run/inputs/drop.in | 1 + bench-run/inputs/take.in | 1 + bench-run/src/main/scala/tuples/Drop.scala | 34 ++++++++++++++++++++++ bench-run/src/main/scala/tuples/Take.scala | 34 ++++++++++++++++++++++ 4 files changed, 70 insertions(+) create mode 100644 bench-run/inputs/drop.in create mode 100644 bench-run/inputs/take.in create mode 100644 bench-run/src/main/scala/tuples/Drop.scala create mode 100644 bench-run/src/main/scala/tuples/Take.scala diff --git a/bench-run/inputs/drop.in b/bench-run/inputs/drop.in new file mode 100644 index 000000000000..4555a56acbc3 --- /dev/null +++ b/bench-run/inputs/drop.in @@ -0,0 +1 @@ +size:0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50 diff --git a/bench-run/inputs/take.in b/bench-run/inputs/take.in new file mode 100644 index 000000000000..4555a56acbc3 --- /dev/null +++ b/bench-run/inputs/take.in @@ -0,0 +1 @@ +size:0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50 diff --git a/bench-run/src/main/scala/tuples/Drop.scala b/bench-run/src/main/scala/tuples/Drop.scala new file mode 100644 index 000000000000..cf824957b639 --- /dev/null +++ b/bench-run/src/main/scala/tuples/Drop.scala @@ -0,0 +1,34 @@ +package dotty.tools.benchmarks.tuples + +import org.openjdk.jmh.annotations._ +import scala.runtime.DynamicTuple + +@State(Scope.Thread) +class Drop { + @Param(Array("0")) + var size: Int = _ + var tuple: Tuple = _ + var array: Array[Object] = _ + var half: Int = _ + + @Setup + def setup(): Unit = { + tuple = () + half = size / 2 + + for (i <- 1 to size) + tuple = "elem" *: tuple + + array = new Array[Object](size) + } + + @Benchmark + def tupleDrop(): Tuple = { + DynamicTuple.dynamicDrop(tuple, half) + } + + @Benchmark + def arrayDrop(): Array[Object] = { + array.drop(half) + } +} diff --git a/bench-run/src/main/scala/tuples/Take.scala b/bench-run/src/main/scala/tuples/Take.scala new file mode 100644 index 000000000000..a64295e7b729 --- /dev/null +++ b/bench-run/src/main/scala/tuples/Take.scala @@ -0,0 +1,34 @@ +package dotty.tools.benchmarks.tuples + +import org.openjdk.jmh.annotations._ +import scala.runtime.DynamicTuple + +@State(Scope.Thread) +class Take { + @Param(Array("0")) + var size: Int = _ + var tuple: Tuple = _ + var array: Array[Object] = _ + var half: Int = _ + + @Setup + def setup(): Unit = { + tuple = () + half = size / 2 + + for (i <- 1 to size) + tuple = "elem" *: tuple + + array = new Array[Object](size) + } + + @Benchmark + def tupleTake(): Tuple = { + DynamicTuple.dynamicTake(tuple, half) + } + + @Benchmark + def arrayTake(): Array[Object] = { + array.take(half) + } +} From dfb8033406d6e5ff6d04dce6136b7e5809dad4f3 Mon Sep 17 00:00:00 2001 From: Antoine Brunner Date: Sun, 24 Nov 2019 09:13:28 +0100 Subject: [PATCH 08/13] Add benchmarks for tuple split --- bench-run/inputs/split.in | 1 + bench-run/src/main/scala/tuples/Split.scala | 34 +++++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 bench-run/inputs/split.in create mode 100644 bench-run/src/main/scala/tuples/Split.scala diff --git a/bench-run/inputs/split.in b/bench-run/inputs/split.in new file mode 100644 index 000000000000..4555a56acbc3 --- /dev/null +++ b/bench-run/inputs/split.in @@ -0,0 +1 @@ +size:0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50 diff --git a/bench-run/src/main/scala/tuples/Split.scala b/bench-run/src/main/scala/tuples/Split.scala new file mode 100644 index 000000000000..eff527dad90b --- /dev/null +++ b/bench-run/src/main/scala/tuples/Split.scala @@ -0,0 +1,34 @@ +package dotty.tools.benchmarks.tuples + +import org.openjdk.jmh.annotations._ +import scala.runtime.DynamicTuple + +@State(Scope.Thread) +class Split { + @Param(Array("0")) + var size: Int = _ + var tuple: Tuple = _ + var array: Array[Object] = _ + var half: Int = _ + + @Setup + def setup(): Unit = { + tuple = () + half = size / 2 + + for (i <- 1 to size) + tuple = "elem" *: tuple + + array = new Array[Object](size) + } + + @Benchmark + def tupleSplit(): (Tuple, Tuple) = { + DynamicTuple.dynamicSplitAt(tuple, half) + } + + @Benchmark + def arraySplit(): (Array[Object], Array[Object]) = { + array.splitAt(half) + } +} From 79d8edeec87ab4d3df9123403a9279bb0572a2a2 Mon Sep 17 00:00:00 2001 From: Antoine Brunner Date: Sun, 24 Nov 2019 09:39:25 +0100 Subject: [PATCH 09/13] Add test for tuple split --- library/src/scala/runtime/DynamicTuple.scala | 15 +++++++----- tests/run/tuple-split.check | 15 ++++++++++++ tests/run/tuple-split.scala | 24 ++++++++++++++++++++ 3 files changed, 48 insertions(+), 6 deletions(-) create mode 100644 tests/run/tuple-split.check create mode 100644 tests/run/tuple-split.scala diff --git a/library/src/scala/runtime/DynamicTuple.scala b/library/src/scala/runtime/DynamicTuple.scala index 1b9599e2cfec..3f4cca31b750 100644 --- a/library/src/scala/runtime/DynamicTuple.scala +++ b/library/src/scala/runtime/DynamicTuple.scala @@ -301,18 +301,21 @@ object DynamicTuple { } def dynamicSplitAt[This <: Tuple, N <: Int](self: This, n: N): Split[This, N] = { + if (n < 0) throw new IndexOutOfBoundsException(n.toString) + val size = self.size + val actualN = Math.min(n, size) + type Result = Split[This, N] val (arr1, arr2) = (self: Any) match { case () => (Array.empty[Object], Array.empty[Object]) case xxl: TupleXXL => - xxl.elems.asInstanceOf[Array[Object]].splitAt(n) + xxl.elems.asInstanceOf[Array[Object]].splitAt(actualN) case _ => - val size = self.asInstanceOf[Product].productArity - val arr1 = new Array[Object](n) - val arr2 = new Array[Object](size - n) + val arr1 = new Array[Object](actualN) + val arr2 = new Array[Object](size - actualN) val it = self.asInstanceOf[Product].productIterator - itToArray(it, n, arr1, 0) - itToArray(it, size - n, arr2, 0) + itToArray(it, actualN, arr1, 0) + itToArray(it, size - actualN, arr2, 0) (arr1, arr2) } ( diff --git a/tests/run/tuple-split.check b/tests/run/tuple-split.check new file mode 100644 index 000000000000..cce6be7a1f18 --- /dev/null +++ b/tests/run/tuple-split.check @@ -0,0 +1,15 @@ +((),()) +((),()) +((),()) +((),()) +((),(1,2,3,4,5)) +((1),(2,3,4,5)) +((1,2,3),(4,5)) +((1,2,3,4,5),()) +((1,2,3,4,5),()) +((),(11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35)) +((11),(12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35)) +((11,12,13,14,15,16,17,18,19,20),(21,22,23,24,25,26,27,28,29,30,31,32,33,34,35)) +((11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30),(31,32,33,34,35)) +((11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35),()) +((11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35),()) \ No newline at end of file diff --git a/tests/run/tuple-split.scala b/tests/run/tuple-split.scala new file mode 100644 index 000000000000..2415462af7cf --- /dev/null +++ b/tests/run/tuple-split.scala @@ -0,0 +1,24 @@ + +object Test extends App { + val emptyTuple: Tuple = () + val tuple: Tuple = ("1", "2", "3", "4", "5") + val tupleXXL: Tuple = ("11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31", "32", "33", "34", "35") + + println(emptyTuple.splitAt(0)) + println(emptyTuple.splitAt(0)) + println(emptyTuple.splitAt(1)) + println(emptyTuple.splitAt(10)) + + println(tuple.splitAt(0)) + println(tuple.splitAt(1)) + println(tuple.splitAt(3)) + println(tuple.splitAt(5)) + println(tuple.splitAt(10)) + + println(tupleXXL.splitAt(0)) + println(tupleXXL.splitAt(1)) + println(tupleXXL.splitAt(10)) + println(tupleXXL.splitAt(20)) + println(tupleXXL.splitAt(25)) + println(tupleXXL.splitAt(30)) +} From 2f09cf14025675f54e6795ddd41c01752adeee90 Mon Sep 17 00:00:00 2001 From: Antoine Brunner Date: Wed, 27 Nov 2019 17:22:09 +0100 Subject: [PATCH 10/13] Add tuple tests to blacklist so that pickling test passes --- compiler/test/dotc/run-test-pickling.blacklist | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/compiler/test/dotc/run-test-pickling.blacklist b/compiler/test/dotc/run-test-pickling.blacklist index f3e4c7ec847c..daccc58a1d35 100644 --- a/compiler/test/dotc/run-test-pickling.blacklist +++ b/compiler/test/dotc/run-test-pickling.blacklist @@ -6,6 +6,10 @@ t7374 tuples1.scala tuples1a.scala tuples1b.scala +tuple-ops.scala +tuple-take.scala +tuple-drop.scala +tuple-zip.scala typeclass-derivation1.scala typeclass-derivation2.scala typeclass-derivation2a.scala @@ -19,4 +23,3 @@ t10889 macros-in-same-project1 i5257.scala enum-java -tuple-ops.scala From 08a7f8da48778b557903a53a0c1995ad0e1d1d60 Mon Sep 17 00:00:00 2001 From: Antoine Brunner Date: Thu, 12 Dec 2019 18:30:23 +0100 Subject: [PATCH 11/13] Replace itToArray by Iterator.copyToArray --- library/src/scala/runtime/DynamicTuple.scala | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/library/src/scala/runtime/DynamicTuple.scala b/library/src/scala/runtime/DynamicTuple.scala index 3f4cca31b750..0c2437408933 100644 --- a/library/src/scala/runtime/DynamicTuple.scala +++ b/library/src/scala/runtime/DynamicTuple.scala @@ -275,7 +275,8 @@ object DynamicTuple { xxl.elems.asInstanceOf[Array[Object]].take(actualN) case _ => val arr = new Array[Object](actualN) - itToArray(self.asInstanceOf[Product].productIterator, actualN, arr, 0) + self.asInstanceOf[Product].productIterator.asInstanceOf[Iterator[Object]] + .copyToArray(arr, 0, actualN) arr } dynamicFromIArray(arr.asInstanceOf[IArray[Object]]).asInstanceOf[Result] @@ -294,7 +295,8 @@ object DynamicTuple { case _ => val rem = size - actualN val arr = new Array[Object](rem) - itToArray(self.asInstanceOf[Product].productIterator.drop(actualN), rem, arr, 0) + self.asInstanceOf[Product].productIterator.asInstanceOf[Iterator[Object]] + .drop(actualN).copyToArray(arr, 0, rem) arr } dynamicFromIArray(arr.asInstanceOf[IArray[Object]]).asInstanceOf[Result] @@ -313,9 +315,9 @@ object DynamicTuple { case _ => val arr1 = new Array[Object](actualN) val arr2 = new Array[Object](size - actualN) - val it = self.asInstanceOf[Product].productIterator - itToArray(it, actualN, arr1, 0) - itToArray(it, size - actualN, arr2, 0) + val it = self.asInstanceOf[Product].productIterator.asInstanceOf[Iterator[Object]] + it.copyToArray(arr1, 0, actualN) + it.copyToArray(arr2, 0, size - actualN) (arr1, arr2) } ( From 68750ea2a43522903fcc2c14c19b19ac33590d0e Mon Sep 17 00:00:00 2001 From: Antoine Brunner Date: Sat, 14 Dec 2019 15:21:17 +0100 Subject: [PATCH 12/13] Remove Unit case in pattern matches --- library/src/scala/runtime/DynamicTuple.scala | 33 ++++++++++++++------ 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/library/src/scala/runtime/DynamicTuple.scala b/library/src/scala/runtime/DynamicTuple.scala index 0c2437408933..827988eb08a9 100644 --- a/library/src/scala/runtime/DynamicTuple.scala +++ b/library/src/scala/runtime/DynamicTuple.scala @@ -269,16 +269,21 @@ object DynamicTuple { val actualN = Math.min(n, self.size) type Result = Take[This, N] + val arr = (self: Any) match { - case () => Array.empty[Object] case xxl: TupleXXL => xxl.elems.asInstanceOf[Array[Object]].take(actualN) case _ => - val arr = new Array[Object](actualN) - self.asInstanceOf[Product].productIterator.asInstanceOf[Iterator[Object]] - .copyToArray(arr, 0, actualN) - arr + if (actualN == 0) + Array.emptyObjectArray + else { + val arr = new Array[Object](actualN) + self.asInstanceOf[Product].productIterator.asInstanceOf[Iterator[Object]] + .copyToArray(arr, 0, actualN) + arr + } } + dynamicFromIArray(arr.asInstanceOf[IArray[Object]]).asInstanceOf[Result] } @@ -288,17 +293,23 @@ object DynamicTuple { val actualN = Math.min(n, size) type Result = Drop[This, N] + val arr = (self: Any) match { - case () => Array.empty[Object] case xxl: TupleXXL => xxl.elems.asInstanceOf[Array[Object]].drop(actualN) case _ => val rem = size - actualN - val arr = new Array[Object](rem) - self.asInstanceOf[Product].productIterator.asInstanceOf[Iterator[Object]] - .drop(actualN).copyToArray(arr, 0, rem) - arr + + if (rem == 0) + Array.emptyObjectArray + else { + val arr = new Array[Object](rem) + self.asInstanceOf[Product].productIterator.asInstanceOf[Iterator[Object]] + .drop(actualN).copyToArray(arr, 0, rem) + arr + } } + dynamicFromIArray(arr.asInstanceOf[IArray[Object]]).asInstanceOf[Result] } @@ -308,6 +319,7 @@ object DynamicTuple { val actualN = Math.min(n, size) type Result = Split[This, N] + val (arr1, arr2) = (self: Any) match { case () => (Array.empty[Object], Array.empty[Object]) case xxl: TupleXXL => @@ -320,6 +332,7 @@ object DynamicTuple { it.copyToArray(arr2, 0, size - actualN) (arr1, arr2) } + ( dynamicFromIArray(arr1.asInstanceOf[IArray[Object]]), dynamicFromIArray(arr2.asInstanceOf[IArray[Object]]) From 77a4496fc70e36fea1596eccce514ecf8d2cab70 Mon Sep 17 00:00:00 2001 From: Antoine Brunner Date: Wed, 18 Dec 2019 20:29:23 +0100 Subject: [PATCH 13/13] Optimized some branches in take and drop --- library/src/scala/runtime/DynamicTuple.scala | 41 +++++++++----------- 1 file changed, 19 insertions(+), 22 deletions(-) diff --git a/library/src/scala/runtime/DynamicTuple.scala b/library/src/scala/runtime/DynamicTuple.scala index 827988eb08a9..0397687eda89 100644 --- a/library/src/scala/runtime/DynamicTuple.scala +++ b/library/src/scala/runtime/DynamicTuple.scala @@ -270,47 +270,44 @@ object DynamicTuple { type Result = Take[This, N] - val arr = (self: Any) match { - case xxl: TupleXXL => - xxl.elems.asInstanceOf[Array[Object]].take(actualN) - case _ => - if (actualN == 0) - Array.emptyObjectArray - else { + if (actualN == 0) ().asInstanceOf[Result] + else { + val arr = (self: Any) match { + case xxl: TupleXXL => + xxl.elems.asInstanceOf[Array[Object]].take(actualN) + case _ => val arr = new Array[Object](actualN) self.asInstanceOf[Product].productIterator.asInstanceOf[Iterator[Object]] .copyToArray(arr, 0, actualN) arr - } - } + } - dynamicFromIArray(arr.asInstanceOf[IArray[Object]]).asInstanceOf[Result] + dynamicFromIArray(arr.asInstanceOf[IArray[Object]]).asInstanceOf[Result] + } } def dynamicDrop[This <: Tuple, N <: Int](self: This, n: N): Drop[This, N] = { if (n < 0) throw new IndexOutOfBoundsException(n.toString) val size = self.size val actualN = Math.min(n, size) + val rem = size - actualN type Result = Drop[This, N] - val arr = (self: Any) match { - case xxl: TupleXXL => - xxl.elems.asInstanceOf[Array[Object]].drop(actualN) - case _ => - val rem = size - actualN - - if (rem == 0) - Array.emptyObjectArray - else { + if (rem == 0) ().asInstanceOf[Result] + else { + val arr = (self: Any) match { + case xxl: TupleXXL => + xxl.elems.asInstanceOf[Array[Object]].drop(actualN) + case _ => val arr = new Array[Object](rem) self.asInstanceOf[Product].productIterator.asInstanceOf[Iterator[Object]] .drop(actualN).copyToArray(arr, 0, rem) arr - } - } + } - dynamicFromIArray(arr.asInstanceOf[IArray[Object]]).asInstanceOf[Result] + dynamicFromIArray(arr.asInstanceOf[IArray[Object]]).asInstanceOf[Result] + } } def dynamicSplitAt[This <: Tuple, N <: Int](self: This, n: N): Split[This, N] = {