diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/CallResolver.java b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/CallResolver.java index 385e72e4201..befe4e08b78 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/CallResolver.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/CallResolver.java @@ -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 diff --git a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/ExpressionTypingVisitorDispatcher.java b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/ExpressionTypingVisitorDispatcher.java index 2230f498232..48a22d06915 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/ExpressionTypingVisitorDispatcher.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/ExpressionTypingVisitorDispatcher.java @@ -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 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 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 visitor) { + return typeInfoPerfCounter.time(new Function0() { + @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) { diff --git a/compiler/frontend/src/org/jetbrains/kotlin/util/PerformanceCounter.kt b/compiler/frontend/src/org/jetbrains/kotlin/util/PerformanceCounter.kt index 216dd5c530d..96ebd30ca8d 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/util/PerformanceCounter.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/util/PerformanceCounter.kt @@ -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() - private val enteredCounters = ThreadLocal>() - 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 getOrPut(threadLocal: ThreadLocal, default: () -> T) : T { + var value = threadLocal.get() + if (value == null) { + value = default() + threadLocal.set(value) + } + return value } } + protected val excludedFrom: MutableList = 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(block: () -> T): T { + public final fun time(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(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") } } -} \ No newline at end of file +} + +private class SimpleCounter(name: String): PerformanceCounter(name) { + override fun 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>() + + private fun enterCounter(counter: ReenterableCounter) = PerformanceCounter.getOrPut(enteredCounters) { HashSet() }.add(counter) + + private fun leaveCounter(counter: ReenterableCounter) { + enteredCounters.get()?.remove(counter) + } + } + + override fun 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>() + + 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 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() + private var intervalStartTime: Long = 0 + + fun Stack.peekOrFalse() = if (isEmpty()) false else peek() + + private fun intervalUsefulTime(callStackUpdate: Stack.() -> 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() + } +}