Skip to content

Add event-loop-friendly async serialization and deserialization - #360

Open
kyken wants to merge 3 commits into
ravionhq:mainfrom
kyken:feat/async-serialize-deserialize
Open

kyken wants to merge 3 commits into
ravionhq:mainfrom
kyken:feat/async-serialize-deserialize

Conversation

@kyken

@kyken kyken commented Sep 18, 2026

Copy link
Copy Markdown

Motivation

Processing large values synchronously can block the event loop, delaying timers, I/O, and other application work.

This change adds asynchronous APIs for transport-boundary processing without changing the existing synchronous APIs.

Summary

  • Add serializeAsync and deserializeAsync as instance, static, and named exports.
  • Yield to the event loop while traversing and copying large values.
  • Add a configurable yieldRate with a default of 1024.
  • Preserve support for special values, circular references, referential equality, custom transformers, and in-place deserialization.
  • Keep the synchronous serialize and deserialize APIs unchanged.

Measured effect

Benchmark setup

Item Value
Host Mac15,13 (Apple M3, arm64)
Memory 16 GiB
OS macOS 26.6.2
Runtime Node.js v20.15.0
Package Compiled output imported from dist/index.js
Input Array of 500,000 numeric primitives
Payload 3,388,900 bytes as UTF-8 JSON

The synchronous serialized payload was created once and reused for all deserialization cases.

Each case was run once in the same Node.js process. The benchmark was started with --expose-gc; global.gc() was called between cases, followed by a 20 ms settling period.

Immediately before each operation, a 1 ms interval timer was started. The benchmark recorded elapsed time, timer callbacks until completion, and the maximum timer gap. The maximum gap includes time during which the operation blocked timer callbacks.

Operation Elapsed Timer callbacks Maximum timer gap
serialize 219.47 ms 0 219.69 ms
serializeAsync, yieldRate=1 6430.25 ms 6311 63.15 ms
serializeAsync, yieldRate=1024 243.69 ms 165 70.02 ms
serializeAsync, yieldRate=8192 237.12 ms 60 89.49 ms
serializeAsync, yieldRate=65536 234.15 ms 6 121.99 ms
deserialize 2.48 ms 0 2.52 ms
deserializeAsync, yieldRate=1 6180.00 ms 6160 20.40 ms
deserializeAsync, yieldRate=1024 29.79 ms 30 1.51 ms
deserializeAsync, yieldRate=8192 26.07 ms 25 2.24 ms
deserializeAsync, yieldRate=65536 24.88 ms 6 10.59 ms

These are single-run directional measurements and may vary with CPU load, garbage collection, and operating-system scheduling.

yieldRate=1 causes substantial scheduling overhead. In this measurement, the default yieldRate=1024 keeps serialization close to the synchronous baseline while allowing timer callbacks to run. Higher values reduce overhead further but allow longer uninterrupted processing periods.

Impact and overhead

The overhead applies only to the asynchronous APIs. Existing synchronous consumers are unaffected.

yieldRate controls the trade-off between responsiveness and throughput: lower values yield more frequently but add overhead, while higher values reduce overhead but allow longer uninterrupted processing. The default is 1024 and can be adjusted for the workload.

@kyken
kyken requested a review from Skn0tt as a code owner September 18, 2026 04:21
@greptile-apps

greptile-apps Bot commented Sep 18, 2026

Copy link
Copy Markdown

PR author is not in the allowed authors list.

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.

1 participant