Skip to content

Commit

Permalink
Improve FieldValuesReader performance (#1564)
Browse files Browse the repository at this point in the history
FieldValuesReader used to wrap Reader which does a map concatenation on every new instance. This change turns FieldValuesReader into a dedicated class for fields. FieldValuesReader can be called for every object in the heap dump so that's a lot.

Method tracing showed a reduction from ~9% of total analysis time to 0.9% when running ProfiledTest#analyzeLargeDump
  • Loading branch information
pyricau committed Sep 9, 2019
1 parent 7fe7d81 commit e032f55
Show file tree
Hide file tree
Showing 4 changed files with 118 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ class ProfiledTest {
leakFinders = listOf(KEYED_WEAK_REFERENCE)
)
SharkLog.d { result.toString() }
// Giving time to stop CPU profiler (otherwise trace won't succeed)
Thread.sleep(20000)
}

}
Expand Down
15 changes: 2 additions & 13 deletions shark-graph/src/main/java/shark/HprofHeapGraph.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package shark

import okio.Buffer
import shark.HeapObject.HeapClass
import shark.HeapObject.HeapInstance
import shark.HeapObject.HeapObjectArray
Expand Down Expand Up @@ -103,18 +102,8 @@ class HprofHeapGraph internal constructor(
return index.fieldName(classId, fieldRecord.nameStringId)
}

internal fun createFieldValuesReader(record: InstanceDumpRecord): FieldValuesReader {
val buffer = Buffer()
buffer.write(record.fieldValues)

val reader = HprofReader(buffer, identifierByteSize)

return object : FieldValuesReader {
override fun readValue(field: FieldRecord): ValueHolder {
return reader.readValue(field.type)
}
}
}
internal fun createFieldValuesReader(record: InstanceDumpRecord) =
FieldValuesReader(record, identifierByteSize)

internal fun className(classId: Long): String {
return index.className(classId)
Expand Down
7 changes: 7 additions & 0 deletions shark-graph/src/main/java/shark/internal/ByteSubArray.kt
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,13 @@ internal class ByteSubArray(
}
}

internal fun ByteArray.readShort(index: Int): Short {
var pos = index
val array = this
val valueAsInt = array[pos++] and 0xff shl 8 or (array[pos] and 0xff)
return valueAsInt.toShort()
}

internal fun ByteArray.readInt(index: Int): Int {
var pos = index
val array = this
Expand Down
109 changes: 107 additions & 2 deletions shark-graph/src/main/java/shark/internal/FieldValuesReader.kt
Original file line number Diff line number Diff line change
@@ -1,8 +1,113 @@
package shark.internal

import shark.HprofRecord.HeapDumpRecord.ObjectRecord.ClassDumpRecord.FieldRecord
import shark.HprofRecord.HeapDumpRecord.ObjectRecord.InstanceDumpRecord
import shark.PrimitiveType
import shark.PrimitiveType.BOOLEAN
import shark.PrimitiveType.BYTE
import shark.PrimitiveType.CHAR
import shark.PrimitiveType.DOUBLE
import shark.PrimitiveType.FLOAT
import shark.PrimitiveType.INT
import shark.PrimitiveType.LONG
import shark.PrimitiveType.SHORT
import shark.ValueHolder
import shark.ValueHolder.BooleanHolder
import shark.ValueHolder.ByteHolder
import shark.ValueHolder.CharHolder
import shark.ValueHolder.DoubleHolder
import shark.ValueHolder.FloatHolder
import shark.ValueHolder.IntHolder
import shark.ValueHolder.LongHolder
import shark.ValueHolder.ReferenceHolder
import shark.ValueHolder.ShortHolder

internal class FieldValuesReader(
private val record: InstanceDumpRecord,
private val identifierByteSize: Int
) {

private var position = 0

fun readValue(field: FieldRecord): ValueHolder {
return when (field.type) {
PrimitiveType.REFERENCE_HPROF_TYPE -> ReferenceHolder(readId())
BOOLEAN_TYPE -> BooleanHolder(readBoolean())
CHAR_TYPE -> CharHolder(readChar())
FLOAT_TYPE -> FloatHolder(readFloat())
DOUBLE_TYPE -> DoubleHolder(readDouble())
BYTE_TYPE -> ByteHolder(readByte())
SHORT_TYPE -> ShortHolder(readShort())
INT_TYPE -> IntHolder(readInt())
LONG_TYPE -> LongHolder(readLong())
else -> throw IllegalStateException("Unknown type ${field.type}")
}
}

private fun readId(): Long {
// As long as we don't interpret IDs, reading signed values here is fine.
return when (identifierByteSize) {
1 -> readByte().toLong()
2 -> readShort().toLong()
4 -> readInt().toLong()
8 -> readLong()
else -> throw IllegalArgumentException("ID Length must be 1, 2, 4, or 8")
}
}

private fun readBoolean(): Boolean {
val value = record.fieldValues[position]
position++
return value != 0.toByte()
}

private fun readByte(): Byte {
val value = record.fieldValues[position]
position++
return value
}

private fun readInt(): Int {
val value = record.fieldValues.readInt(position)
position += 4
return value
}

private fun readShort(): Short {
val value = record.fieldValues.readShort(position)
position += 2
return value
}

private fun readLong(): Long {
val value = record.fieldValues.readLong(position)
position += 8
return value
}

private fun readFloat(): Float {
return Float.fromBits(readInt())
}

private fun readDouble(): Double {
return Double.fromBits(readLong())
}

private fun readChar(): Char {
val string = String(record.fieldValues, position, 2, Charsets.UTF_16BE)
position += 2
return string[0]
}

companion object {
private val BOOLEAN_TYPE = BOOLEAN.hprofType
private val CHAR_TYPE = CHAR.hprofType
private val FLOAT_TYPE = FLOAT.hprofType
private val DOUBLE_TYPE = DOUBLE.hprofType
private val BYTE_TYPE = BYTE.hprofType
private val SHORT_TYPE = SHORT.hprofType
private val INT_TYPE = INT.hprofType
private val LONG_TYPE = LONG.hprofType
}

internal interface FieldValuesReader {
fun readValue(field: FieldRecord): ValueHolder
}

0 comments on commit e032f55

Please sign in to comment.