Skip to content

More efficient parsing and serialization - #502

Open
plokhotnyuk wants to merge 8 commits into
VirtusLab:mainfrom
plokhotnyuk:main
Open

More efficient parsing and serialization#502
plokhotnyuk wants to merge 8 commits into
VirtusLab:mainfrom
plokhotnyuk:main

Conversation

@plokhotnyuk

Copy link
Copy Markdown
Contributor

Below are benchmark results using Oracle GraalVM 25 on MacBook M5

Before:

[info] Benchmark                                                        (name)   Mode  Cnt        Score     Error   Units
[info] ParsingBench.fromStringToEvents                     docker-compose.yaml  thrpt   25    46736.555 ± 558.045   ops/s
[info] ParsingBench.fromStringToEvents:gc.alloc.rate       docker-compose.yaml  thrpt   25     4740.680 ±  56.544  MB/sec
[info] ParsingBench.fromStringToEvents:gc.alloc.rate.norm  docker-compose.yaml  thrpt   25   106384.126 ±   0.002    B/op
[info] ParsingBench.fromStringToEvents:gc.count            docker-compose.yaml  thrpt   25       50.000            counts
[info] ParsingBench.fromStringToEvents:gc.time             docker-compose.yaml  thrpt   25       45.000                ms
[info] ParsingBench.fromStringToEvents                                geo.yaml  thrpt   25     7797.191 ±  81.347   ops/s
[info] ParsingBench.fromStringToEvents:gc.alloc.rate                  geo.yaml  thrpt   25     5343.425 ±  55.762  MB/sec
[info] ParsingBench.fromStringToEvents:gc.alloc.rate.norm             geo.yaml  thrpt   25   718736.756 ±   0.008    B/op
[info] ParsingBench.fromStringToEvents:gc.count                       geo.yaml  thrpt   25       57.000            counts
[info] ParsingBench.fromStringToEvents:gc.time                        geo.yaml  thrpt   25       50.000                ms
[info] ParsingBench.fromStringToNode                       docker-compose.yaml  thrpt   25    39831.328 ± 182.796   ops/s
[info] ParsingBench.fromStringToNode:gc.alloc.rate         docker-compose.yaml  thrpt   25     4755.505 ±  21.984  MB/sec
[info] ParsingBench.fromStringToNode:gc.alloc.rate.norm    docker-compose.yaml  thrpt   25   125216.148 ±   0.001    B/op
[info] ParsingBench.fromStringToNode:gc.count              docker-compose.yaml  thrpt   25       50.000            counts
[info] ParsingBench.fromStringToNode:gc.time               docker-compose.yaml  thrpt   25       44.000                ms
[info] ParsingBench.fromStringToNode                                  geo.yaml  thrpt   25     4393.558 ±  20.437   ops/s
[info] ParsingBench.fromStringToNode:gc.alloc.rate                    geo.yaml  thrpt   25     6090.129 ±  28.325  MB/sec
[info] ParsingBench.fromStringToNode:gc.alloc.rate.norm               geo.yaml  thrpt   25  1453705.341 ±   0.006    B/op
[info] ParsingBench.fromStringToNode:gc.count                         geo.yaml  thrpt   25       68.000            counts
[info] ParsingBench.fromStringToNode:gc.time                          geo.yaml  thrpt   25       50.000                ms
[info] ParsingBench.fromStringToStruct                     docker-compose.yaml  thrpt   25    30661.500 ± 183.371   ops/s
[info] ParsingBench.fromStringToStruct:gc.alloc.rate       docker-compose.yaml  thrpt   25     5198.347 ±  45.195  MB/sec
[info] ParsingBench.fromStringToStruct:gc.alloc.rate.norm  docker-compose.yaml  thrpt   25   177803.392 ± 792.030    B/op
[info] ParsingBench.fromStringToStruct:gc.count            docker-compose.yaml  thrpt   25       59.000            counts
[info] ParsingBench.fromStringToStruct:gc.time             docker-compose.yaml  thrpt   25       51.000                ms
[info] ParsingBench.fromStringToStruct                                geo.yaml  thrpt   25     2738.700 ±  13.551   ops/s
[info] ParsingBench.fromStringToStruct:gc.alloc.rate                  geo.yaml  thrpt   25     7480.484 ±  37.146  MB/sec
[info] ParsingBench.fromStringToStruct:gc.alloc.rate.norm             geo.yaml  thrpt   25  2864496.856 ±  57.681    B/op
[info] ParsingBench.fromStringToStruct:gc.count                       geo.yaml  thrpt   25       81.000            counts
[info] ParsingBench.fromStringToStruct:gc.time                        geo.yaml  thrpt   25       46.000                ms

After:

