perf: use StringBuilderWriter across renderers and stdlib escape paths#891
Open
He-Pin wants to merge 2 commits into
Open
perf: use StringBuilderWriter across renderers and stdlib escape paths#891He-Pin wants to merge 2 commits into
He-Pin wants to merge 2 commits into
Conversation
For Val.Str inputs, directly call BaseRenderer.escape with a StringBuilderWriter instead of going through the full Materializer.stringify -> Renderer -> StringWriter pipeline. This avoids: Renderer allocation, CharBuilder allocation, StringWriter synchronization, and the visitor dispatch overhead. For non-string inputs, use StringBuilderWriter instead of java.io.StringWriter to avoid synchronization overhead.
… stdlib Motivation: Continues the synchronous StringWriter elimination started by PR databricks#875 (TomlRenderer), databricks#889 (std.deepJoin), and the existing commit in this PR (escapeStringJson/Python). Remaining hot paths still allocate a java.io.StringWriter with its synchronized StringBuffer. Modification: - StringBuilderWriter: expose getBuilder for direct StringBuilder access (YamlRenderer trims trailing spaces via length/charAt/setLength). - Renderer / PythonRenderer: default `out` constructor parameter changes from java.io.StringWriter to StringBuilderWriter. Format.scala's complex-type Renderer paths and Val.Obj.renderString automatically benefit via the new default. - YamlRenderer: full visitor type-parameter migration from StringWriter to StringBuilderWriter; outBuffer switches from StringBuffer to StringBuilder (same length/setLength/charAt interface). - PrettyYamlRenderer: default `out` parameter to StringBuilderWriter; also drops a dead StringWriter allocation in the quoted-string branch (the writer's result was never consumed — `quotedStr` is used instead). - std.escapeStringXML: replace internal StringWriter with StringBuilderWriter, pre-sized to input length + 16. - bench: MaterializerBenchmark switches its renderWith helper to StringBuilderWriter to match the new constructor signatures. Result: - Eliminates StringBuffer monitor acquisition on every char/string write across all remaining renderers (Yaml/PrettyYaml/Python/Json default paths) and std.escapeStringXML. - Removes one dead StringWriter allocation per quoted-string in PrettyYamlRenderer. - All JVM tests pass (sjsonnet.jvm[3.3.7].test).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation
Continues the synchronous
java.io.StringWriterelimination started by PRs #875 (TomlRenderer) and #889 (std.deepJoin). EachStringWriterwrite goes throughStringBuffer'ssynchronizedmonitor — pure overhead for the single-threaded code paths every renderer takes.This PR landed in two parts:
std.escapeStringJson/std.escapeStringPython.StringBuilderWritersubstitution across the remaining stdlib escape path (std.escapeStringXML), the YAML / PrettyYAML / Python / JSON renderers, andRenderer/PythonRendererdefaultoutparameters (which transitively benefitVal.Obj.renderString,Format.scala's%rcomplex-type path, etc.).Modification
Part 1: stdlib escape entry points
std.escapeStringJson: forVal.Strinput, callBaseRenderer.escapedirectly with aStringBuilderWriter, bypassingMaterializer.stringifydispatch,Rendererallocation, andCharBuilderallocation. For non-string input, useStringBuilderWriterinstead ofjava.io.StringWriter.std.escapeStringPython: sameStringBuilderWritersubstitution.Part 2: rest of the renderer surface
std.escapeStringXML: replace internalStringWriterwith a pre-sizedStringBuilderWriter.Renderer/PythonRenderer: defaultoutparameter changes fromjava.io.StringWritertoStringBuilderWriter. Call sites that rely on the default (Val.Obj.renderStringinVal.scala,Format.scala's complex-type%rpath) inherit the optimization with no signature change.YamlRenderer: full visitor type-parameter migration fromStringWritertoStringBuilderWriter;outBufferswitches fromStringBuffertoStringBuilder(samelength/setLength/charAtinterface).StringBuilderWritergains a smallgetBuilderaccessor soYamlRenderer's trailing-space trim can still reach into the buffer.PrettyYamlRenderer: defaultoutparameter toStringBuilderWriter. Also drops a deadStringWriterallocation in the quoted-string branch — the writer's result was never consumed (quotedStris used instead).bench:MaterializerBenchmark.renderWithupdated to construct aStringBuilderWriterto match the new constructor signatures.Result
StringBuffermonitor acquisition on every char / string write across all remaining renderers (YAML / PrettyYAML / Python / default-JSON) and all threestd.escapeString*entry points.StringWriterallocation per quoted-string inPrettyYamlRenderer.Benchmarks (Scala Native, Apple M3 Pro)
std.escapeStringJsonmicro-benchmark via hyperfine:Tests