commit 612f84975ef380bbaaf5939051f0c33d627c3318
parent 13d388fa5201d0b1b92b7ee2acd75243f7ad7924
Author: André Bargull <andre.bargull@gmail.com>
Date: Mon, 20 Oct 2025 12:03:53 +0000
Bug 1994067 - Part 3: Improve performance of Uint8Array.prototype.toBase64. r=spidermonkey-reviewers,iain
Similar to part 1, add templates for shared and unshared memory. And also
reduce the number of calls to `infallibleAppend`.
For a 5KB Uint8Array, about 25% of the time is spend in `ToBase64`. The rest is
call and allocation overhead.
Differential Revision: https://phabricator.services.mozilla.com/D269017
Diffstat:
1 file changed, 104 insertions(+), 53 deletions(-)
diff --git a/js/src/vm/TypedArrayObject.cpp b/js/src/vm/TypedArrayObject.cpp
@@ -5330,6 +5330,107 @@ static bool uint8array_setFromHex(JSContext* cx, unsigned argc, Value* vp) {
args);
}
+template <typename Ops>
+static void ToBase64(TypedArrayObject* tarray, size_t length, Alphabet alphabet,
+ OmitPadding omitPadding, JSStringBuilder& sb) {
+ const auto& base64Chars = alphabet == Alphabet::Base64
+ ? Base64::Encode::Base64
+ : Base64::Encode::Base64Url;
+
+ auto encode = [&base64Chars](uint32_t value) {
+ return base64Chars[value & 0x3f];
+ };
+
+ // Our implementation directly converts the bytes to their string
+ // representation instead of first collecting them into an intermediate list.
+ auto data = Ops::extract(tarray).template cast<uint8_t*>();
+ auto toRead = length;
+
+ if (toRead >= 12) {
+ auto data32 = data.template cast<uint32_t*>();
+ for (; toRead >= 12; toRead -= 12) {
+ // Read three 32-bit words.
+ auto word0 = mozilla::NativeEndian::swapToBigEndian(Ops::load(data32++));
+ auto word1 = mozilla::NativeEndian::swapToBigEndian(Ops::load(data32++));
+ auto word2 = mozilla::NativeEndian::swapToBigEndian(Ops::load(data32++));
+
+ // Split into four uint24 values.
+ auto u24_0 = word0 >> 8;
+ auto u24_1 = (word0 << 16) | (word1 >> 16);
+ auto u24_2 = (word1 << 8) | (word2 >> 24);
+ auto u24_3 = word2;
+
+ // Encode the uint24 values as base64 and write in blocks of eight
+ // characters.
+ char chars1[] = {
+ encode(u24_0 >> 18), encode(u24_0 >> 12),
+ encode(u24_0 >> 6), encode(u24_0 >> 0),
+
+ encode(u24_1 >> 18), encode(u24_1 >> 12),
+ encode(u24_1 >> 6), encode(u24_1 >> 0),
+ };
+ sb.infallibleAppend(chars1, sizeof(chars1));
+
+ char chars2[] = {
+ encode(u24_2 >> 18), encode(u24_2 >> 12),
+ encode(u24_2 >> 6), encode(u24_2 >> 0),
+
+ encode(u24_3 >> 18), encode(u24_3 >> 12),
+ encode(u24_3 >> 6), encode(u24_3 >> 0),
+ };
+ sb.infallibleAppend(chars2, sizeof(chars2));
+ }
+ data = data32.template cast<uint8_t*>();
+ }
+
+ for (; toRead >= 3; toRead -= 3) {
+ // Combine three input bytes into a single uint24 value.
+ auto byte0 = Ops::load(data++);
+ auto byte1 = Ops::load(data++);
+ auto byte2 = Ops::load(data++);
+ auto u24 = (uint32_t(byte0) << 16) | (uint32_t(byte1) << 8) | byte2;
+
+ // Encode the uint24 value as base64.
+ char chars[] = {
+ encode(u24 >> 18),
+ encode(u24 >> 12),
+ encode(u24 >> 6),
+ encode(u24 >> 0),
+ };
+ sb.infallibleAppend(chars, sizeof(chars));
+ }
+
+ // Trailing two and one element bytes are optionally padded with '='.
+ if (toRead == 2) {
+ // Combine two input bytes into a single uint24 value.
+ auto byte0 = Ops::load(data++);
+ auto byte1 = Ops::load(data++);
+ auto u24 = (uint32_t(byte0) << 16) | (uint32_t(byte1) << 8);
+
+ // Encode the uint24 value as base64, optionally including padding.
+ sb.infallibleAppend(encode(u24 >> 18));
+ sb.infallibleAppend(encode(u24 >> 12));
+ sb.infallibleAppend(encode(u24 >> 6));
+ if (omitPadding == OmitPadding::No) {
+ sb.infallibleAppend('=');
+ }
+ } else if (toRead == 1) {
+ // Combine one input byte into a single uint24 value.
+ auto byte0 = Ops::load(data++);
+ auto u24 = uint32_t(byte0) << 16;
+
+ // Encode the uint24 value as base64, optionally including padding.
+ sb.infallibleAppend(encode(u24 >> 18));
+ sb.infallibleAppend(encode(u24 >> 12));
+ if (omitPadding == OmitPadding::No) {
+ sb.infallibleAppend('=');
+ sb.infallibleAppend('=');
+ }
+ } else {
+ MOZ_ASSERT(toRead == 0);
+ }
+}
+
/**
* Uint8Array.prototype.toBase64 ( [ options ] )
*
@@ -5390,60 +5491,10 @@ static bool uint8array_toBase64(JSContext* cx, const CallArgs& args) {
}
// Steps 9-10.
- const auto& base64Chars = alphabet == Alphabet::Base64
- ? Base64::Encode::Base64
- : Base64::Encode::Base64Url;
-
- auto encode = [&base64Chars](uint32_t value) {
- return base64Chars[value & 0x3f];
- };
-
- // Our implementation directly converts the bytes to their string
- // representation instead of first collecting them into an intermediate list.
- auto data = tarray->dataPointerEither().cast<uint8_t*>();
- auto toRead = *length;
- for (; toRead >= 3; toRead -= 3) {
- // Combine three input bytes into a single uint24 value.
- auto byte0 = jit::AtomicOperations::loadSafeWhenRacy(data++);
- auto byte1 = jit::AtomicOperations::loadSafeWhenRacy(data++);
- auto byte2 = jit::AtomicOperations::loadSafeWhenRacy(data++);
- auto u24 = (uint32_t(byte0) << 16) | (uint32_t(byte1) << 8) | byte2;
-
- // Encode the uint24 value as base64.
- sb.infallibleAppend(encode(u24 >> 18));
- sb.infallibleAppend(encode(u24 >> 12));
- sb.infallibleAppend(encode(u24 >> 6));
- sb.infallibleAppend(encode(u24 >> 0));
- }
-
- // Trailing two and one element bytes are optionally padded with '='.
- if (toRead == 2) {
- // Combine two input bytes into a single uint24 value.
- auto byte0 = jit::AtomicOperations::loadSafeWhenRacy(data++);
- auto byte1 = jit::AtomicOperations::loadSafeWhenRacy(data++);
- auto u24 = (uint32_t(byte0) << 16) | (uint32_t(byte1) << 8);
-
- // Encode the uint24 value as base64, optionally including padding.
- sb.infallibleAppend(encode(u24 >> 18));
- sb.infallibleAppend(encode(u24 >> 12));
- sb.infallibleAppend(encode(u24 >> 6));
- if (omitPadding == OmitPadding::No) {
- sb.infallibleAppend('=');
- }
- } else if (toRead == 1) {
- // Combine one input byte into a single uint24 value.
- auto byte0 = jit::AtomicOperations::loadSafeWhenRacy(data++);
- auto u24 = uint32_t(byte0) << 16;
-
- // Encode the uint24 value as base64, optionally including padding.
- sb.infallibleAppend(encode(u24 >> 18));
- sb.infallibleAppend(encode(u24 >> 12));
- if (omitPadding == OmitPadding::No) {
- sb.infallibleAppend('=');
- sb.infallibleAppend('=');
- }
+ if (tarray->isSharedMemory()) {
+ ToBase64<SharedOps>(tarray, *length, alphabet, omitPadding, sb);
} else {
- MOZ_ASSERT(toRead == 0);
+ ToBase64<UnsharedOps>(tarray, *length, alphabet, omitPadding, sb);
}
MOZ_ASSERT(sb.length() == outLength.value(), "all characters were written");