Reduce compiler runtime overhead of MaxStackFrameSizeAndLocalsCalculator

Profiling the compilation of kotlinx.serialization, MaxStackFrameSizeAndLocalsCalculator
causes ~7% of the runtime to be spent in java.lang.Object.hashCode

This is through two uses:
- visitMaxs(..) has a pushed hashSet that causes ~2%
- labelWrappersMap used to attach additional data to asm Labels, causes ~ 5%

visitMaxs can use the existing SmartSet (not to be confused with SmartHashSet)

Analysis of the visitMaxs HashSet creation & sizes:

| What               | Amount |
| calls to visitMaxs | 4416   |
| max pushed	     | 158    |
| median pushed	     | 4      |
| average pushed     | 5.20   |
| stddev pushed	     | 7.66   |
| 90 percentile      | 10     |

Analysis of labelWrappersMap creation & sizes:

| What               | Amount |
| ------------------ | ------ |
| hashtables created | 4006   |
| max entries        | 175    |
| median entries     | 5      |
| average entries    | 6.10   |
| stdev entries      | 8.28   |
| 90 percentile      | 11     |

testing with a non hash based map using an array for keys and an array for values
showed that the cost of MaxStackFrameSizeAndLocalsCalculator became neglible to
the overall running time.

SmartIdentityTable is a Map like structure that uses reference identity for keys.
It uses 2 arrays to store keys & values until the number of entries stored is larger than 10.
At that point it switches to using an IdentityHashMap.

This structure can be used instead of HashMap when reference identity can be used and
the number of entries inserted is small (<= 10) on average, drastically reducing the overhead
of calls to Object.hashCode

Between the two changes, compilation of kotlinx.serialization through kotlinc
commandline decreased from 14 seconds to 11 seconds on my machine
This commit is contained in:
Jeffrey van Gogh
2019-06-07 15:51:13 -07:00
committed by Alexander Udalov
parent 0b19a4a32b
commit 1dbe487077
3 changed files with 198 additions and 4 deletions
@@ -0,0 +1,86 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.utils
import java.util.IdentityHashMap
/**
* [SmartIdentityTable] is a Map like structure that uses reference identity for keys.
* It uses 2 arrays to store keys & values until the number of entries stored is larger than [ARRAY_UNTIL_SIZE].
* At that point it switches to using an IdentityHashMap.
*
* This structure can be used instead of [HashMap] when reference identity can be used and
* the number of entries inserted is small (<= [ARRAY_UNTIL_SIZE]) on average, drastically reducing the overhead
* of calls to [Object.hashCode].
*
* The implementation of [SmartIdentityTable] is not synchronized.
*/
class SmartIdentityTable<K, V> {
private var keysArray: MutableList<K>? = ArrayList(ARRAY_UNTIL_SIZE)
private var valuesArray: MutableList<V>? = ArrayList(ARRAY_UNTIL_SIZE)
private var largeMap: IdentityHashMap<K, V>? = null
val size: Int
get() = keysArray?.size ?: largeMap!!.size
operator fun get(key: K): V? {
return keysArray?.let {
for ((index, k) in it.withIndex()) {
if (k === key) {
return valuesArray!![index]
}
}
return null
} ?: largeMap!![key]
}
operator fun set(key: K, value: V): V? {
val ka = keysArray
if (ka != null) {
val va = valuesArray!!
// scan for existing keys in array
for (i in 0 until ka.size) {
if (ka[i] === key) {
val tmp = va[i]
va[i] = value
return tmp
}
}
// if a new key, and array has room
if (ka.size < ARRAY_UNTIL_SIZE) {
ka.add(key)
va.add(value)
return null
}
convertToHashMap()
}
// all other cases, fallback to IdentityHashMap implementation
return largeMap!!.put(key, value)
}
private fun convertToHashMap() {
val map = IdentityHashMap<K, V>()
val ka = keysArray!!
val va = valuesArray!!
for (i in 0 until ka.size) {
map[ka[i]] = va[i]
}
largeMap = map
keysArray = null
valuesArray = null
}
fun getOrCreate(key: K, factory: () -> V): V {
return this[key] ?: factory().also {
this[key] = it
}
}
companion object {
private const val ARRAY_UNTIL_SIZE = 10
}
}