You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Yeah, this is also an opportunity to align the implementations between Node.js and the browser, might be a good code-cleanup. Did some quick testing, but I was unable to get things to work in a 15 min time-span (had some trouble running the tests on a browser as well).
I suspect atob and btoa will have more performance overhead than a custom base64 implementation due to the need for string preprocessing (to match node.js behavior) and the extra conversion back and forth between a binary string a buffer.
I don't have proof because I haven't benchmarked it, but the implementation in #349 is probably the fastest/best we can get.
edit: On second thought, if we optimistically parse base64 with atob and fallback to preprocessing on error, it may be faster? Something like:
In other words, the node.js atob function runs with O(n^2) time complexity! How code this irresponsible made it into the node.js codebase is beyond me.
I'll have to benchmark this in an actual web browser to get some real numbers.
In other words, the node.js atob function runs with O(n^2) time complexity! How code this irresponsible made it into the node.js codebase is beyond me.
Oh damn, nice find! I wonder how other run-times such as Bun and Deno compare in this regard. Did you log an issue with the Node.js team?
In other words, the node.js atob function runs with O(n^2) time complexity! How code this irresponsible made it into the node.js codebase is beyond me.
Because Buffer exists as the better alternative (from Node.js' perspective that is). The atob function is marked as Legacy:
Stability: 3 - Legacy. Although this feature is unlikely to be removed and is still covered by semantic versioning guarantees, it is no longer actively maintained, and other alternatives are available.
So perhaps take the Buffer.from() approach for Node.js and see if the perf for atob in browsers is acceptable? Would prevent the need to roll a bunch of custom code.
@vweevers, it's still trivial to optimize it with a lookup table. I don't see "legacy" as an excuse to be lazy.
Interesting that it mentions the performance issue in the code but not the docs (where it might be the most helpful).
In any case, filing an issue to have it optimized now does nothing, because we'll never know whether we're using the optimized version or the unoptimized version. @jonkoops' approach makes more sense.
@dcousens, performance in chromium seems optimal. I'm seeing a 1.4x speedup compared to the JS impl. I don't know if I'll get around to testing firefox anytime soon as I don't have good tooling to run FF headless, but I'll start working on a revision of #349 that uses atob in the browser and Buffer.from -> toString in node.
I have added a comment in nodejs/node#38433 (comment), and I suspect that's where we can leave it. We are targeting browsers, and the fact that atob and btoa is slow in node is only a loss for node, not this library.
The overhead of Buffer -> binary string -> base64 is one of the things I was worried about. I guess we have to make a decision whether we want to keep the base64 serialization code, or take the perf hit and use btoa.
I have to agree with @dcousens that it's not really an issue that some of these functions are slow in Node.js, it is very clear that compatibility and performance with existing popular web APIs is an afterthought there. And it doesn't really matter anyways, as this library will almost always be used in a web context.
Thanks for your hard work @chjj, I appreciate you.
There's now native support in browsers for base64 and hex encoding/decoding on Uint8Array, although for decoding it's more strict than Buffer and so can require preprocessing. However, it is much more optimized than atob/btoa at least in Chrome, since it uses simd, an approach not available to a JS implementation.
Activity
dcousens commentedon Jan 23, 2024
@jonkoops do we actually need it for browsers in 2024? Could we simply use
atobandbtoanow?jonkoops commentedon Jan 23, 2024
You are correct @dcousens, that is in fact what we did for Keycloak.
dcousens commentedon Jan 23, 2024
https://nodejs.org/api/globals.html#atobdata was added in Node
16, so I think we're good if we're assuming LTS for the library (we are)jonkoops commentedon Jan 23, 2024
Yeah, this is also an opportunity to align the implementations between Node.js and the browser, might be a good code-cleanup. Did some quick testing, but I was unable to get things to work in a 15 min time-span (had some trouble running the tests on a browser as well).
dcousens commentedon Feb 5, 2024
@jonkoops if you want to help review #349 🧡
chjj commentedon Feb 5, 2024
I suspect
atobandbtoawill have more performance overhead than a custom base64 implementation due to the need for string preprocessing (to match node.js behavior) and the extra conversion back and forth between a binary string a buffer.I don't have proof because I haven't benchmarked it, but the implementation in #349 is probably the fastest/best we can get.
edit: On second thought, if we optimistically parse base64 with
atoband fallback to preprocessing on error, it may be faster? Something like:I'll have a crack at it at some point.
dcousens commentedon Feb 5, 2024
Using
atob/btoais strongly preferred, if at least for the first attempt for easier code review.We can optimize afterwards.
chjj commentedon Feb 5, 2024
Here are the numbers for
atob(in node.js):Compared to the implementation in #349:
This was the code used (with the optimized version of
asciiWrite):If you're scratching your head like I was, look no further than the insanely broken implementation of
atobin node.js itself.This line in particular is the problem:
In other words, the node.js
atobfunction runs withO(n^2)time complexity! How code this irresponsible made it into the node.js codebase is beyond me.I'll have to benchmark this in an actual web browser to get some real numbers.
jonkoops commentedon Feb 5, 2024
Oh damn, nice find! I wonder how other run-times such as Bun and Deno compare in this regard. Did you log an issue with the Node.js team?
dcousens commentedon Feb 5, 2024
What is the browser performance like? We could probably still rely on
atobandbtoaand then log the nodejs issue upstream?vweevers commentedon Feb 5, 2024
Because Buffer exists as the better alternative (from Node.js' perspective that is). The
atobfunction is marked as Legacy:Which explains this comment:
That said, you could make the argument that this hurts web compatibility, and still file an issue.
jonkoops commentedon Feb 5, 2024
So perhaps take the
Buffer.from()approach for Node.js and see if the perf foratobin browsers is acceptable? Would prevent the need to roll a bunch of custom code.chjj commentedon Feb 5, 2024
@vweevers, it's still trivial to optimize it with a lookup table. I don't see "legacy" as an excuse to be lazy.
Interesting that it mentions the performance issue in the code but not the docs (where it might be the most helpful).
In any case, filing an issue to have it optimized now does nothing, because we'll never know whether we're using the optimized version or the unoptimized version. @jonkoops' approach makes more sense.
@dcousens, performance in chromium seems optimal. I'm seeing a 1.4x speedup compared to the JS impl. I don't know if I'll get around to testing firefox anytime soon as I don't have good tooling to run FF headless, but I'll start working on a revision of #349 that uses
atobin the browser andBuffer.from -> toStringin node.dcousens commentedon Feb 5, 2024
I have added a comment in nodejs/node#38433 (comment), and I suspect that's where we can leave it. We are targeting browsers, and the fact that
atobandbtoais slow innodeis only a loss fornode, not this library.chjj commentedon Feb 5, 2024
Just an update: while using
atobis faster (in chromium), the same can't be said forbtoa.(Note the numbers differ from the ones in my PR because I ran this on a faster machine)
We're basically doing this:
The overhead of
Buffer -> binary string -> base64is one of the things I was worried about. I guess we have to make a decision whether we want to keep the base64 serialization code, or take the perf hit and usebtoa.dcousens commentedon Feb 5, 2024
@chjj I think we can probably say that
btoaandatobshould be faster and better maintained in the long-termchjj commentedon Feb 5, 2024
New implementation up at #349
chjj commentedon Feb 5, 2024
I made a PR fixing atob in node.js. Let's see if anything happens.
chjj commentedon Feb 5, 2024
Wow their linter is strict about whitespace. Okay, I'm out of energy on that. If they accept it, they accept it, if not, we'll figure something out.
jonkoops commentedon Feb 5, 2024
I have to agree with @dcousens that it's not really an issue that some of these functions are slow in Node.js, it is very clear that compatibility and performance with existing popular web APIs is an afterthought there. And it doesn't really matter anyways, as this library will almost always be used in a web context.
Thanks for your hard work @chjj, I appreciate you.
bakkot commentedon Dec 26, 2025
There's now native support in browsers for base64 and hex encoding/decoding on Uint8Array, although for decoding it's more strict than Buffer and so can require preprocessing. However, it is much more optimized than atob/btoa at least in Chrome, since it uses simd, an approach not available to a JS implementation.