[info] Benchmark                                                        (name)   Mode  Cnt        Score     Error   Units
[info] ParsingBench.fromStringToEvents                     docker-compose.yaml  thrpt   25    55977.332 ± 298.644   ops/s
[info] ParsingBench.fromStringToEvents:gc.alloc.rate       docker-compose.yaml  thrpt   25     5656.691 ±  30.228  MB/sec
[info] ParsingBench.fromStringToEvents:gc.alloc.rate.norm  docker-compose.yaml  thrpt   25   105984.105 ±   0.001    B/op
[info] ParsingBench.fromStringToEvents:gc.count            docker-compose.yaml  thrpt   25       60.000            counts
[info] ParsingBench.fromStringToEvents:gc.time             docker-compose.yaml  thrpt   25       37.000                ms
[info] ParsingBench.fromStringToEvents                                geo.yaml  thrpt   25     7755.329 ±  55.361   ops/s
[info] ParsingBench.fromStringToEvents:gc.alloc.rate                  geo.yaml  thrpt   25     5311.990 ±  37.928  MB/sec
[info] ParsingBench.fromStringToEvents:gc.alloc.rate.norm             geo.yaml  thrpt   25   718368.759 ±   0.006    B/op
[info] ParsingBench.fromStringToEvents:gc.count                       geo.yaml  thrpt   25       56.000            counts
[info] ParsingBench.fromStringToEvents:gc.time                        geo.yaml  thrpt   25       47.000                ms
[info] ParsingBench.fromStringToNode                       docker-compose.yaml  thrpt   25    48454.088 ± 168.912   ops/s
[info] ParsingBench.fromStringToNode:gc.alloc.rate         docker-compose.yaml  thrpt   25     5437.207 ±  18.964  MB/sec
[info] ParsingBench.fromStringToNode:gc.alloc.rate.norm    docker-compose.yaml  thrpt   25   117688.121 ±   0.001    B/op
[info] ParsingBench.fromStringToNode:gc.count              docker-compose.yaml  thrpt   25       60.000            counts
[info] ParsingBench.fromStringToNode:gc.time               docker-compose.yaml  thrpt   25       41.000                ms
[info] ParsingBench.fromStringToNode                                  geo.yaml  thrpt   25     4354.844 ±  15.967   ops/s
[info] ParsingBench.fromStringToNode:gc.alloc.rate                    geo.yaml  thrpt   25     6032.219 ±  22.210  MB/sec
[info] ParsingBench.fromStringToNode:gc.alloc.rate.norm               geo.yaml  thrpt   25  1452705.352 ±   0.005    B/op
[info] ParsingBench.fromStringToNode:gc.count                         geo.yaml  thrpt   25       68.000            counts
[info] ParsingBench.fromStringToNode:gc.time                          geo.yaml  thrpt   25       53.000                ms
[info] ParsingBench.fromStringToStruct                     docker-compose.yaml  thrpt   25    36156.194 ± 116.783   ops/s
[info] ParsingBench.fromStringToStruct:gc.alloc.rate       docker-compose.yaml  thrpt   25     5828.650 ±  18.286  MB/sec
[info] ParsingBench.fromStringToStruct:gc.alloc.rate.norm  docker-compose.yaml  thrpt   25   169067.363 ±  40.865    B/op
[info] ParsingBench.fromStringToStruct:gc.count            docker-compose.yaml  thrpt   25       60.000            counts
[info] ParsingBench.fromStringToStruct:gc.time             docker-compose.yaml  thrpt   25       46.000                ms
[info] ParsingBench.fromStringToStruct                                geo.yaml  thrpt   25     2730.227 ±   9.010   ops/s
[info] ParsingBench.fromStringToStruct:gc.alloc.rate                  geo.yaml  thrpt   25     7454.276 ±  24.591  MB/sec
[info] ParsingBench.fromStringToStruct:gc.alloc.rate.norm             geo.yaml  thrpt   25  2863357.667 ±  41.049    B/op
[info] ParsingBench.fromStringToStruct:gc.count                       geo.yaml  thrpt   25       80.000            counts
[info] ParsingBench.fromStringToStruct:gc.time                        geo.yaml  thrpt   25       46.000                ms

@lbialy
lbialy requested review from lbialy and a balanced review from Copilot August 15, 2026 09:26

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Optimizes YAML parsing and serialization by reducing allocations and streamlining hot paths.

Changes:

  • Reworks reader, tokenizer, parser, and composer internals for efficiency.
  • Reuses immutable serialization events and builders.
  • Updates decoder expectations for sequence-backed values.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
DecoderSuite.scala Accepts generic sequences in decoder test.
TokenizerContext.scala Replaces indentation deque with an array stack.
Tokenizer.scala Optimizes tokenization paths and allocations.
Token.scala Simplifies scalar construction.
Reader.scala Optimizes character traversal; introduces CR handling defects.
ParserImpl.scala Reduces parser allocations and chained operations.
Event.scala Uses direct event and metadata construction.
Composer.scala Optimizes event composition and alias storage.
SerializerImpl.scala Reuses immutable events and simplifies traversal.
PresenterImpl.scala Uses direct event construction.
Suppressed comments (2)

core/shared/src/main/scala/org/virtuslab/yaml/internal/load/reader/Reader.scala:114

  • The CRLF lookahead mutates i before confirming the next character is \n. Thus whitespace such as "\rvalue" silently drops the v; inspect with charAt(i) first and only advance the extra position for a confirmed CRLF pair.
        c == '\n' || c == '\r' && {
          i < len && {
            c = in.charAt(i)
            i += 1
            c == '\n'
          }

core/shared/src/main/scala/org/virtuslab/yaml/internal/load/reader/Reader.scala:134

  • read() must return and consume the current character, but for a standalone \r followed by text this probe overwrites c, advances twice, and returns the next character. Only consume and return \n when a CRLF pair is actually present.
      c == '\n' || c == '\r' && {
        i < len && {
          c = in.charAt(i)
          i += 1
          c == '\n'
        }

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +84 to +89
c == '\n' || c == '\r' && {
i < len && {
c = in.charAt(i)
i += 1
c == '\n'
}
@plokhotnyuk
plokhotnyuk force-pushed the main branch 3 times, most recently from 9924d9e to b92b978 Compare September 1, 2026 07:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants