Create performance counter with excluded counters
This commit is contained in:
@@ -48,6 +48,7 @@ import org.jetbrains.kotlin.types.JetType;
|
||||
import org.jetbrains.kotlin.types.TypeSubstitutor;
|
||||
import org.jetbrains.kotlin.types.expressions.ExpressionTypingContext;
|
||||
import org.jetbrains.kotlin.types.expressions.ExpressionTypingServices;
|
||||
import org.jetbrains.kotlin.types.expressions.ExpressionTypingVisitorDispatcher;
|
||||
import org.jetbrains.kotlin.types.expressions.OperatorConventions;
|
||||
import org.jetbrains.kotlin.util.PerformanceCounter;
|
||||
|
||||
@@ -74,7 +75,7 @@ public class CallResolver {
|
||||
private TaskPrioritizer taskPrioritizer;
|
||||
private AdditionalCheckerProvider additionalCheckerProvider;
|
||||
|
||||
private static final PerformanceCounter callResolvePerfCounter = PerformanceCounter.Companion.create("Call resolve", true);
|
||||
private static final PerformanceCounter callResolvePerfCounter = PerformanceCounter.Companion.create("Call resolve", ExpressionTypingVisitorDispatcher.typeInfoPerfCounter);
|
||||
private static final PerformanceCounter candidatePerfCounter = PerformanceCounter.Companion.create("Call resolve candidate analysis", true);
|
||||
|
||||
@Inject
|
||||
|
||||
+51
-42
@@ -18,6 +18,7 @@ package org.jetbrains.kotlin.types.expressions;
|
||||
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.progress.ProcessCanceledException;
|
||||
import kotlin.jvm.functions.Function0;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.kotlin.diagnostics.DiagnosticUtils;
|
||||
@@ -30,6 +31,7 @@ import org.jetbrains.kotlin.types.DeferredType;
|
||||
import org.jetbrains.kotlin.types.ErrorUtils;
|
||||
import org.jetbrains.kotlin.types.JetType;
|
||||
import org.jetbrains.kotlin.types.expressions.typeInfoFactory.TypeInfoFactoryPackage;
|
||||
import org.jetbrains.kotlin.util.PerformanceCounter;
|
||||
import org.jetbrains.kotlin.util.ReenteringLazyValueComputationException;
|
||||
import org.jetbrains.kotlin.utils.KotlinFrontEndException;
|
||||
|
||||
@@ -38,6 +40,8 @@ import static org.jetbrains.kotlin.resolve.bindingContextUtil.BindingContextUtil
|
||||
|
||||
public class ExpressionTypingVisitorDispatcher extends JetVisitor<JetTypeInfo, ExpressionTypingContext> implements ExpressionTypingInternals {
|
||||
|
||||
public static final PerformanceCounter typeInfoPerfCounter = PerformanceCounter.Companion.create("Type info", true);
|
||||
|
||||
public interface StatementVisitorProvider {
|
||||
ExpressionTypingVisitorForStatements get(@NotNull ExpressionTypingContext context);
|
||||
}
|
||||
@@ -161,50 +165,55 @@ public class ExpressionTypingVisitorDispatcher extends JetVisitor<JetTypeInfo, E
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static JetTypeInfo getTypeInfo(@NotNull JetExpression expression, ExpressionTypingContext context, JetVisitor<JetTypeInfo, ExpressionTypingContext> visitor) {
|
||||
try {
|
||||
JetTypeInfo recordedTypeInfo = BindingContextUtils.getRecordedTypeInfo(expression, context.trace.getBindingContext());
|
||||
if (recordedTypeInfo != null) {
|
||||
return recordedTypeInfo;
|
||||
}
|
||||
JetTypeInfo result;
|
||||
try {
|
||||
result = expression.accept(visitor, context);
|
||||
// Some recursive definitions (object expressions) must put their types in the cache manually:
|
||||
//noinspection ConstantConditions
|
||||
if (context.trace.get(BindingContext.PROCESSED, expression)) {
|
||||
JetType type = context.trace.getBindingContext().getType(expression);
|
||||
return result.replaceType(type);
|
||||
}
|
||||
private static JetTypeInfo getTypeInfo(@NotNull final JetExpression expression, final ExpressionTypingContext context, final JetVisitor<JetTypeInfo, ExpressionTypingContext> visitor) {
|
||||
return typeInfoPerfCounter.time(new Function0<JetTypeInfo>() {
|
||||
@Override
|
||||
public JetTypeInfo invoke() {
|
||||
try {
|
||||
JetTypeInfo recordedTypeInfo = BindingContextUtils.getRecordedTypeInfo(expression, context.trace.getBindingContext());
|
||||
if (recordedTypeInfo != null) {
|
||||
return recordedTypeInfo;
|
||||
}
|
||||
JetTypeInfo result;
|
||||
try {
|
||||
result = expression.accept(visitor, context);
|
||||
// Some recursive definitions (object expressions) must put their types in the cache manually:
|
||||
//noinspection ConstantConditions
|
||||
if (context.trace.get(BindingContext.PROCESSED, expression)) {
|
||||
JetType type = context.trace.getBindingContext().getType(expression);
|
||||
return result.replaceType(type);
|
||||
}
|
||||
|
||||
if (result.getType() instanceof DeferredType) {
|
||||
result = result.replaceType(((DeferredType) result.getType()).getDelegate());
|
||||
}
|
||||
context.trace.record(BindingContext.EXPRESSION_TYPE_INFO, expression, result);
|
||||
}
|
||||
catch (ReenteringLazyValueComputationException e) {
|
||||
context.trace.report(TYPECHECKER_HAS_RUN_INTO_RECURSIVE_PROBLEM.on(expression));
|
||||
result = TypeInfoFactoryPackage.noTypeInfo(context);
|
||||
}
|
||||
if (result.getType() instanceof DeferredType) {
|
||||
result = result.replaceType(((DeferredType) result.getType()).getDelegate());
|
||||
}
|
||||
context.trace.record(BindingContext.EXPRESSION_TYPE_INFO, expression, result);
|
||||
}
|
||||
catch (ReenteringLazyValueComputationException e) {
|
||||
context.trace.report(TYPECHECKER_HAS_RUN_INTO_RECURSIVE_PROBLEM.on(expression));
|
||||
result = TypeInfoFactoryPackage.noTypeInfo(context);
|
||||
}
|
||||
|
||||
context.trace.record(BindingContext.PROCESSED, expression);
|
||||
recordScopeAndDataFlowInfo(context.replaceDataFlowInfo(result.getDataFlowInfo()), expression);
|
||||
return result;
|
||||
}
|
||||
catch (ProcessCanceledException e) {
|
||||
throw e;
|
||||
}
|
||||
catch (KotlinFrontEndException e) {
|
||||
throw e;
|
||||
}
|
||||
catch (Throwable e) {
|
||||
context.trace.report(Errors.EXCEPTION_FROM_ANALYZER.on(expression, e));
|
||||
logOrThrowException(expression, e);
|
||||
return TypeInfoFactoryPackage.createTypeInfo(
|
||||
ErrorUtils.createErrorType(e.getClass().getSimpleName() + " from analyzer"),
|
||||
context
|
||||
);
|
||||
}
|
||||
context.trace.record(BindingContext.PROCESSED, expression);
|
||||
recordScopeAndDataFlowInfo(context.replaceDataFlowInfo(result.getDataFlowInfo()), expression);
|
||||
return result;
|
||||
}
|
||||
catch (ProcessCanceledException e) {
|
||||
throw e;
|
||||
}
|
||||
catch (KotlinFrontEndException e) {
|
||||
throw e;
|
||||
}
|
||||
catch (Throwable e) {
|
||||
context.trace.report(Errors.EXCEPTION_FROM_ANALYZER.on(expression, e));
|
||||
logOrThrowException(expression, e);
|
||||
return TypeInfoFactoryPackage.createTypeInfo(
|
||||
ErrorUtils.createErrorType(e.getClass().getSimpleName() + " from analyzer"),
|
||||
context
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void logOrThrowException(@NotNull JetExpression expression, Throwable e) {
|
||||
|
||||
@@ -17,35 +17,20 @@
|
||||
package org.jetbrains.kotlin.util
|
||||
|
||||
import java.lang.management.ManagementFactory
|
||||
import java.util.*
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
public class PerformanceCounter private constructor (val name: String, val reenterable: Boolean = false) {
|
||||
public abstract class PerformanceCounter protected constructor(val name: String) {
|
||||
companion object {
|
||||
private val threadMxBean = ManagementFactory.getThreadMXBean()
|
||||
private val allCounters = arrayListOf<PerformanceCounter>()
|
||||
|
||||
private val enteredCounters = ThreadLocal<MutableSet<PerformanceCounter>>()
|
||||
|
||||
private var enabled = false
|
||||
|
||||
init {
|
||||
threadMxBean.setThreadCpuTimeEnabled(true)
|
||||
}
|
||||
|
||||
private fun enterCounter(counter: PerformanceCounter): Boolean {
|
||||
var enteredCountersInThread = enteredCounters.get()
|
||||
if (enteredCountersInThread == null) {
|
||||
enteredCountersInThread = hashSetOf(counter)
|
||||
enteredCounters.set(enteredCountersInThread)
|
||||
return true
|
||||
}
|
||||
return enteredCountersInThread.add(counter)
|
||||
}
|
||||
|
||||
private fun leaveCounter(counter: PerformanceCounter) {
|
||||
enteredCounters.get()?.remove(counter)
|
||||
}
|
||||
|
||||
public fun currentThreadCpuTime(): Long = threadMxBean.getCurrentThreadUserTime()
|
||||
|
||||
public fun report(consumer: (String) -> Unit) {
|
||||
@@ -59,13 +44,29 @@ public class PerformanceCounter private constructor (val name: String, val reent
|
||||
enabled = enable
|
||||
}
|
||||
|
||||
public jvmOverloads fun create(name: String, reentable: Boolean = false): PerformanceCounter {
|
||||
return PerformanceCounter(name, reentable)
|
||||
public jvmOverloads fun create(name: String, reenterable: Boolean = false): PerformanceCounter {
|
||||
return if (reenterable)
|
||||
ReenterableCounter(name)
|
||||
else
|
||||
SimpleCounter(name)
|
||||
}
|
||||
|
||||
public fun create(name: String, vararg excluded: PerformanceCounter): PerformanceCounter = CounterWithExclude(name, *excluded)
|
||||
|
||||
protected inline fun <T> getOrPut(threadLocal: ThreadLocal<T>, default: () -> T) : T {
|
||||
var value = threadLocal.get()
|
||||
if (value == null) {
|
||||
value = default()
|
||||
threadLocal.set(value)
|
||||
}
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
protected val excludedFrom: MutableList<CounterWithExclude> = ArrayList()
|
||||
|
||||
private var count: Int = 0
|
||||
private var totalTimeNanos: Long = 0
|
||||
protected var totalTimeNanos: Long = 0
|
||||
|
||||
init {
|
||||
synchronized(allCounters) {
|
||||
@@ -73,27 +74,25 @@ public class PerformanceCounter private constructor (val name: String, val reent
|
||||
}
|
||||
}
|
||||
|
||||
public fun increment() {
|
||||
public final fun increment() {
|
||||
count++
|
||||
}
|
||||
|
||||
public fun time<T>(block: () -> T): T {
|
||||
public final fun time<T>(block: () -> T): T {
|
||||
count++
|
||||
if (!enabled) return block()
|
||||
|
||||
val needTime = !reenterable || enterCounter(this)
|
||||
val startTime = currentThreadCpuTime()
|
||||
excludedFrom.forEach { it.enterExcludedMethod() }
|
||||
try {
|
||||
return block()
|
||||
return countTime(block)
|
||||
}
|
||||
finally {
|
||||
if (needTime) {
|
||||
totalTimeNanos += currentThreadCpuTime() - startTime
|
||||
if (reenterable) leaveCounter(this)
|
||||
}
|
||||
excludedFrom.forEach { it.exitExcludedMethod() }
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract fun countTime<T>(block: () -> T): T
|
||||
|
||||
public fun report(consumer: (String) -> Unit) {
|
||||
if (totalTimeNanos == 0L) {
|
||||
consumer("$name performed $count times")
|
||||
@@ -103,4 +102,112 @@ public class PerformanceCounter private constructor (val name: String, val reent
|
||||
consumer("$name performed $count times, total time $millis ms")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class SimpleCounter(name: String): PerformanceCounter(name) {
|
||||
override fun <T> countTime(block: () -> T): T {
|
||||
val startTime = PerformanceCounter.currentThreadCpuTime()
|
||||
try {
|
||||
return block()
|
||||
}
|
||||
finally {
|
||||
totalTimeNanos += PerformanceCounter.currentThreadCpuTime() - startTime
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class ReenterableCounter(name: String): PerformanceCounter(name) {
|
||||
companion object {
|
||||
private val enteredCounters = ThreadLocal<MutableSet<ReenterableCounter>>()
|
||||
|
||||
private fun enterCounter(counter: ReenterableCounter) = PerformanceCounter.getOrPut(enteredCounters) { HashSet() }.add(counter)
|
||||
|
||||
private fun leaveCounter(counter: ReenterableCounter) {
|
||||
enteredCounters.get()?.remove(counter)
|
||||
}
|
||||
}
|
||||
|
||||
override fun <T> countTime(block: () -> T): T {
|
||||
val startTime = PerformanceCounter.currentThreadCpuTime()
|
||||
val needTime = enterCounter(this)
|
||||
try {
|
||||
return block()
|
||||
}
|
||||
finally {
|
||||
if (needTime) {
|
||||
totalTimeNanos += PerformanceCounter.currentThreadCpuTime() - startTime
|
||||
leaveCounter(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This class allows to calculate pure time for some method excluding some other methods.
|
||||
* For example, we can calculate total time for CallResolver excluding time for getTypeInfo().
|
||||
*
|
||||
* Main and excluded methods may be reenterable.
|
||||
*/
|
||||
private class CounterWithExclude(name: String, vararg excludedCounters: PerformanceCounter): PerformanceCounter(name) {
|
||||
companion object {
|
||||
private val counterToCallStackMapThreadLocal = ThreadLocal<MutableMap<CounterWithExclude, CallStackWithTime>>()
|
||||
|
||||
private fun getCallStack(counter: CounterWithExclude)
|
||||
= PerformanceCounter.getOrPut(counterToCallStackMapThreadLocal) { HashMap() }.getOrPut(counter) { CallStackWithTime() }
|
||||
}
|
||||
|
||||
init {
|
||||
excludedCounters.forEach { it.excludedFrom.add(this) }
|
||||
}
|
||||
|
||||
private val callStack: CallStackWithTime
|
||||
get() = getCallStack(this)
|
||||
|
||||
override fun <T> countTime(block: () -> T): T {
|
||||
totalTimeNanos += callStack.push(true)
|
||||
try {
|
||||
return block()
|
||||
}
|
||||
finally {
|
||||
totalTimeNanos += callStack.pop(true)
|
||||
}
|
||||
}
|
||||
|
||||
fun enterExcludedMethod() {
|
||||
totalTimeNanos += callStack.push(false)
|
||||
}
|
||||
|
||||
fun exitExcludedMethod() {
|
||||
totalTimeNanos += callStack.pop(false)
|
||||
}
|
||||
|
||||
private class CallStackWithTime {
|
||||
private val callStack = Stack<Boolean>()
|
||||
private var intervalStartTime: Long = 0
|
||||
|
||||
fun Stack<Boolean>.peekOrFalse() = if (isEmpty()) false else peek()
|
||||
|
||||
private fun intervalUsefulTime(callStackUpdate: Stack<Boolean>.() -> Unit): Long {
|
||||
val delta = if (callStack.peekOrFalse()) PerformanceCounter.currentThreadCpuTime() - intervalStartTime else 0
|
||||
callStack.callStackUpdate()
|
||||
|
||||
intervalStartTime = PerformanceCounter.currentThreadCpuTime()
|
||||
return delta
|
||||
}
|
||||
|
||||
fun push(usefulCall: Boolean): Long {
|
||||
if (!isEnteredCounter() && !usefulCall) return 0
|
||||
|
||||
return intervalUsefulTime { push(usefulCall) }
|
||||
}
|
||||
|
||||
fun pop(usefulCall: Boolean): Long {
|
||||
if (!isEnteredCounter()) return 0
|
||||
|
||||
assert(callStack.peek() == usefulCall)
|
||||
return intervalUsefulTime { pop() }
|
||||
}
|
||||
|
||||
fun isEnteredCounter(): Boolean = !callStack.isEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user