[JS IR] More detailed time measurement for the IC infrastructure

This commit is contained in:
Alexander Korepanov
2022-10-28 11:03:14 +02:00
committed by Space Team
parent 21c4580e20
commit 731dd9c3e8
7 changed files with 103 additions and 46 deletions
@@ -251,18 +251,10 @@ class K2JsIrCompiler : CLICompiler<K2JSCompilerArguments>() {
compilerInterfaceFactory = { mainModule, cfg -> JsIrCompilerWithIC(mainModule, cfg, arguments.granularity) } compilerInterfaceFactory = { mainModule, cfg -> JsIrCompilerWithIC(mainModule, cfg, arguments.granularity) }
) )
var tp = System.currentTimeMillis() val artifacts = cacheUpdater.actualizeCaches()
val icTimeMessages = mutableListOf("cache updater initialization: ${tp - start}ms")
val artifacts = cacheUpdater.actualizeCaches {
val now = System.currentTimeMillis()
icTimeMessages += "$it: ${now - tp}ms"
tp = now
}
messageCollector.report(INFO, "IC rebuilt overall time: ${System.currentTimeMillis() - start}ms") messageCollector.report(INFO, "IC rebuilt overall time: ${System.currentTimeMillis() - start}ms")
for (icTimeMessage in icTimeMessages) { for ((event, duration) in cacheUpdater.getStopwatchLaps()) {
messageCollector.report(INFO, " $icTimeMessage") messageCollector.report(INFO, " $event: ${(duration / 1e6).toInt()}ms")
} }
var libIndex = 0 var libIndex = 0
@@ -337,17 +329,17 @@ class K2JsIrCompiler : CLICompiler<K2JSCompilerArguments>() {
relativeRequirePath = true relativeRequirePath = true
) )
val outputs = jsExecutableProducer.buildExecutable( val (outputs, rebuiltModules) = jsExecutableProducer.buildExecutable(arguments.irPerModule, outJsProgram = false)
multiModule = arguments.irPerModule,
outJsProgram = false,
rebuildCallback = { rebuiltModule ->
messageCollector.report(INFO, "IC module builder rebuilt module [${File(rebuiltModule).name}]")
}
)
outputs.write(outputDir, outputName) outputs.write(outputDir, outputName)
messageCollector.report(INFO, "Executable production duration (IC): ${System.currentTimeMillis() - beforeIc2Js}ms") messageCollector.report(INFO, "Executable production duration (IC): ${System.currentTimeMillis() - beforeIc2Js}ms")
for ((event, duration) in jsExecutableProducer.getStopwatchLaps()) {
messageCollector.report(INFO, " $event: ${(duration / 1e6).toInt()}ms")
}
for (module in rebuiltModules) {
messageCollector.report(INFO, "IC module builder rebuilt JS for module [${File(module).name}]")
}
return OK return OK
} }
@@ -57,6 +57,8 @@ class CacheUpdater(
private val mainArguments: List<String>?, private val mainArguments: List<String>?,
private val compilerInterfaceFactory: JsIrCompilerICInterfaceFactory private val compilerInterfaceFactory: JsIrCompilerICInterfaceFactory
) { ) {
private val stopwatch = StopwatchIC().apply { startNext("Cache initializing and klibs loading") }
private val signatureHashCalculator = IdSignatureHashCalculator() private val signatureHashCalculator = IdSignatureHashCalculator()
private val libraries = loadLibraries(allModules) private val libraries = loadLibraries(allModules)
@@ -75,8 +77,14 @@ class CacheUpdater(
private val dirtyFileStats = KotlinSourceFileMutableMap<EnumSet<DirtyFileState>>() private val dirtyFileStats = KotlinSourceFileMutableMap<EnumSet<DirtyFileState>>()
init {
stopwatch.stop()
}
fun getDirtyFileStats(): KotlinSourceFileMap<EnumSet<DirtyFileState>> = dirtyFileStats fun getDirtyFileStats(): KotlinSourceFileMap<EnumSet<DirtyFileState>> = dirtyFileStats
fun getStopwatchLaps() = stopwatch.laps
private fun MutableMap<KotlinSourceFile, EnumSet<DirtyFileState>>.addDirtFileStat(srcFile: KotlinSourceFile, state: DirtyFileState) { private fun MutableMap<KotlinSourceFile, EnumSet<DirtyFileState>>.addDirtFileStat(srcFile: KotlinSourceFile, state: DirtyFileState) {
when (val stats = this[srcFile]) { when (val stats = this[srcFile]) {
null -> this[srcFile] = EnumSet.of(state) null -> this[srcFile] = EnumSet.of(state)
@@ -282,6 +290,7 @@ class CacheUpdater(
decl.getter?.let(::addSymbol) decl.getter?.let(::addSymbol)
decl.setter?.let(::addSymbol) decl.setter?.let(::addSymbol)
} }
is IrClass -> { is IrClass -> {
if (addSymbol(decl)) { if (addSymbol(decl)) {
addNestedDeclarations(decl) addNestedDeclarations(decl)
@@ -494,6 +503,7 @@ class CacheUpdater(
val newMetadata = addNewMetadata(dependentLibFile, dependentSrcFile, dependentSrcMetadata) val newMetadata = addNewMetadata(dependentLibFile, dependentSrcFile, dependentSrcMetadata)
newMetadata.importedSignaturesModified = true newMetadata.importedSignaturesModified = true
} }
dependentSignatures.keys != newSignatures -> { dependentSignatures.keys != newSignatures -> {
val newMetadata = addNewMetadata(dependentLibFile, dependentSrcFile, dependentSrcMetadata) val newMetadata = addNewMetadata(dependentLibFile, dependentSrcFile, dependentSrcMetadata)
newMetadata.directDependencies[libFile, srcFile] = newSignatures.associateWith { newMetadata.directDependencies[libFile, srcFile] = newSignatures.associateWith {
@@ -660,15 +670,18 @@ class CacheUpdater(
loadedFragments: Map<KotlinLibraryFile, IrModuleFragment>, loadedFragments: Map<KotlinLibraryFile, IrModuleFragment>,
dirtyFileExports: KotlinSourceFileMap<*> dirtyFileExports: KotlinSourceFileMap<*>
): List<JsIrFragmentAndBinaryAst> { ): List<JsIrFragmentAndBinaryAst> {
stopwatch.startNext("Processing IR - initializing backend context")
val mainModule = loadedFragments[mainLibraryFile] ?: notFoundIcError("main lib loaded fragment", mainLibraryFile) val mainModule = loadedFragments[mainLibraryFile] ?: notFoundIcError("main lib loaded fragment", mainLibraryFile)
val compilerForIC = compilerInterfaceFactory.createCompilerForIC(mainModule, compilerConfiguration) val compilerForIC = compilerInterfaceFactory.createCompilerForIC(mainModule, compilerConfiguration)
// Load declarations referenced during `context` initialization // Load declarations referenced during `context` initialization
linker.loadUnboundSymbols(true) linker.loadUnboundSymbols(true)
stopwatch.startNext("Processing IR - updating intrinsics and builtins dependencies")
updateStdlibIntrinsicDependencies(linker, mainModule, loadedFragments, dirtyFileExports) updateStdlibIntrinsicDependencies(linker, mainModule, loadedFragments, dirtyFileExports)
return compilerForIC.compile( stopwatch.startNext("Processing IR - lowering")
val result = compilerForIC.compile(
allModules = loadedFragments.values, allModules = loadedFragments.values,
dirtyFiles = loadedFragments.flatMap { (libFile, libFragment) -> dirtyFiles = loadedFragments.flatMap { (libFile, libFragment) ->
dirtyFileExports[libFile]?.let { libDirtyFiles -> dirtyFileExports[libFile]?.let { libDirtyFiles ->
@@ -677,54 +690,61 @@ class CacheUpdater(
}, },
mainArguments = mainArguments mainArguments = mainArguments
) )
stopwatch.stop()
return result
} }
fun actualizeCaches(eventCallback: (String) -> Unit = {}): List<ModuleArtifact> { fun actualizeCaches(): List<ModuleArtifact> {
dirtyFileStats.clear() dirtyFileStats.clear()
stopwatch.startNext("Modified files - checking hashes and collecting")
val modifiedFiles = loadModifiedFiles() val modifiedFiles = loadModifiedFiles()
stopwatch.startNext("Modified files - collecting exported signatures")
val dirtyFileExports = collectExportedSymbolsForDirtyFiles(modifiedFiles) val dirtyFileExports = collectExportedSymbolsForDirtyFiles(modifiedFiles)
stopwatch.startNext("Modified files - loading and linking IR")
val jsIrLinkerLoader = JsIrLinkerLoader(compilerConfiguration, mainLibrary, dependencyGraph, irFactory()) val jsIrLinkerLoader = JsIrLinkerLoader(compilerConfiguration, mainLibrary, dependencyGraph, irFactory())
var loadedIr = jsIrLinkerLoader.loadIr(dirtyFileExports) var loadedIr = jsIrLinkerLoader.loadIr(dirtyFileExports)
eventCallback("initial loading of updated files")
var iterations = 0 var iterations = 0
var lastDirtyFiles: KotlinSourceFileMap<KotlinSourceFileExports> = dirtyFileExports var lastDirtyFiles: KotlinSourceFileMap<KotlinSourceFileExports> = dirtyFileExports
while (true) { while (true) {
stopwatch.startNext("Dependencies ($iterations) - calculating transitive hashes for inline functions")
signatureHashCalculator.updateInlineFunctionTransitiveHashes(loadedIr.loadedFragments.values) signatureHashCalculator.updateInlineFunctionTransitiveHashes(loadedIr.loadedFragments.values)
stopwatch.startNext("Dependencies ($iterations) - updating a dependency graph")
val dirtyHeaders = rebuildDirtySourceMetadata(loadedIr.linker, loadedIr.loadedFragments, lastDirtyFiles) val dirtyHeaders = rebuildDirtySourceMetadata(loadedIr.linker, loadedIr.loadedFragments, lastDirtyFiles)
stopwatch.startNext("Dependencies ($iterations) - collecting files with updated exports and imports")
val filesWithModifiedExportsOrImports = collectFilesWithModifiedExportsAndImports(dirtyHeaders) val filesWithModifiedExportsOrImports = collectFilesWithModifiedExportsAndImports(dirtyHeaders)
stopwatch.startNext("Dependencies ($iterations) - collecting exported signatures for files with updated exports and imports")
val filesToRebuild = collectFilesToRebuildSignatures(filesWithModifiedExportsOrImports) val filesToRebuild = collectFilesToRebuildSignatures(filesWithModifiedExportsOrImports)
eventCallback("actualization iteration $iterations")
if (filesToRebuild.isEmpty()) { if (filesToRebuild.isEmpty()) {
break break
} }
loadedIr = jsIrLinkerLoader.loadIr(filesToRebuild)
iterations++
lastDirtyFiles = filesToRebuild lastDirtyFiles = filesToRebuild
dirtyFileExports.copyFilesFrom(filesToRebuild) dirtyFileExports.copyFilesFrom(filesToRebuild)
stopwatch.startNext("Dependencies ($iterations) - loading and linking IR for files with modified exports and imports")
loadedIr = jsIrLinkerLoader.loadIr(filesToRebuild)
iterations++
} }
if (iterations != 0) { if (iterations != 0) {
stopwatch.startNext("Loading and linking all IR")
loadedIr = jsIrLinkerLoader.loadIr(dirtyFileExports) loadedIr = jsIrLinkerLoader.loadIr(dirtyFileExports)
eventCallback("final loading of updated files")
} }
stopwatch.stop()
val rebuiltFragments = compileDirtyFiles(loadedIr.linker, loadedIr.loadedFragments, dirtyFileExports) val rebuiltFragments = compileDirtyFiles(loadedIr.linker, loadedIr.loadedFragments, dirtyFileExports)
eventCallback("updated files processing (lowering)")
stopwatch.startNext("Cache committing")
val artifacts = buildModuleArtifactsAndCommitCache(loadedIr.linker, loadedIr.loadedFragments, rebuiltFragments) val artifacts = buildModuleArtifactsAndCommitCache(loadedIr.linker, loadedIr.loadedFragments, rebuiltFragments)
eventCallback("cache committing") stopwatch.stop()
return artifacts return artifacts
} }
} }
@@ -59,3 +59,27 @@ internal inline fun <E> buildSetUntil(to: Int, builderAction: MutableSet<E>.(Int
internal inline fun <K, V> buildMapUntil(to: Int, builderAction: MutableMap<K, V>.(Int) -> Unit): Map<K, V> { internal inline fun <K, V> buildMapUntil(to: Int, builderAction: MutableMap<K, V>.(Int) -> Unit): Map<K, V> {
return buildMap(to) { repeat(to) { builderAction(it) } } return buildMap(to) { repeat(to) { builderAction(it) } }
} }
internal class StopwatchIC {
private var lapStart: Long = 0
private var lapDescription: String? = null
private val lapsImpl = mutableListOf<Pair<String, Long>>()
val laps: List<Pair<String, Long>>
get() = lapsImpl
fun startNext(description: String) {
val now = System.nanoTime()
stop(now)
lapDescription = description
lapStart = now
}
fun stop(stopTime: Long? = null) {
lapDescription?.let { description ->
lapsImpl += description to ((stopTime ?: System.nanoTime()) - lapStart)
}
lapDescription = null
}
}
@@ -17,13 +17,23 @@ class JsExecutableProducer(
private val caches: List<ModuleArtifact>, private val caches: List<ModuleArtifact>,
private val relativeRequirePath: Boolean private val relativeRequirePath: Boolean
) { ) {
fun buildExecutable(multiModule: Boolean, outJsProgram: Boolean, rebuildCallback: (String) -> Unit = {}) = if (multiModule) { data class BuildResult(val compilationOut: CompilationOutputs, val buildModules: List<String>)
buildMultiModuleExecutable(outJsProgram, rebuildCallback)
} else { private val stopwatch = StopwatchIC()
buildSingleModuleExecutable(outJsProgram, rebuildCallback)
fun getStopwatchLaps() = buildMap {
stopwatch.laps.forEach {
this[it.first] = it.second + (this[it.first] ?: 0L)
}
} }
private fun buildSingleModuleExecutable(outJsProgram: Boolean, rebuildCallback: (String) -> Unit): CompilationOutputs { fun buildExecutable(multiModule: Boolean, outJsProgram: Boolean) = if (multiModule) {
buildMultiModuleExecutable(outJsProgram)
} else {
buildSingleModuleExecutable(outJsProgram)
}
private fun buildSingleModuleExecutable(outJsProgram: Boolean): BuildResult {
val modules = caches.map { cacheArtifact -> cacheArtifact.loadJsIrModule() } val modules = caches.map { cacheArtifact -> cacheArtifact.loadJsIrModule() }
val out = generateSingleWrappedModuleBody( val out = generateSingleWrappedModuleBody(
moduleName = mainModuleName, moduleName = mainModuleName,
@@ -33,31 +43,39 @@ class JsExecutableProducer(
generateCallToMain = true, generateCallToMain = true,
outJsProgram = outJsProgram outJsProgram = outJsProgram
) )
rebuildCallback(mainModuleName) return BuildResult(out, listOf(mainModuleName))
return out
} }
private fun buildMultiModuleExecutable(outJsProgram: Boolean, rebuildCallback: (String) -> Unit): CompilationOutputs { private fun buildMultiModuleExecutable(outJsProgram: Boolean): BuildResult {
val rebuildModules = mutableListOf<String>()
stopwatch.startNext("JS code cache loading")
val jsMultiModuleCache = JsMultiModuleCache(caches) val jsMultiModuleCache = JsMultiModuleCache(caches)
val cachedProgram = jsMultiModuleCache.loadProgramHeadersFromCache() val cachedProgram = jsMultiModuleCache.loadProgramHeadersFromCache()
stopwatch.startNext("Cross module references resolving")
val resolver = CrossModuleDependenciesResolver(moduleKind, cachedProgram.map { it.jsIrHeader }) val resolver = CrossModuleDependenciesResolver(moduleKind, cachedProgram.map { it.jsIrHeader })
val crossModuleReferences = resolver.resolveCrossModuleDependencies(relativeRequirePath) val crossModuleReferences = resolver.resolveCrossModuleDependencies(relativeRequirePath)
stopwatch.startNext("Loading JS IR modules with updated cross module references")
jsMultiModuleCache.loadRequiredJsIrModules(crossModuleReferences) jsMultiModuleCache.loadRequiredJsIrModules(crossModuleReferences)
fun JsMultiModuleCache.CachedModuleInfo.compileModule(moduleName: String, generateCallToMain: Boolean): CompilationOutputs { fun JsMultiModuleCache.CachedModuleInfo.compileModule(moduleName: String, generateCallToMain: Boolean): CompilationOutputs {
if (jsIrHeader.associatedModule == null) { if (jsIrHeader.associatedModule == null) {
stopwatch.startNext("Fetching cached JS code")
val compilationOutputs = jsMultiModuleCache.fetchCompiledJsCode(artifact) val compilationOutputs = jsMultiModuleCache.fetchCompiledJsCode(artifact)
if (compilationOutputs != null) { if (compilationOutputs != null) {
return compilationOutputs return compilationOutputs
} }
// theoretically should never happen
stopwatch.startNext("Loading JS IR modules")
jsIrHeader.associatedModule = artifact.loadJsIrModule() jsIrHeader.associatedModule = artifact.loadJsIrModule()
} }
stopwatch.startNext("Initializing JS imports")
val associatedModule = jsIrHeader.associatedModule ?: icError("can not load module $moduleName") val associatedModule = jsIrHeader.associatedModule ?: icError("can not load module $moduleName")
val crossRef = crossModuleReferences[jsIrHeader] ?: icError("can not find cross references for module $moduleName") val crossRef = crossModuleReferences[jsIrHeader] ?: icError("can not find cross references for module $moduleName")
crossRef.initJsImportsForModule(associatedModule) crossRef.initJsImportsForModule(associatedModule)
stopwatch.startNext("Generating JS code")
val compiledModule = generateSingleWrappedModuleBody( val compiledModule = generateSingleWrappedModuleBody(
moduleName = moduleName, moduleName = moduleName,
moduleKind = moduleKind, moduleKind = moduleKind,
@@ -67,8 +85,10 @@ class JsExecutableProducer(
crossModuleReferences = crossRef, crossModuleReferences = crossRef,
outJsProgram = outJsProgram outJsProgram = outJsProgram
) )
stopwatch.startNext("Committing compiled JS code")
jsMultiModuleCache.commitCompiledJsCode(artifact, compiledModule) jsMultiModuleCache.commitCompiledJsCode(artifact, compiledModule)
rebuildCallback(moduleName) rebuildModules += moduleName
return compiledModule return compiledModule
} }
@@ -79,6 +99,8 @@ class JsExecutableProducer(
val dependencies = cachedOtherModules.map { val dependencies = cachedOtherModules.map {
it.jsIrHeader.externalModuleName to it.compileModule(it.jsIrHeader.externalModuleName, false) it.jsIrHeader.externalModuleName to it.compileModule(it.jsIrHeader.externalModuleName, false)
} }
return CompilationOutputs(mainModule.jsCode, mainModule.jsProgram, mainModule.sourceMap, dependencies) stopwatch.stop()
val compilationOut = CompilationOutputs(mainModule.jsCode, mainModule.jsProgram, mainModule.sourceMap, dependencies)
return BuildResult(compilationOut, rebuildModules)
} }
} }
@@ -190,7 +190,7 @@ abstract class AbstractInvalidationTest : KotlinTestWithEnvironment() {
} }
} }
private fun verifyJsExecutableProducerBuildModules(stepId: Int, gotRebuilt: Set<String>, expectedRebuilt: List<String>) { private fun verifyJsExecutableProducerBuildModules(stepId: Int, gotRebuilt: List<String>, expectedRebuilt: List<String>) {
val got = gotRebuilt.filter { it != STDLIB_MODULE_NAME } val got = gotRebuilt.filter { it != STDLIB_MODULE_NAME }
JUnit4Assertions.assertSameElements(got, expectedRebuilt) { JUnit4Assertions.assertSameElements(got, expectedRebuilt) {
"Mismatched rebuilt modules at step $stepId" "Mismatched rebuilt modules at step $stepId"
@@ -255,8 +255,7 @@ abstract class AbstractInvalidationTest : KotlinTestWithEnvironment() {
relativeRequirePath = true relativeRequirePath = true
) )
val rebuiltModules = mutableSetOf<String>() val (jsOutput, rebuiltModules) = jsExecutableProducer.buildExecutable(multiModule = true, outJsProgram = true)
val jsOutput = jsExecutableProducer.buildExecutable(multiModule = true, outJsProgram = true) { rebuiltModules += it }
verifyJsExecutableProducerBuildModules(projStep.id, rebuiltModules, projStep.dirtyJS) verifyJsExecutableProducerBuildModules(projStep.id, rebuiltModules, projStep.dirtyJS)
verifyJsCode(projStep.id, mainModuleName, jsOutput) verifyJsCode(projStep.id, mainModuleName, jsOutput)
} }
@@ -181,7 +181,7 @@ abstract class AbstractJsKLibABITestCase : KtUsefulTestCase() {
relativeRequirePath = true relativeRequirePath = true
) )
return jsExecutableProducer.buildExecutable(multiModule = true, outJsProgram = true) {} return jsExecutableProducer.buildExecutable(multiModule = true, outJsProgram = true).compilationOut
} }
private fun buildBinaryNoIC( private fun buildBinaryNoIC(
@@ -104,7 +104,7 @@ class JsIrBackendFacade(
caches = testServices.jsIrIncrementalDataProvider.getCaches(), caches = testServices.jsIrIncrementalDataProvider.getCaches(),
relativeRequirePath = false relativeRequirePath = false
) )
jsExecutableProducer.buildExecutable(it.perModule, true) jsExecutableProducer.buildExecutable(it.perModule, true).compilationOut
}, },
tsDefinitions = null tsDefinitions = null
) )