Implement optimized removeRange for ArrayDeque #KT-64956
Test ArrayDeque with size 15. With the existing sizes the removeRange implementation couldn't reach the maximum number of iterations - 3. Benchmark results for ArrayDeque.subList().clear() that uses removeRange() can be found here: https://github.com/qurbonzoda/KotlinArrayDequeBenchmarks/tree/master/results
This commit is contained in:
committed by
Space Team
parent
240a423bed
commit
b67ebf36a2
@@ -10085,6 +10085,8 @@ public final class ArrayDeque<E> : kotlin.collections.AbstractMutableList<E> {
|
|||||||
|
|
||||||
public final fun removeLastOrNull(): E?
|
public final fun removeLastOrNull(): E?
|
||||||
|
|
||||||
|
protected open override fun removeRange(fromIndex: kotlin.Int, toIndex: kotlin.Int): kotlin.Unit
|
||||||
|
|
||||||
public open override fun retainAll(elements: kotlin.collections.Collection<E>): kotlin.Boolean
|
public open override fun retainAll(elements: kotlin.collections.Collection<E>): kotlin.Boolean
|
||||||
|
|
||||||
public open override operator fun set(index: kotlin.Int, element: E): E
|
public open override operator fun set(index: kotlin.Int, element: E): E
|
||||||
|
|||||||
@@ -203,6 +203,11 @@ public actual abstract class AbstractMutableList<E> protected actual constructor
|
|||||||
return list.set(fromIndex + index, element)
|
return list.set(fromIndex + index, element)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun removeRange(fromIndex: Int, toIndex: Int) {
|
||||||
|
list.removeRange(this.fromIndex + fromIndex, this.fromIndex + toIndex)
|
||||||
|
_size -= toIndex - fromIndex
|
||||||
|
}
|
||||||
|
|
||||||
override val size: Int get() = _size
|
override val size: Int get() = _size
|
||||||
|
|
||||||
internal override fun checkIsMutable(): Unit = list.checkIsMutable()
|
internal override fun checkIsMutable(): Unit = list.checkIsMutable()
|
||||||
|
|||||||
@@ -221,6 +221,13 @@ public actual abstract class AbstractMutableList<E> protected actual constructor
|
|||||||
return list.set(fromIndex + index, element)
|
return list.set(fromIndex + index, element)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun removeRange(fromIndex: Int, toIndex: Int) {
|
||||||
|
checkForComodification()
|
||||||
|
list.removeRange(this.fromIndex + fromIndex, this.fromIndex + toIndex)
|
||||||
|
_size -= toIndex - fromIndex
|
||||||
|
modCount = list.modCount
|
||||||
|
}
|
||||||
|
|
||||||
override val size: Int
|
override val size: Int
|
||||||
get() {
|
get() {
|
||||||
checkForComodification()
|
checkForComodification()
|
||||||
|
|||||||
@@ -517,12 +517,9 @@ public class ArrayDeque<E> : AbstractMutableList<E> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public override fun clear() {
|
public override fun clear() {
|
||||||
val tail = internalIndex(size)
|
if (isNotEmpty()) {
|
||||||
if (head < tail) {
|
val tail = internalIndex(size)
|
||||||
elementData.fill(null, head, tail)
|
nullifyNonEmpty(head, tail)
|
||||||
} else if (isNotEmpty()) {
|
|
||||||
elementData.fill(null, head, elementData.size)
|
|
||||||
elementData.fill(null, 0, tail)
|
|
||||||
}
|
}
|
||||||
head = 0
|
head = 0
|
||||||
size = 0
|
size = 0
|
||||||
@@ -550,9 +547,85 @@ public class ArrayDeque<E> : AbstractMutableList<E> {
|
|||||||
return toArray(arrayOfNulls<Any?>(size))
|
return toArray(arrayOfNulls<Any?>(size))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun removeRange(fromIndex: Int, toIndex: Int) {
|
||||||
|
AbstractList.checkRangeIndexes(fromIndex, toIndex, size)
|
||||||
|
// TODO: registerModification()
|
||||||
|
|
||||||
|
val length = toIndex - fromIndex
|
||||||
|
when (length) {
|
||||||
|
0 -> return
|
||||||
|
size -> {
|
||||||
|
clear()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
1 -> {
|
||||||
|
removeAt(fromIndex)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fromIndex < size - toIndex) {
|
||||||
|
// closer to the first element -> shift preceding elements
|
||||||
|
removeRangeShiftPreceding(fromIndex, toIndex)
|
||||||
|
|
||||||
|
val newHead = positiveMod(head + length)
|
||||||
|
nullifyNonEmpty(head, newHead)
|
||||||
|
head = newHead
|
||||||
|
} else {
|
||||||
|
// closer to the last element -> shift succeeding elements
|
||||||
|
removeRangeShiftSucceeding(fromIndex, toIndex)
|
||||||
|
|
||||||
|
val tail = internalIndex(size)
|
||||||
|
nullifyNonEmpty(negativeMod(tail - length), tail)
|
||||||
|
}
|
||||||
|
|
||||||
|
size -= length
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun removeRangeShiftPreceding(fromIndex: Int, toIndex: Int) {
|
||||||
|
var copyFromIndex = internalIndex(fromIndex - 1) // upper bound of range, inclusive
|
||||||
|
var copyToIndex = internalIndex(toIndex - 1) // upper bound of range, inclusive
|
||||||
|
var copyCount = fromIndex
|
||||||
|
|
||||||
|
while (copyCount > 0) { // maximum 3 iterations
|
||||||
|
val segmentLength = minOf(copyCount, copyFromIndex + 1, copyToIndex + 1)
|
||||||
|
elementData.copyInto(elementData, copyToIndex - segmentLength + 1, copyFromIndex - segmentLength + 1, copyFromIndex + 1)
|
||||||
|
|
||||||
|
copyFromIndex = negativeMod(copyFromIndex - segmentLength)
|
||||||
|
copyToIndex = negativeMod(copyToIndex - segmentLength)
|
||||||
|
copyCount -= segmentLength
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun removeRangeShiftSucceeding(fromIndex: Int, toIndex: Int) {
|
||||||
|
var copyFromIndex = internalIndex(toIndex) // lower bound of range, inclusive
|
||||||
|
var copyToIndex = internalIndex(fromIndex) // lower bound of range, inclusive
|
||||||
|
var copyCount = size - toIndex
|
||||||
|
|
||||||
|
while (copyCount > 0) { // maximum 3 iterations
|
||||||
|
val segmentLength = minOf(copyCount, elementData.size - copyFromIndex, elementData.size - copyToIndex)
|
||||||
|
elementData.copyInto(elementData, copyToIndex, copyFromIndex, copyFromIndex + segmentLength)
|
||||||
|
|
||||||
|
copyFromIndex = positiveMod(copyFromIndex + segmentLength)
|
||||||
|
copyToIndex = positiveMod(copyToIndex + segmentLength)
|
||||||
|
copyCount -= segmentLength
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** If `internalFromIndex == internalToIndex`, the buffer is considered full and all elements are nullified. */
|
||||||
|
private fun nullifyNonEmpty(internalFromIndex: Int, internalToIndex: Int) {
|
||||||
|
if (internalFromIndex < internalToIndex) {
|
||||||
|
elementData.fill(null, internalFromIndex, internalToIndex)
|
||||||
|
} else {
|
||||||
|
elementData.fill(null, internalFromIndex, elementData.size)
|
||||||
|
elementData.fill(null, 0, internalToIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// for testing
|
// for testing
|
||||||
internal fun <T> testToArray(array: Array<T>): Array<T> = toArray(array)
|
internal fun <T> testToArray(array: Array<T>): Array<T> = toArray(array)
|
||||||
internal fun testToArray(): Array<Any?> = toArray()
|
internal fun testToArray(): Array<Any?> = toArray()
|
||||||
|
internal fun testRemoveRange(fromIndex: Int, toIndex: Int) = removeRange(fromIndex, toIndex)
|
||||||
|
|
||||||
internal companion object {
|
internal companion object {
|
||||||
private val emptyElementData = emptyArray<Any?>()
|
private val emptyElementData = emptyArray<Any?>()
|
||||||
|
|||||||
@@ -352,7 +352,7 @@ class ArrayDequeTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun testArrayDeque(test: (bufferSize: Int, dequeSize: Int, head: Int, tail: Int) -> Unit) {
|
private fun testArrayDeque(test: (bufferSize: Int, dequeSize: Int, head: Int, tail: Int) -> Unit) {
|
||||||
for (bufferSize in listOf(0, 2, 8)) {
|
for (bufferSize in listOf(0, 2, 8, 15)) {
|
||||||
for (dequeSize in 0..bufferSize) {
|
for (dequeSize in 0..bufferSize) {
|
||||||
for (tail in 0 until bufferSize) {
|
for (tail in 0 until bufferSize) {
|
||||||
val head = tail - dequeSize
|
val head = tail - dequeSize
|
||||||
@@ -609,6 +609,40 @@ class ArrayDequeTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun removeRange() = testArrayDeque { bufferSize: Int, dequeSize: Int, head: Int, tail: Int ->
|
||||||
|
for (fromIndex in 0..dequeSize) {
|
||||||
|
for (toIndex in fromIndex..dequeSize) {
|
||||||
|
val deque = generateArrayDeque(head, tail, bufferSize).apply { testRemoveRange(fromIndex, toIndex) }
|
||||||
|
|
||||||
|
val length = toIndex - fromIndex
|
||||||
|
val expectedHead = when {
|
||||||
|
length == 0 -> head
|
||||||
|
length == dequeSize -> 0
|
||||||
|
fromIndex < dequeSize - toIndex -> head + length // shift preceding elements
|
||||||
|
else -> // shift succeeding elements
|
||||||
|
if (tail <= length - 1)
|
||||||
|
head + bufferSize // head becomes positive(head < tail)
|
||||||
|
else
|
||||||
|
head
|
||||||
|
}
|
||||||
|
|
||||||
|
val expectedElements = (head until tail).toMutableList().apply {
|
||||||
|
repeat(length) { removeAt(fromIndex) }
|
||||||
|
}
|
||||||
|
|
||||||
|
deque.internalStructure { actualHead, actualElements ->
|
||||||
|
assertEquals(
|
||||||
|
expectedHead,
|
||||||
|
actualHead,
|
||||||
|
"bufferSize: $bufferSize, head: $head, tail: $tail, fromIndex: $fromIndex, toIndex: $toIndex"
|
||||||
|
)
|
||||||
|
assertEquals(expectedElements, actualElements.toList())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Suppress("INVISIBLE_MEMBER")
|
@Suppress("INVISIBLE_MEMBER")
|
||||||
@Test
|
@Test
|
||||||
fun toArray() {
|
fun toArray() {
|
||||||
|
|||||||
Reference in New Issue
Block a user