[HN Gopher] How we made JSON.stringify more than twice as fast
___________________________________________________________________
How we made JSON.stringify more than twice as fast
Author : emschwartz
Score : 111 points
Date : 2025-08-04 14:09 UTC (8 hours ago)
(HTM) web link (v8.dev)
(TXT) w3m dump (v8.dev)
| hinkley wrote:
| JSON encoding is a huge impediment to interprocess communication
| in NodeJS.
|
| Sooner or later is seems like everyone gets the idea of reducing
| event loop stalls in their NodeJS code by trying to offload it to
| another thread, only to discover they've tripled the CPU load in
| the main thread.
|
| I've seen people stringify arrays one entry at a time. Sounds
| like maybe they are doing that internally now.
|
| If anything I would encourage the V8 team to go farther with
| this. Can you avoid bailing out for subsets of data? What about
| the CString issue? Does this bring faststr back from the dead?
| jcdavis wrote:
| Based off of my first ever forays into node performance
| analysis last year, JSON.stringify was one of the biggest
| impediments to _just about everything_ around performant node
| services. The fact that everyone uses stringify to for dict
| keys, the fact that apollo /express just serializes the entire
| response into a string instead of incrementally streaming it
| back (I think there are some possible workarounds for this, but
| they seemed very hacky)
|
| As someone who has come from a JVM/go background, I was kinda
| shocked how amateur hour it felt tbh.
| MehdiHK wrote:
| > JSON.stringify was one of the biggest impediments to just
| about everything around performant node services
|
| That's what I experienced too. But I think the deeper problem
| is Node's cooperative multitasking model. A preemptive
| multitasking (like Go) wouldn't block the whole event-loop
| (other concurrent tasks) during serializing a large response
| (often the case with GraphQL, but possible with any other API
| too). Yeah, it does kinda feel like amateur hour.
| hinkley wrote:
| > Based off of my first ever forays into node performance
| analysis last year, JSON.stringify was one of the biggest
| impediments to just about everything around performant node
| services
|
| Just so. It is, or at least can be, the plurality of the
| sequential part of any Amdahl's Law calculation for Nodejs.
|
| I'm curious if any of the 'side effect free' commentary in
| this post is about moving parts of the JSON calculation off
| of the event loop. That would certainly be very interesting
| if true.
|
| However for concurrency reasons I suspect it could never be
| fully off. The best you could likely do is have multiple
| threads converting the object while the event loop remains
| blocked. Not entirely unlike concurrent marking in the JVM.
| dmit wrote:
| Node is the biggest impediment to performant Node services.
| The entire value proposition is "What if you could hire
| people who write code in the most popular programming
| language in the world?" Well, guess what
| brundolf wrote:
| Yeah. I think I've only ever found one situation where
| offloading work to a worker saved more time than was lost
| through serializing/deserializing. Doing heavy work often means
| working with a huge set of data- which means the cost of
| passing that data via messages scales with the benefits of
| parallelizing the work.
| hinkley wrote:
| I think the clues are all there in the MDN docs for web
| workers. Having a worker act as a forward proxy for services;
| you send it a URL, it decides if it needs to make a network
| request, it cooks down the response for you and sends you the
| condensed result.
|
| Most tasks take more memory in the middle that at the
| beginning and end. And if you're sharing memory between
| processes that can only communicate by setting bytes, then
| the memory at the beginning and end represents the
| communication overhead. The latency.
|
| But this is also why things like p-limit work - they pause an
| array of arbitrary tasks during the induction phase, before
| the data expands into a complex state that has to be retained
| in memory concurrent with all of its peers. By partially
| linearizing you put a clamp on peak memory usage that
| Promise.all(arr.map(...)) does not, not just the thundering
| herd fix.
| dwattttt wrote:
| Now to just write the processing code in something that
| compiles to WebAssembly, and you can start copying and sending
| ArrayBuffers to your workers!
|
| Or I guess you can do it without the WebAssembly step.
| MutedEstate45 wrote:
| I really like seeing the segmented buffer approach. It's
| basically the rope data structure trick I used to hand-roll in
| userland with libraries like fast-json-stringify, now native and
| way cleaner. Have you run into the bailout conditions much? Any
| replacer, space, or custom .toJSON() kicks you back to the slow
| path?
| jonas21 wrote:
| The part that was most surprising to me was how much the
| performance of serializing floating-point numbers has improved,
| even just in the past decade [1].
|
| [1] https://github.com/jk-jeon/dragonbox?tab=readme-ov-
| file#perf...
| iouser wrote:
| Did you run any tests/regressions against the security problems
| that are common with parsers? Seems like the solution might be at
| risk of creating CVEs later
| taeric wrote:
| I confess that I'm at a bit of a loss to know what sort of side
| effects would be common when serializing something? Is there an
| obvious class of reasons for this that I'm just accidentally
| ignoring right off?
| vinkelhake wrote:
| A simple example is `toJSON`. If an object defines that method,
| it'll get invoked automatically by JSON.stringify and it could
| have arbitrary side effects.
|
| I think it's less about side effects being common when
| serializing, just that their fast path avoids anything that
| _could_ have side effects (like toJSON).
|
| The article touches briefly on this.
| kevingadd wrote:
| Calling a property getter can have side effects, so if you
| serialize an object with a getter you have to be very cautious
| to make sure nothing weird happens underneath you during
| serialization.
|
| People have exploited this sort of side effect to get bug
| bounties before via type confusion attacks, iirc.
| monster_truck wrote:
| I don't think v8 gets enough praise. It is fucking insane how
| fast javascript can be these days
| andyferris wrote:
| Yeah, it is quite impressive!
|
| It's a real example of "you can solve just about anything with
| a billion dollars" though :)
|
| I'd prefer JavaScript kept evolving (think "strict", but
| "stricter", "stricter still", ...) to a simpler and easier to
| compile/JIT language.
| ayaros wrote:
| Yes, this is what I want too. Give me "stricter" mode.
| ot wrote:
| The SWAR escaping algorithm [1] is very similar to the one I
| implemented in Folly JSON a few years ago [2]. The latter works
| on 8 byte words instead of 4 bytes, and it also returns the
| position of the first byte that needs escaping, so that the fast
| path does not add noticeable overhead on escape-heavy strings.
|
| [1]
| https://source.chromium.org/chromium/_/chromium/v8/v8/+/5cbc...
|
| [2]
| https://github.com/facebook/folly/commit/2f0cabfb48b8a8df84f...
| pyrolistical wrote:
| > Optimizing the underlying temporary buffer
|
| So array list instead of array?
___________________________________________________________________
(page generated 2025-08-04 23:00 UTC)