Skip to content

Commit d6d6a37

Browse files
authored
Merge pull request #5211 from tanishiking/copy-on-write-arraylist
Optimize CopyOnWriteArrayList to use scala.Array on Wasm
2 parents 07ae33a + 718d3ec commit d6d6a37

File tree

1 file changed

+205
-23
lines changed

1 file changed

+205
-23
lines changed

javalib/src/main/scala/java/util/concurrent/CopyOnWriteArrayList.scala

Lines changed: 205 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212

1313
package java.util.concurrent
1414

15+
import scala.language.higherKinds
16+
1517
import java.lang.Cloneable
1618
import java.lang.Utils._
1719
import java.lang.{reflect => jlr}
@@ -23,32 +25,46 @@ import scala.annotation.tailrec
2325
import ScalaOps._
2426

2527
import scala.scalajs._
28+
import scala.scalajs.LinkingInfo._
2629

27-
class CopyOnWriteArrayList[E <: AnyRef] private (private var inner: js.Array[E])
30+
class CopyOnWriteArrayList[E <: AnyRef] private (initialCapacity: Int)
2831
extends List[E] with RandomAccess with Cloneable with Serializable {
2932
self =>
3033

34+
/* This class has two different implementations for the
35+
* internal data storage, depending on whether we are on Wasm or JS.
36+
* We use `js.Array` on JS, and `scala.Array` on Wasm.
37+
* The initialCapacity parameter is effective only in Wasm,
38+
* since js.Array doesn't support explicit pre-allocation.
39+
*
40+
* On Wasm, we store the length at the index 0 of the array.
41+
*/
42+
43+
import CopyOnWriteArrayList._
44+
45+
private var inner: innerImpl.Repr[E] = innerImpl.make(initialCapacity)
46+
3147
// requiresCopyOnWrite is false if and only if no other object
3248
// (like the iterator) may have a reference to inner
3349
private var requiresCopyOnWrite = false
3450

3551
def this() = {
36-
this(new js.Array[E])
52+
this(16)
3753
}
3854

3955
def this(c: Collection[_ <: E]) = {
40-
this()
56+
this(c.size())
4157
addAll(c)
4258
}
4359

4460
def this(toCopyIn: Array[E]) = {
45-
this()
61+
this(toCopyIn.length)
4662
for (i <- 0 until toCopyIn.length)
4763
add(toCopyIn(i))
4864
}
4965

5066
def size(): Int =
51-
inner.length
67+
innerImpl.length(inner)
5268

5369
def isEmpty(): Boolean =
5470
size() == 0
@@ -175,7 +191,7 @@ class CopyOnWriteArrayList[E <: AnyRef] private (private var inner: js.Array[E])
175191
}
176192

177193
def clear(): Unit = {
178-
inner = new js.Array[E]
194+
inner = innerImpl.make(16)
179195
requiresCopyOnWrite = false
180196
}
181197

@@ -274,43 +290,49 @@ class CopyOnWriteArrayList[E <: AnyRef] private (private var inner: js.Array[E])
274290
}
275291

276292
protected def innerGet(index: Int): E =
277-
inner(index)
293+
innerImpl.get(inner, index)
278294

279295
protected def innerSet(index: Int, elem: E): Unit =
280-
inner(index) = elem
296+
innerImpl.set(inner, index, elem)
281297

282-
protected def innerPush(elem: E): Unit =
283-
inner.push(elem)
298+
protected def innerPush(elem: E): Unit = {
299+
val newInner = innerImpl.push(inner, elem)
300+
if (LinkingInfo.isWebAssembly) // opt: for JS we know it's always the same
301+
inner = newInner
302+
}
284303

285-
protected def innerInsert(index: Int, elem: E): Unit =
286-
inner.splice(index, 0, elem)
304+
protected def innerInsert(index: Int, elem: E): Unit = {
305+
val newInner = innerImpl.add(inner, index, elem)
306+
if (LinkingInfo.isWebAssembly) // opt: for JS we know it's always the same
307+
inner = newInner
308+
}
287309

288310
protected def innerInsertMany(index: Int, items: Collection[_ <: E]): Unit = {
289-
val itemsArray = js.Array[E]()
290-
items.scalaOps.foreach(itemsArray.push(_))
291-
inner.splice(index, 0, itemsArray.toSeq: _*)
311+
val newInner = innerImpl.add(inner, index, items)
312+
if (LinkingInfo.isWebAssembly) // opt: for JS we know it's always the same
313+
inner = newInner
292314
}
293315

294316
protected def innerRemove(index: Int): E =
295-
arrayRemoveAndGet(inner, index)
317+
innerImpl.remove(inner, index)
296318

297319
protected def innerRemoveMany(index: Int, count: Int): Unit =
298-
inner.splice(index, count)
320+
innerImpl.remove(inner, index, count)
299321

300322
protected def copyIfNeeded(): Unit = {
301323
if (requiresCopyOnWrite) {
302-
inner = inner.jsSlice()
324+
inner = innerImpl.clone(inner)
303325
requiresCopyOnWrite = false
304326
}
305327
}
306328

