Skip to content

Commit c73ce02

Browse files
Abduqodiri Qurbonzodamcpiroman
andauthored
Don't allocate temporary buffer in SmallPersistentVector.removeAll (#164)
--------- Co-authored-by: mcpiroman <[email protected]>
1 parent e4600f8 commit c73ce02

File tree

2 files changed

+108
-12
lines changed

2 files changed

+108
-12
lines changed
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
/*
2+
* Copyright 2016-2023 JetBrains s.r.o.
3+
* Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file.
4+
*/
5+
6+
package benchmarks.immutableList
7+
8+
import benchmarks.*
9+
import kotlinx.collections.immutable.PersistentList
10+
import kotlinx.benchmark.*
11+
import kotlinx.collections.immutable.persistentListOf
12+
import kotlin.random.Random
13+
14+
@State(Scope.Benchmark)
15+
open class RemoveAllPredicate {
16+
@Param(BM_1, BM_10, BM_100, BM_1000, BM_10000, BM_100000, BM_1000000, BM_10000000)
17+
var size: Int = 0
18+
19+
private var persistentList = persistentListOf<String>()
20+
private val truePredicate: (String) -> Boolean = { true }
21+
private val falsePredicate: (String) -> Boolean = { false }
22+
private var randomHalfElementsPredicate: (String) -> Boolean = truePredicate
23+
private var randomTenElementsPredicate: (String) -> Boolean = truePredicate
24+
private var randomOneElementPredicate: (String) -> Boolean = truePredicate
25+
private var tailElementsPredicate: (String) -> Boolean = truePredicate
26+
27+
@Setup
28+
fun prepare() {
29+
val randomHalfElements = randomIndexes(size / 2).map { it.toString() }.toHashSet()
30+
randomHalfElementsPredicate = { it in randomHalfElements }
31+
32+
val randomTenElements = randomIndexes(10).map { it.toString() }.toHashSet()
33+
randomTenElementsPredicate = { it in randomTenElements }
34+
35+
val randomOneElement = Random.nextInt(size).toString()
36+
randomOneElementPredicate = { it == randomOneElement }
37+
38+
val tailElements = List(tailSize()) { (size - 1 - it).toString() }.toHashSet()
39+
tailElementsPredicate = { it in tailElements }
40+
41+
val allElements = List(size) { it.toString() }
42+
persistentList = persistentListOf<String>().addAll(allElements)
43+
}
44+
45+
// The benchmarks measure (time and memory spent in `removeAll` operation) / size
46+
//
47+
// Expected time: nearly constant
48+
// Expected memory: nearly constant
49+
50+
/** Removes all elements. */
51+
@Benchmark
52+
fun removeAll_All(): PersistentList<String> {
53+
return persistentList.removeAll(truePredicate)
54+
}
55+
56+
/** Removes no elements. */
57+
@Benchmark
58+
fun removeAll_Non(): PersistentList<String> {
59+
return persistentList.removeAll(falsePredicate)
60+
}
61+
62+
/** Removes half of the elements randomly selected. */
63+
@Benchmark
64+
fun removeAll_RandomHalf(): PersistentList<String> {
65+
return persistentList.removeAll(randomHalfElementsPredicate)
66+
}
67+
68+
/** Removes 10 random elements. */
69+
@Benchmark
70+
fun removeAll_RandomTen(): PersistentList<String> {
71+
return persistentList.removeAll(randomTenElementsPredicate)
72+
}
73+
74+
/** Removes a random element. */
75+
@Benchmark
76+
fun removeAll_RandomOne(): PersistentList<String> {
77+
return persistentList.removeAll(randomOneElementPredicate)
78+
}
79+
80+
/** Removes last [tailSize] elements. */
81+
@Benchmark
82+
fun removeAll_Tail(): PersistentList<String> {
83+
return persistentList.removeAll(tailElementsPredicate)
84+
}
85+
86+
private fun randomIndexes(count: Int): List<Int> {
87+
return List(count) { Random.nextInt(size) }
88+
}
89+
90+
private fun tailSize(): Int {
91+
val bufferSize = 32
92+
return (size and (bufferSize - 1)).let { if (it == 0) bufferSize else it }
93+
}
94+
}

core/commonMain/src/implementations/immutableList/SmallPersistentVector.kt

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -49,30 +49,32 @@ internal class SmallPersistentVector<E>(private val buffer: Array<Any?>) : Immut
4949
}
5050

5151
override fun removeAll(predicate: (E) -> Boolean): PersistentList<E> {
52-
var newBuffer = buffer
5352
var newSize = size
54-
55-
var anyRemoved = false
53+
var removeMask = 0
5654

5755
for (index in 0 until size) {
5856
@Suppress("UNCHECKED_CAST")
5957
val element = buffer[index] as E
6058

6159
if (predicate(element)) {
62-
if (!anyRemoved) {
63-
newBuffer = buffer.copyOf()
64-
newSize = index
65-
66-
anyRemoved = true
67-
}
68-
} else if (anyRemoved) {
69-
newBuffer[newSize++] = element
60+
newSize--
61+
removeMask = removeMask or (1 shl index)
7062
}
7163
}
64+
7265
return when (newSize) {
7366
size -> this
7467
0 -> EMPTY
75-
else -> SmallPersistentVector(newBuffer.copyOfRange(0, newSize))
68+
else -> {
69+
val newBuffer = buffer.copyOf(newSize)
70+
var newIndex = removeMask.countTrailingZeroBits()
71+
for (index in newIndex + 1 until size) {
72+
if ((removeMask ushr index) and 1 == 0) {
73+
newBuffer[newIndex++] = buffer[index]
74+
}
75+
}
76+
SmallPersistentVector(newBuffer)
77+
}
7678
}
7779
}
7880

0 commit comments

Comments
 (0)