Skip to content

Commit 6e8b652

Browse files
author
Abduqodiri Qurbonzoda
committed
Add canonicalization benchmarks
1 parent 30c1ba3 commit 6e8b652

File tree

5 files changed

+338
-3
lines changed

5 files changed

+338
-3
lines changed
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
/*
2+
* Copyright 2016-2019 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.immutableMap
7+
8+
import benchmarks.*
9+
import kotlinx.collections.immutable.PersistentMap
10+
import kotlinx.collections.immutable.persistentMapOf
11+
import org.openjdk.jmh.annotations.*
12+
import org.openjdk.jmh.infra.Blackhole
13+
14+
15+
/**
16+
* Benchmarks below measure how the trie canonicalization affects performance of the persistent map.
17+
*
18+
* Benchmark methods firstly remove some entries of the [persistentMap].
19+
* The expected height of the resulting map is half of the [persistentMap]'s expected height.
20+
* Then another operation is applied on the resulting map.
21+
*
22+
* If the [persistentMap] does not maintain its canonical form,
23+
* after removing some entries its actual average height will become bigger then its expected height.
24+
* Thus, many operations on the resulting map will be slower.
25+
*/
26+
@State(Scope.Thread)
27+
open class Canonicalization {
28+
@Param(BM_1, BM_10, BM_100, BM_1000, BM_10000, BM_100000, BM_1000000)
29+
var size: Int = 0
30+
31+
@Param(HASH_IMPL, ORDERED_IMPL)
32+
var implementation = ""
33+
34+
@Param(ASCENDING_HASH_CODE, RANDOM_HASH_CODE, COLLISION_HASH_CODE)
35+
var hashCodeType = ""
36+
37+
private var keys = listOf<IntWrapper>()
38+
private var keysToRemove = listOf<IntWrapper>()
39+
private var persistentMap = persistentMapOf<IntWrapper, String>()
40+
41+
/**
42+
* Expected height of this persistent map is equal to the [persistentMap]'s expected height divided by 2.
43+
* Obtained by removing some entries of the [persistentMap].
44+
*/
45+
private var halfHeightPersistentMap = persistentMapOf<IntWrapper, String>()
46+
47+
@Setup(Level.Trial)
48+
fun prepare() {
49+
keys = generateKeys(hashCodeType, size)
50+
persistentMap = persistentMapPut(implementation, keys)
51+
52+
val entriesToLeave = sizeForHalfHeight(persistentMap)
53+
keysToRemove = keys.shuffled().subList(fromIndex = entriesToLeave, toIndex = size)
54+
55+
halfHeightPersistentMap = persistentMapRemove(persistentMap, keysToRemove)
56+
}
57+
58+
/**
59+
* Removes `keysToRemove.size` entries of the [persistentMap] and
60+
* then puts `keysToRemove.size` new entries.
61+
*
62+
* Measures mean time and memory spent per (roughly one) `remove` and `put` operations.
63+
*
64+
* Expected time: [Remove.remove] + [putAfterRemove]
65+
* Expected memory: [Remove.remove] + [putAfterRemove]
66+
*/
67+
@Benchmark
68+
fun removeAndPut(): PersistentMap<IntWrapper, String> {
69+
var map = persistentMapRemove(persistentMap, keysToRemove)
70+
71+
for (key in keysToRemove) {
72+
map = map.put(key, "new value")
73+
}
74+
75+
return map
76+
}
77+
78+
/**
79+
* Removes `keysToRemove.size` entries of the [persistentMap] and
80+
* then iterates keys of the resulting map several times until iterating [size] elements.
81+
*
82+
* Measures mean time and memory spent per (roughly one) `remove` and `next` operations.
83+
*
84+
* Expected time: [Remove.remove] + [iterateKeysAfterRemove]
85+
* Expected memory: [Remove.remove] + [iterateKeysAfterRemove]
86+
*/
87+
@Benchmark
88+
fun removeAndIterateKeys(bh: Blackhole) {
89+
val map = persistentMapRemove(persistentMap, keysToRemove)
90+
91+
var count = 0
92+
while (count < size) {
93+
for (e in map) {
94+
bh.consume(e)
95+
96+
if (++count == size)
97+
break
98+
}
99+
}
100+
}
101+
102+
/**
103+
* Puts `size - halfHeightPersistentMap.size` new entries to the [Canonicalization.halfHeightPersistentMap].
104+
*
105+
* Measures mean time and memory spent per (roughly one) `put` operation.
106+
*
107+
* Expected time: [Put.put]
108+
* Expected memory: [Put.put]
109+
*/
110+
@Benchmark
111+
fun putAfterRemove(): PersistentMap<IntWrapper, String> {
112+
var map = halfHeightPersistentMap
113+
114+
repeat(size - halfHeightPersistentMap.size) { index ->
115+
map = map.put(keys[index], "new value")
116+
}
117+
118+
return map
119+
}
120+
121+
/**
122+
* Iterates keys of the [Canonicalization.halfHeightPersistentMap] several times until iterating [size] elements.
123+
*
124+
* Measures mean time and memory spent per `iterate` operation.
125+
*
126+
* Expected time: [Iterate.iterateKeys] with [Iterate.size] = `halfHeightPersistentMap.size`
127+
* Expected memory: [Iterate.iterateKeys] with [Iterate.size] = `halfHeightPersistentMap.size`
128+
*/
129+
@Benchmark
130+
fun iterateKeysAfterRemove(bh: Blackhole) {
131+
var count = 0
132+
while (count < size) {
133+
for (e in halfHeightPersistentMap) {
134+
bh.consume(e)
135+
136+
if (++count == size)
137+
break
138+
}
139+
}
140+
}
141+
}

