From faebb44afee53df230fc2a99abc676a6f7f5fa5f Mon Sep 17 00:00:00 2001 From: Oleg Yukhnevich Date: Wed, 18 Sep 2024 11:57:29 +0300 Subject: [PATCH] HTML rendering memory allocations optimisation (#3800) * Don't parse text to lines during writing in FileWriter * Don't create HTML pages of 32kb size for embedded html contents * Don't create HTML pages of 32kb size during embedding resources links * Don't create HTML if the content node is nullable --- .../dokka/base/renderers/FileWriter.kt | 2 +- .../dokka/base/renderers/html/HtmlRenderer.kt | 31 +++++++++++++------ .../DefaultTemplateModelFactory.kt | 13 +++----- 3 files changed, 27 insertions(+), 19 deletions(-) diff --git a/dokka-subprojects/plugin-base/src/main/kotlin/org/jetbrains/dokka/base/renderers/FileWriter.kt b/dokka-subprojects/plugin-base/src/main/kotlin/org/jetbrains/dokka/base/renderers/FileWriter.kt index 0fb0c7ac5b..e51ea2c2da 100644 --- a/dokka-subprojects/plugin-base/src/main/kotlin/org/jetbrains/dokka/base/renderers/FileWriter.kt +++ b/dokka-subprojects/plugin-base/src/main/kotlin/org/jetbrains/dokka/base/renderers/FileWriter.kt @@ -29,7 +29,7 @@ public class FileWriter( val dir = Paths.get(root.absolutePath, path.dropLastWhile { it != '/' }).toFile() withContext(Dispatchers.IO) { dir.mkdirsOrFail() - Files.write(Paths.get(root.absolutePath, "$path$ext"), text.lines()) + Paths.get(root.absolutePath, "$path$ext").toFile().writeText(text) } } catch (e: Throwable) { context.logger.error("Failed to write $this. ${e.message}") diff --git a/dokka-subprojects/plugin-base/src/main/kotlin/org/jetbrains/dokka/base/renderers/html/HtmlRenderer.kt b/dokka-subprojects/plugin-base/src/main/kotlin/org/jetbrains/dokka/base/renderers/html/HtmlRenderer.kt index 760cd786be..ad66c04047 100644 --- a/dokka-subprojects/plugin-base/src/main/kotlin/org/jetbrains/dokka/base/renderers/html/HtmlRenderer.kt +++ b/dokka-subprojects/plugin-base/src/main/kotlin/org/jetbrains/dokka/base/renderers/html/HtmlRenderer.kt @@ -5,6 +5,9 @@ package org.jetbrains.dokka.base.renderers.html import kotlinx.html.* +import kotlinx.html.consumers.delayed +import kotlinx.html.consumers.onFinalizeMap +import kotlinx.html.stream.HTMLStreamBuilder import kotlinx.html.stream.createHTML import org.jetbrains.dokka.DokkaSourceSetID import org.jetbrains.dokka.Platform @@ -315,12 +318,12 @@ public open class HtmlRenderer( ): List> { var counter = 0 return nodes.toList().map { (sourceSet, elements) -> - val htmlContent = createHTML(prettyPrint = false).prepareForTemplates().div { + val htmlContent = createSmallHTML(prettyPrint = false).prepareForTemplates().div { elements.forEach { buildContentNode(it, pageContext, sourceSet) } }.stripDiv() - sourceSet to createHTML(prettyPrint = false).prepareForTemplates() + sourceSet to createSmallHTML(prettyPrint = false).prepareForTemplates() .div(classes = "content sourceset-dependent-content") { if (counter++ == 0) attributes["data-active"] = "" attributes["data-togglable"] = sourceSet.sourceSetIDs.merged.toString() @@ -344,18 +347,18 @@ public open class HtmlRenderer( val distinct = groupDivergentInstancesWithSourceSet(it.value, it.key, pageContext, beforeTransformer = { instance, _, sourceSet -> - createHTML(prettyPrint = false).prepareForTemplates().div { - instance.before?.let { before -> + instance.before?.let { before -> + createSmallHTML(prettyPrint = false).prepareForTemplates().div { buildContentNode(before, pageContext, sourceSet) - } - }.stripDiv() + }.stripDiv() + } ?: "" }, afterTransformer = { instance, _, sourceSet -> - createHTML(prettyPrint = false).prepareForTemplates().div { - instance.after?.let { after -> + instance.after?.let { after -> + createSmallHTML(prettyPrint = false).prepareForTemplates().div { buildContentNode(after, pageContext, sourceSet) - } - }.stripDiv() + }.stripDiv() + } ?: "" }) val isPageWithOverloadedMembers = pageContext is MemberPage && pageContext.documentables().size > 1 @@ -943,6 +946,14 @@ public open class HtmlRenderer( else -> null } + // same as createHTML but creates StringBuilder of default size instead of using StringBuilder with 32kb size (default) + // it's useful for cases with "embedded html" f.e source-set-dependent content. + // capacity of the StringBuilder was selected after experiments based on generating documentation for kotlin-stdlib + private fun createSmallHTML(prettyPrint: Boolean = true, xhtmlCompatible: Boolean = false): TagConsumer = + HTMLStreamBuilder(StringBuilder(256), prettyPrint, xhtmlCompatible) + .onFinalizeMap { sb, _ -> sb.toString() } + .delayed() + public open fun buildHtml( page: PageNode, resources: List, content: FlowContent.() -> Unit diff --git a/dokka-subprojects/plugin-base/src/main/kotlin/org/jetbrains/dokka/base/renderers/html/innerTemplating/DefaultTemplateModelFactory.kt b/dokka-subprojects/plugin-base/src/main/kotlin/org/jetbrains/dokka/base/renderers/html/innerTemplating/DefaultTemplateModelFactory.kt index 2ecfe12936..f0b8de7d8f 100644 --- a/dokka-subprojects/plugin-base/src/main/kotlin/org/jetbrains/dokka/base/renderers/html/innerTemplating/DefaultTemplateModelFactory.kt +++ b/dokka-subprojects/plugin-base/src/main/kotlin/org/jetbrains/dokka/base/renderers/html/innerTemplating/DefaultTemplateModelFactory.kt @@ -7,6 +7,7 @@ package org.jetbrains.dokka.base.renderers.html.innerTemplating import freemarker.core.Environment import freemarker.template.* import kotlinx.html.* +import kotlinx.html.stream.appendHTML import kotlinx.html.stream.createHTML import org.jetbrains.dokka.DokkaConfiguration import org.jetbrains.dokka.base.DokkaBase @@ -102,10 +103,9 @@ public class DefaultTemplateModelFactory( private val String.isAbsolute: Boolean get() = URI(this).isAbsolute - private fun Appendable.resourcesForPage(pathToRoot: String, resources: List): Unit = - resources.forEach { resource -> - - val resourceHtml = with(createHTML()) { + private fun Appendable.resourcesForPage(pathToRoot: String, resources: List) { + with(appendHTML()) { + resources.forEach { resource -> when { resource.URIExtension == "css" -> @@ -126,13 +126,10 @@ public class DefaultTemplateModelFactory( } resource.isImage() -> link(href = if (resource.isAbsolute) resource else "$pathToRoot$resource") - else -> null } } - if (resourceHtml != null) { - append(resourceHtml) - } } + } }