307-
protected def innerSnapshot(): js.Array[E] = {
329+
protected def innerSnapshot(): innerImpl.Repr[E] = {
308330
requiresCopyOnWrite = true
309331
inner
310332
}
311333

312334
private class CopyOnWriteArrayListView(fromIndex: Int, private var toIndex: Int)
313-
extends CopyOnWriteArrayList[E](null: js.Array[E]) {
335+
extends CopyOnWriteArrayList[E](initialCapacity) {
314336
viewSelf =>
315337

316338
override def size(): Int =
@@ -381,7 +403,7 @@ class CopyOnWriteArrayList[E <: AnyRef] private (private var inner: js.Array[E])
381403
override protected def copyIfNeeded(): Unit =
382404
self.copyIfNeeded()
383405

384-
override protected def innerSnapshot(): js.Array[E] =
406+
override protected def innerSnapshot(): innerImpl.Repr[E] =
385407
self.innerSnapshot()
386408

387409
protected def changeSize(delta: Int): Unit =
@@ -399,7 +421,8 @@ class CopyOnWriteArrayList[E <: AnyRef] private (private var inner: js.Array[E])
399421
}
400422
}
401423

402-
private class CopyOnWriteArrayListIterator[E](arraySnapshot: js.Array[E], i: Int, start: Int, end: Int)
424+
private class CopyOnWriteArrayListIterator[E](
425+
arraySnapshot: CopyOnWriteArrayList.innerImpl.Repr[E], i: Int, start: Int, end: Int)
403426
extends AbstractRandomAccessListIterator[E](i, start, end) {
404427
override def remove(): Unit =
405428
throw new UnsupportedOperationException
@@ -411,7 +434,7 @@ private class CopyOnWriteArrayListIterator[E](arraySnapshot: js.Array[E], i: Int
411434
throw new UnsupportedOperationException
412435

413436
protected def get(index: Int): E =
414-
arraySnapshot(index)
437+
CopyOnWriteArrayList.innerImpl.get(arraySnapshot, index)
415438

416439
protected def remove(index: Int): Unit =
417440
throw new UnsupportedOperationException
@@ -422,3 +445,162 @@ private class CopyOnWriteArrayListIterator[E](arraySnapshot: js.Array[E], i: Int
422445
protected def add(index: Int, e: E): Unit =
423446
throw new UnsupportedOperationException
424447
}
448+
449+
object CopyOnWriteArrayList {
450+
451+
/* Get the best implementation of inner array for the given platform.
452+
*
453+
* Use Array[AnyRef] in WebAssembly to avoid JS-interop. In JS, use js.Array.
454+
* It is resizable by nature, so manual resizing is not needed.
455+
*
456+
* `linkTimeIf` is needed here to ensure the optimizer knows
457+
* there is only one implementation of `InnerArrayImpl`, and de-virtualize/inline
458+
* the function calls.
459+
*/
460+
461+
// package private so that `protected def innerSnapshot` can access this field.
462+
private[concurrent] val innerImpl: InnerArrayImpl = {
463+
LinkingInfo.linkTimeIf[InnerArrayImpl](LinkingInfo.isWebAssembly) {
464+
InnerArrayImpl.JArrayImpl
465+
} {
466+
InnerArrayImpl.JSArrayImpl
467+
}
468+
}
469+
470+
private[concurrent] sealed abstract class InnerArrayImpl {
471+
type Repr[E] <: AnyRef
472+
473+
def make[E](initialCapacity: Int): Repr[E]
474+
def length(v: Repr[_]): Int
475+
def get[E](v: Repr[E], index: Int): E
476+
def set[E](v: Repr[E], index: Int, e: E): Unit
477+
def push[E](v: Repr[E], e: E): Repr[E]
478+
def add[E](v: Repr[E], index: Int, e: E): Repr[E]
479+
def add[E](v: Repr[E], index: Int, items: Collection[_ <: E]): Repr[E]
480+
def remove[E](v: Repr[E], index: Int): E
481+
def remove(v: Repr[_], index: Int, count: Int): Unit
482+
def clone[E](v: Repr[E]): Repr[E]
483+
}
484+
485+
private object InnerArrayImpl {
486+
object JSArrayImpl extends InnerArrayImpl {
487+
import scala.scalajs.js
488+
489+
type Repr[E] = js.Array[AnyRef]
490+
491+
@inline def make[E](_initialCapacity: Int): Repr[E] = js.Array[AnyRef]()
492+
493+
@inline def length(v: Repr[_]): Int = v.length
494+
495+
@inline def get[E](v: Repr[E], index: Int): E = v(index).asInstanceOf[E]
496+
497+
@inline def set[E](v: Repr[E], index: Int, e: E): Unit =
498+
v(index) = e.asInstanceOf[AnyRef]
499+
500+
@inline def push[E](v: Repr[E], e: E): Repr[E] = {
501+
v.push(e.asInstanceOf[AnyRef])
502+
v
503+
}
504+
505+
@inline def add[E](v: Repr[E], index: Int, e: E): Repr[E] = {
506+
v.splice(index, 0, e.asInstanceOf[AnyRef])
507+
v
508+
}
509+
510+
@inline def add[E](v: Repr[E], index: Int, items: Collection[_ <: E]): Repr[E] = {
511+
val itemsArray = js.Array[AnyRef]()
512+
items.scalaOps.foreach(e => itemsArray.push(e.asInstanceOf[AnyRef]))
513+
v.splice(index, 0, itemsArray.toSeq: _*)
514+
v
515+
}
516+
517+
@inline def remove[E](v: Repr[E], index: Int): E =
518+
arrayRemoveAndGet(v, index).asInstanceOf[E]
519+
520+
@inline def remove(v: Repr[_], index: Int, count: Int): Unit =
521+
v.splice(index, count)
522+
523+
@inline def clone[E](v: Repr[E]): Repr[E] =
524+
v.jsSlice(0)
525+
}
526+
527+
object JArrayImpl extends InnerArrayImpl {
528+
type Repr[E] = Array[AnyRef]
529+
530+
@inline def make[E](initialCapacity: Int): Repr[E] = {
531+
val v = new Array[AnyRef](roundUpToPowerOfTwo(initialCapacity + 1))
532+
v(0) = 0.asInstanceOf[AnyRef]
533+
v
534+
}
535+
536+
@inline def length(v: Repr[_]): Int = v(0).asInstanceOf[Int]
537+
538+
@inline def get[E](v: Repr[E], index: Int): E = v(index + 1).asInstanceOf[E] // Index 0 stores the length
539+
540+
@inline def set[E](v: Repr[E], index: Int, e: E): Unit =
541+
v(index + 1) = e.asInstanceOf[AnyRef]
542+
543+
@inline def push[E](v: Repr[E], e: E): Repr[E] = {
544+
val size = length(v)
545+
val newArr = ensureCapacity(v, size + 1)
546+
newArr(size + 1) = e.asInstanceOf[AnyRef]
547+
newArr(0) = (size + 1).asInstanceOf[AnyRef]
548+
newArr
549+
}
550+
551+
@inline def add[E](v: Repr[E], index: Int, e: E): Repr[E] = {
552+
val innerIdx = index + 1
553+
val size = length(v)
554+
val newArr = ensureCapacity(v, size + 1)
555+
System.arraycopy(newArr, innerIdx, newArr, innerIdx + 1, size + 1 - innerIdx)
556+
newArr(innerIdx) = e.asInstanceOf[AnyRef]
557+
newArr(0) = (size + 1).asInstanceOf[AnyRef]
558+
newArr
559+
}
560+
561+
@inline def add[E](v: Repr[E], index: Int, items: Collection[_ <: E]): Repr[E] = {
562+
val innerIdx = index + 1
563+
val size = length(v)
564+
val itemsSize = items.size()
565+
val newArr = ensureCapacity(v, size + itemsSize)
566+
System.arraycopy(newArr, innerIdx, newArr, innerIdx + itemsSize, size + 1 - innerIdx)
567+
System.arraycopy(items.toArray(), 0, newArr, innerIdx, itemsSize)
568+
newArr(0) = (size + itemsSize).asInstanceOf[AnyRef]
569+
newArr
570+
}
571+
572+
@inline def remove[E](v: Repr[E], index: Int): E = {
573+
val innerIdx = index + 1
574+
val size = length(v)
575+
val removed = v(innerIdx)
576+
System.arraycopy(v, innerIdx + 1, v, innerIdx, size - innerIdx)
577+
v(size) = null // free reference for GC
578+
v(0) = (size - 1).asInstanceOf[AnyRef]
579+
removed.asInstanceOf[E]
580+
}
581+
582+
@inline def remove(v: Repr[_], index: Int, count: Int): Unit = {
583+
val innerIdx = index + 1
584+
val size = length(v)
585+
val toIndex = innerIdx + count
586+
System.arraycopy(v, toIndex, v, innerIdx, size + 1 - toIndex)
587+
val newSize = size - count
588+
Arrays.fill(v, newSize + 1, newSize + 1 + count, null) // free references for GC
589+
v(0) = newSize.asInstanceOf[AnyRef]
590+
}
591+
592+
@inline def clone[E](v: Repr[E]): Repr[E] =
593+
v.clone()
594+
595+
@inline private def ensureCapacity[E](v: Repr[E], minCapacity: Int): Repr[E] = {
596+
val capacity = v.length - 1
597+
if (capacity < minCapacity) {
598+
val newCapacity = roundUpToPowerOfTwo(minCapacity + 1) // Index 0 stores the length
599+
Arrays.copyOf(v, newCapacity)
600+
} else {
601+
v
602+
}
603+
}
604+
}
605+
}
606+
}

0 commit comments

Comments
 (0)