Skip to content

Commit 1076073

Browse files
committed
VectorBuilder avoids copying data if possible
When calling VectorBuilder#addAll(Vector), if the builder is currently empty, it will share structure with the argument Vector in order to avoid iterating and copying the entire argument Vector. This is very helpful especially for the Vector#concat implementation, which in most cases will work by creating a new builder, and adding both vectors to the builder.
1 parent 73621f2 commit 1076073

File tree

1 file changed

+120
-43
lines changed

1 file changed

+120
-43
lines changed

library/src/scala/collection/immutable/Vector.scala

Lines changed: 120 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,7 @@ package scala
1414
package collection
1515
package immutable
1616

17-
import java.io.{ObjectInputStream, ObjectOutputStream}
18-
import java.util
19-
20-
import scala.collection.mutable.{Builder, ReusableBuilder}
17+
import scala.collection.mutable.ReusableBuilder
2118
import scala.annotation.unchecked.uncheckedVariance
2219
import scala.collection.generic.DefaultSerializable
2320
import scala.runtime.Statics.releaseFence
@@ -77,7 +74,7 @@ object Vector extends StrictOptimizedSeqFactory[Vector] {
7774
* @define mayNotTerminateInf
7875
* @define willNotTerminateInf
7976
*/
80-
final class Vector[+A] private[immutable] (private[collection] val startIndex: Int, private[collection] val endIndex: Int, focus: Int)
77+
final class Vector[+A] private[immutable] (private[collection] val startIndex: Int, private[collection] val endIndex: Int, private[immutable] val focus: Int)
8178
extends AbstractSeq[A]
8279
with IndexedSeq[A]
8380
with IndexedSeqOps[A, Vector, Vector[A]]
@@ -228,30 +225,51 @@ final class Vector[+A] private[immutable] (private[collection] val startIndex: I
228225
dropRight(1)
229226
}
230227

231-
// appendAll (suboptimal but avoids worst performance gotchas)
232-
override def appendedAll[B >: A](suffix: collection.IterableOnce[B]): Vector[B] = {
233-
import Vector.{Log2ConcatFaster, TinyAppendFaster}
234-
if (suffix.iterator.isEmpty) this
235-
else {
236-
suffix match {
237-
case suffix: collection.Iterable[B] =>
238-
suffix.size match {
239-
// Often it's better to append small numbers of elements (or prepend if RHS is a vector)
240-
case n if n <= TinyAppendFaster || n < (this.size >>> Log2ConcatFaster) =>
241-
var v: Vector[B] = this
242-
for (x <- suffix) v = v :+ x
243-
v
244-
case n if this.size < (n >>> Log2ConcatFaster) && suffix.isInstanceOf[Vector[_]] =>
245-
var v = suffix.asInstanceOf[Vector[B]]
246-
val ri = this.reverseIterator
247-
while (ri.hasNext) v = ri.next() +: v
248-
v
249-
case _ => super.appendedAll(suffix)
228+
override def appendedAll[B >: A](suffix: collection.IterableOnce[B]): Vector[B] =
229+
suffix match {
230+
case v: Vector[B] =>
231+
val thisLength = this.length
232+
val thatLength = v.length
233+
if (thisLength == 0) v
234+
else if (thatLength == 0) this
235+
else if (thatLength <= Vector.TinyAppendFaster) {
236+
// Often it's better to append small numbers of elements (or prepend if RHS is a vector)
237+
var v0: Vector[B] = this
238+
var i = 0
239+
while (i < thatLength) {
240+
v0 = v0.appended(v(i))
241+
i += 1
250242
}
251-
case _ => super.appendedAll(suffix)
252-
}
243+
v0
244+
} else {
245+
if (thisLength < (thatLength >>> Vector.Log2ConcatFaster)) {
246+
var v0 = v
247+
val iter = this.reverseIterator
248+
while(iter.hasNext) {
249+
v0 = iter.next() +: v0
250+
}
251+
v0
252+
} else {
253+
new VectorBuilder[B]().addAll(this).addAll(suffix).result()
254+
}
255+
}
256+
case _ =>
257+
val thatKnownSize = suffix.knownSize
258+
if (thatKnownSize == 0) this
259+
else if (thatKnownSize > 0 && thatKnownSize <= Vector.TinyAppendFaster) {
260+
var v0: Vector[B] = this
261+
val iter = suffix.iterator
262+
while (iter.hasNext) {
263+
v0 = v0.appended(iter.next())
264+
}
265+
v0
266+
} else {
267+
val iter = suffix.iterator
268+
if (iter.hasNext) {
269+
new VectorBuilder[B]().addAll(this).addAll(suffix).result()
270+
} else this
271+
}
253272
}
254-
}
255273

256274
override def prependedAll[B >: A](prefix: collection.IterableOnce[B]): Vector[B] = {
257275
// Implementation similar to `appendAll`: when of the collections to concatenate (either `this` or `prefix`)
@@ -755,10 +773,21 @@ final class VectorBuilder[A]() extends ReusableBuilder[A, Vector[A]] with Vector
755773
display0 = new Array[AnyRef](32)
756774
depth = 1
757775

776+
/** The index within the final Vector of `this.display0(0)` */
758777
private[this] var blockIndex = 0
778+
/** The index within `this.display0` which is the next available index to write to.
779+
* This value may be equal to display0.length, in which case before writing, a new block
780+
* should be created (see advanceToNextBlockIfNecessary)*/
759781
private[this] var lo = 0
760-
761-
def size: Int = blockIndex + lo
782+
/** Indicates an offset of the final vector from the actual underlying array elements. This is
783+
* used for example in `drop(1)` where instead of copying the entire Vector, only the startIndex is changed.
784+
*
785+
* This is present in the Builder because we may be able to share structure with a Vector that is `addAll`'d to this.
786+
* In which case we must track that Vector's startIndex offset.
787+
* */
788+
private[this] var startIndex = 0
789+
790+
def size: Int = (blockIndex & ~31) + lo - startIndex
762791
def isEmpty: Boolean = size == 0
763792
def nonEmpty: Boolean = size != 0
764793

@@ -781,10 +810,49 @@ final class VectorBuilder[A]() extends ReusableBuilder[A, Vector[A]] with Vector
781810
}
782811

783812
override def addAll(xs: IterableOnce[A]): this.type = {
784-
val it = (xs.iterator : Iterator[A]).asInstanceOf[Iterator[AnyRef]]
785-
while (it.hasNext) {
786-
advanceToNextBlockIfNecessary()
787-
lo += it.copyToArray(xs = display0, start = lo, len = display0.length - lo)
813+
814+
xs match {
815+
case v: Vector[A] if this.isEmpty && v.length >= 32 =>
816+
depth = v.depth
817+
blockIndex = (v.endIndex - 1) & ~31
818+
lo = v.endIndex - blockIndex
819+
startIndex = v.startIndex
820+
821+
/** `initFrom` will overwrite display0. Keep reference to it so we can reuse the array.*/
822+
val initialDisplay0 = display0
823+
initFrom(v)
824+
stabilize(v.focus)
825+
gotoPosWritable1(v.focus, blockIndex, v.focus ^ blockIndex, initialDisplay0)
826+
827+
depth match {
828+
case 2 =>
829+
display1((blockIndex >>> 5) & 31) = display0
830+
case 3 =>
831+
display1((blockIndex >>> 5) & 31) = display0
832+
display2((blockIndex >>> 10) & 31) = display1
833+
case 4 =>
834+
display1((blockIndex >>> 5) & 31) = display0
835+
display2((blockIndex >>> 10) & 31) = display1
836+
display3((blockIndex >>> 15) & 31) = display2
837+
case 5 =>
838+
display1((blockIndex >>> 5) & 31) = display0
839+
display2((blockIndex >>> 10) & 31) = display1
840+
display3((blockIndex >>> 15) & 31) = display2
841+
display4((blockIndex >>> 20) & 31) = display3
842+
case 6 =>
843+
display1((blockIndex >>> 5) & 31) = display0
844+
display2((blockIndex >>> 10) & 31) = display1
845+
display3((blockIndex >>> 15) & 31) = display2
846+
display4((blockIndex >>> 20) & 31) = display3
847+
display5((blockIndex >>> 25) & 31) = display4
848+
case _ => ()
849+
}
850+
case _ =>
851+
val it = (xs.iterator : Iterator[A]).asInstanceOf[Iterator[AnyRef]]
852+
while (it.hasNext) {
853+
advanceToNextBlockIfNecessary()
854+
lo += it.copyToArray(xs = display0, start = lo, len = display0.length - lo)
855+
}
788856
}
789857
this
790858
}
@@ -793,9 +861,9 @@ final class VectorBuilder[A]() extends ReusableBuilder[A, Vector[A]] with Vector
793861
val size = this.size
794862
if (size == 0)
795863
return Vector.empty
796-
val s = new Vector[A](0, size, 0) // should focus front or back?
864+
val s = new Vector[A](startIndex, blockIndex + lo, 0) // should focus front or back?
797865
s.initFrom(this)
798-
if (depth > 1) s.gotoPos(0, size - 1) // we're currently focused to size - 1, not size!
866+
if (depth > 1) s.gotoPos(startIndex, blockIndex + lo - 1) // we're currently focused to size - 1, not size!
799867
releaseFence()
800868
s
801869
}
@@ -805,6 +873,7 @@ final class VectorBuilder[A]() extends ReusableBuilder[A, Vector[A]] with Vector
805873
display0 = new Array[AnyRef](32)
806874
blockIndex = 0
807875
lo = 0
876+
startIndex = 0
808877
}
809878
}
810879

@@ -1000,10 +1069,19 @@ private[immutable] trait VectorPointer[T] {
10001069

10011070
// STUFF BELOW USED BY APPEND / UPDATE
10021071

1003-
private[immutable] final def nullSlotAndCopy[T <: AnyRef](array: Array[Array[T]], index: Int): Array[T] = {
1072+
/** Sets array(index) to null and returns an array with same contents as what was previously at array(index)
1073+
*
1074+
* If `destination` array is not null, original contents of array(index) will be copied to it, and it will be returned.
1075+
* Otherwise array(index).clone() is returned
1076+
*/
1077+
private[immutable] final def nullSlotAndCopy[T <: AnyRef](array: Array[Array[T]], index: Int, destination: Array[T] = null): Array[T] = {
10041078
val x = array(index)
10051079
array(index) = null
1006-
x.clone()
1080+
if (destination == null) x.clone()
1081+
else {
1082+
x.copyToArray(destination, 0)
1083+
destination
1084+
}
10071085
}
10081086

10091087
// make sure there is no aliasing
@@ -1086,10 +1164,9 @@ private[immutable] trait VectorPointer[T] {
10861164
display0 = display0.clone()
10871165
}
10881166

1089-
10901167
// requires structure is dirty and at pos oldIndex,
10911168
// ensures structure is dirty and at pos newIndex and writable at level 0
1092-
private[immutable] final def gotoPosWritable1(oldIndex: Int, newIndex: Int, xor: Int): Unit = {
1169+
private[immutable] final def gotoPosWritable1(oldIndex: Int, newIndex: Int, xor: Int, reuseDisplay0: Array[AnyRef] = null): Unit = {
10931170
if (xor < (1 << 5)) { // level = 0
10941171
display0 = display0.clone()
10951172
} else if (xor < (1 << 10)) { // level = 1
@@ -1102,7 +1179,7 @@ private[immutable] trait VectorPointer[T] {
11021179
display1((oldIndex >>> 5) & 31) = display0
11031180
display2((oldIndex >>> 10) & 31) = display1
11041181
display1 = nullSlotAndCopy(display2, (newIndex >>> 10) & 31)
1105-
display0 = nullSlotAndCopy(display1, (newIndex >>> 5) & 31)
1182+
display0 = nullSlotAndCopy(display1, (newIndex >>> 5) & 31, reuseDisplay0)
11061183
} else if (xor < (1 << 20)) { // level = 3
11071184
display1 = display1.clone()
11081185
display2 = display2.clone()
@@ -1112,7 +1189,7 @@ private[immutable] trait VectorPointer[T] {
11121189
display3((oldIndex >>> 15) & 31) = display2
11131190
display2 = nullSlotAndCopy(display3, (newIndex >>> 15) & 31)
11141191
display1 = nullSlotAndCopy(display2, (newIndex >>> 10) & 31)
1115-
display0 = nullSlotAndCopy(display1, (newIndex >>> 5) & 31)
1192+
display0 = nullSlotAndCopy(display1, (newIndex >>> 5) & 31, reuseDisplay0)
11161193
} else if (xor < (1 << 25)) { // level = 4
11171194
display1 = display1.clone()
11181195
display2 = display2.clone()
@@ -1125,7 +1202,7 @@ private[immutable] trait VectorPointer[T] {
11251202
display3 = nullSlotAndCopy(display4, (newIndex >>> 20) & 31)
11261203
display2 = nullSlotAndCopy(display3, (newIndex >>> 15) & 31)
11271204
display1 = nullSlotAndCopy(display2, (newIndex >>> 10) & 31)
1128-
display0 = nullSlotAndCopy(display1, (newIndex >>> 5) & 31)
1205+
display0 = nullSlotAndCopy(display1, (newIndex >>> 5) & 31, reuseDisplay0)
11291206
} else if (xor < (1 << 30)) { // level = 5
11301207
display1 = display1.clone()
11311208
display2 = display2.clone()
@@ -1141,7 +1218,7 @@ private[immutable] trait VectorPointer[T] {
11411218
display3 = nullSlotAndCopy(display4, (newIndex >>> 20) & 31)
11421219
display2 = nullSlotAndCopy(display3, (newIndex >>> 15) & 31)
11431220
display1 = nullSlotAndCopy(display2, (newIndex >>> 10) & 31)
1144-
display0 = nullSlotAndCopy(display1, (newIndex >>> 5) & 31)
1221+
display0 = nullSlotAndCopy(display1, (newIndex >>> 5) & 31, reuseDisplay0)
11451222
} else { // level = 6
11461223
throw new IllegalArgumentException()
11471224
}

0 commit comments

Comments
 (0)