benchmarks-mpp/src/jvmMain/kotlin/benchmarks/immutableMap/builder/utils.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,12 @@ fun persistentMapBuilderPut(
1919

2020
var map = emptyPersistentMap<IntWrapper, String>(implementation)
2121
for (index in 0 until immutableSize) {
22-
map = map.put(keys[index], "some element")
22+
map = map.put(keys[index], "some value")
2323
}
2424

2525
val builder = map.builder()
2626
for (index in immutableSize until keys.size) {
27-
builder[keys[index]] = "some element"
27+
builder[keys[index]] = "some value"
2828
}
2929

3030
return builder

benchmarks-mpp/src/jvmMain/kotlin/benchmarks/immutableMap/utils.kt

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import benchmarks.*
99
import kotlinx.collections.immutable.PersistentMap
1010
import kotlinx.collections.immutable.persistentHashMapOf
1111
import kotlinx.collections.immutable.persistentMapOf
12+
import kotlin.math.ceil
13+
import kotlin.math.log
1214

1315

1416
fun <K, V> emptyPersistentMap(implementation: String): PersistentMap<K, V> = when (implementation) {
@@ -20,7 +22,32 @@ fun <K, V> emptyPersistentMap(implementation: String): PersistentMap<K, V> = whe
2022
fun <K> persistentMapPut(implementation: String, keys: List<K>): PersistentMap<K, String> {
2123
var map = emptyPersistentMap<K, String>(implementation)
2224
for (key in keys) {
23-
map = map.put(key, "some element")
25+
map = map.put(key, "some value")
2426
}
2527
return map
2628
}
29+
30+
fun <K> persistentMapRemove(persistentMap: PersistentMap<K, String>, keys: List<K>): PersistentMap<K, String> {
31+
var map = persistentMap
32+
for (key in keys) {
33+
map = map.remove(key)
34+
}
35+
return map
36+
}
37+
38+
39+
private const val branchingFactor = 32
40+
private const val logBranchingFactor = 5
41+
42+
private fun expectedHeightOfPersistentMapWithSize(size: Int): Int {
43+
return ceil(log(size.toDouble(), branchingFactor.toDouble())).toInt()
44+
}
45+
46+
/**
47+
* Returns the size of a persistent map whose expected height is
48+
* half of the specified [persistentMap]'s expected height.
49+
*/
50+
fun sizeForHalfHeight(persistentMap: PersistentMap<*, *>): Int {
51+
val expectedHeight = expectedHeightOfPersistentMapWithSize(persistentMap.size)
52+
return 1 shl ((expectedHeight / 2) * logBranchingFactor)
53+
}
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
/*
2+
* Copyright 2016-2019 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.immutableSet
7+
8+
import benchmarks.*
9+
import kotlinx.collections.immutable.PersistentSet
10+
import kotlinx.collections.immutable.persistentSetOf
11+
import org.openjdk.jmh.annotations.*
12+
import org.openjdk.jmh.infra.Blackhole
13+
14+
15+
/**
16+
* Benchmarks below measure how the trie canonicalization affects performance of the persistent set.
17+
*
18+
* Benchmark methods firstly remove some elements of the [persistentSet].
19+
* The expected height of the resulting set is half of the [persistentSet]'s expected height.
20+
* Then another operation is applied on the resulting set.
21+
*
22+
* If the [persistentSet] does not maintain its canonical form,
23+
* after removing some elements its actual average height will become bigger then its expected height.
24+
* Thus, many operations on the resulting set will be slower.
25+
*/
26+
@State(Scope.Thread)
27+
open class Canonicalization {
28+
@Param(BM_1, BM_10, BM_100, BM_1000, BM_10000, BM_100000, BM_1000000)
29+
var size: Int = 0
30+
31+
@Param(HASH_IMPL, ORDERED_IMPL)
32+
var implementation = ""
33+
34+
@Param(ASCENDING_HASH_CODE, RANDOM_HASH_CODE, COLLISION_HASH_CODE)
35+
var hashCodeType = ""
36+
37+
private var elements = listOf<IntWrapper>()
38+
private var elementsToRemove = listOf<IntWrapper>()
39+
private var persistentSet = persistentSetOf<IntWrapper>()
40+
41+
/**
42+
* Expected height of this persistent set is equal to the [persistentSet]'s expected height divided by 2.
43+
* Obtained by removing some elements of the [persistentSet].
44+
*/
45+
private var halfHeightPersistentSet = persistentSetOf<IntWrapper>()
46+
47+
@Setup(Level.Trial)
48+
fun prepare() {
49+
elements = generateElements(hashCodeType, size)
50+
persistentSet = persistentSetAdd(implementation, elements)
51+
52+
val elementsToLeave = sizeForHalfHeight(persistentSet)
53+
elementsToRemove = elements.shuffled().subList(fromIndex = elementsToLeave, toIndex = size)
54+
55+
halfHeightPersistentSet = persistentSetRemove(persistentSet, elementsToRemove)
56+
}
57+
58+
/**
59+
* Removes `elementsToRemove.size` elements of the [persistentSet] and
60+
* then puts `elementsToRemove.size` new elements.
61+
*
62+
* Measures mean time and memory spent per (roughly one) `remove` and `add` operations.
63+
*
64+
* Expected time: [Remove.remove] + [addAfterRemove]
65+
* Expected memory: [Remove.remove] + [addAfterRemove]
66+
*/
67+
@Benchmark
68+
fun removeAndAdd(): PersistentSet<IntWrapper> {
69+
var set = persistentSetRemove(persistentSet, elementsToRemove)
70+
71+
for (element in elementsToRemove) {
72+
set = set.add(element)
73+
}
74+
75+
return set
76+
}
77+
78+
/**
79+
* Removes `elementsToRemove.size` elements of the [persistentSet] and
80+
* then iterates elements of the resulting set several times until iterating [size] elements.
81+
*
82+
* Measures mean time and memory spent per (roughly one) `remove` and `next` operations.
83+
*
84+
* Expected time: [Remove.remove] + [iterateAfterRemove]
85+
* Expected memory: [Remove.remove] + [iterateAfterRemove]
86+
*/
87+
@Benchmark
88+
fun removeAndIterate(bh: Blackhole) {
89+
val set = persistentSetRemove(persistentSet, elementsToRemove)
90+
91+
var count = 0
92+
while (count < size) {
93+
for (e in set) {
94+
bh.consume(e)
95+
96+
if (++count == size)
97+
break
98+
}
99+
}
100+
}
101+
102+
/**
103+
* Adds `size - halfHeightPersistentSet.size` new elements to the [Canonicalization.halfHeightPersistentSet].
104+
*
105+
* Measures mean time and memory spent per (roughly one) `add` operation.
106+
*
107+
* Expected time: [Add.add]
108+
* Expected memory: [Add.add]
109+
*/
110+
@Benchmark
111+
fun addAfterRemove(): PersistentSet<IntWrapper> {
112+
var set = halfHeightPersistentSet
113+
114+
for (element in elementsToRemove) {
115+
set = set.add(element)
116+
}
117+
118+
return set
119+
}
120+
121+
/**
122+
* Iterates elements of the [Remove.halfHeightPersistentSet] several times until iterating [size] elements.
123+
*
124+
* Measures mean time and memory spent per `iterate` operation.
125+
*
126+
* Expected time: [Iterate.iterate] with [Iterate.size] = `halfHeightPersistentSet.size`
127+
* Expected memory: [Iterate.iterate] with [Iterate.size] = `halfHeightPersistentSet.size`
128+
*/
129+
@Benchmark
130+
fun iterateAfterRemove(bh: Blackhole) {
131+
var count = 0
132+
while (count < size) {
133+
for (e in halfHeightPersistentSet) {
134+
bh.consume(e)
135+
136+
if (++count == size)
137+
break
138+
}
139+
}
140+
}
141+
}

benchmarks-mpp/src/jvmMain/kotlin/benchmarks/immutableSet/utils.kt

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import benchmarks.*
99
import kotlinx.collections.immutable.PersistentSet
1010
import kotlinx.collections.immutable.persistentHashSetOf
1111
import kotlinx.collections.immutable.persistentSetOf
12+
import kotlin.math.ceil
13+
import kotlin.math.log
1214

1315

1416
fun <E> emptyPersistentSet(implementation: String): PersistentSet<E> = when (implementation) {
@@ -24,3 +26,27 @@ fun <E> persistentSetAdd(implementation: String, elements: List<E>): PersistentS
2426
}
2527
return set
2628
}
29+
30+
fun <E> persistentSetRemove(persistentSet: PersistentSet<E>, elements: List<E>): PersistentSet<E> {
31+
var set = persistentSet
32+
for (element in elements) {
33+
set = set.remove(element)
34+
}
35+
return set
36+
}
37+
38+
private const val branchingFactor = 32
39+
private const val logBranchingFactor = 5
40+
41+
private fun expectedHeightOfPersistentSetWithSize(size: Int): Int {
42+
return ceil(log(size.toDouble(), branchingFactor.toDouble())).toInt()
43+
}
44+
45+
/**
46+
* Returns the size of a persistent set whose expected height is
47+
* half of the specified [persistentSet]'s expected height.
48+
*/
49+
fun sizeForHalfHeight(persistentSet: PersistentSet<*>): Int {
50+
val expectedHeight = expectedHeightOfPersistentSetWithSize(persistentSet.size)
51+
return 1 shl ((expectedHeight / 2) * logBranchingFactor)
52+
}

0 commit comments

Comments
 (0)