tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit 6a7261ea6224640f30a71ba61c3eed32d285c7c4
parent 3f4905592370efbcc5667a9fced5149870545ecf
Author: André Bargull <andre.bargull@gmail.com>
Date:   Mon, 20 Oct 2025 12:03:53 +0000

Bug 1994067 - Part 1: Improve performance of Uint8Array.prototype.toHex. r=spidermonkey-reviewers,iain

Perform the following two changes:
- Add different code paths for shared and unshared memory to improve the
  performance for unshared memory.
- Reduce the number of calls to `infallibleAppend` by writing multiple
  characters at once.

For 5KB Uint8Arrays, we now spend about 20% of time in `ToHex`, the rest is
call and allocation overhead. (Measured by commenting out the call to `ToHex`
and then adding `sb.growByUninitialized(outLength)`.)

Differential Revision: https://phabricator.services.mozilla.com/D269014

Diffstat:
Mjs/src/vm/TypedArrayObject.cpp | 60+++++++++++++++++++++++++++++++++++++++++++++++-------------
1 file changed, 47 insertions(+), 13 deletions(-)

diff --git a/js/src/vm/TypedArrayObject.cpp b/js/src/vm/TypedArrayObject.cpp @@ -5339,6 +5339,49 @@ static bool uint8array_toBase64(JSContext* cx, unsigned argc, Value* vp) { args); } +template <typename Ops> +static void ToHex(TypedArrayObject* tarray, size_t length, + JSStringBuilder& sb) { + // NB: Lower case hex digits. + static constexpr char HexDigits[] = "0123456789abcdef"; + static_assert(std::char_traits<char>::length(HexDigits) == 16); + + // Process multiple bytes per loop to reduce number of calls to + // infallibleAppend. Choose four bytes because tested compilers can optimize + // this amount of bytes into a single write operation. + constexpr size_t BYTES_PER_LOOP = 4; + + size_t alignedLength = length & ~(BYTES_PER_LOOP - 1); + + // Steps 3 and 5. + // + // 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*>(); + for (size_t index = 0; index < alignedLength;) { + char chars[BYTES_PER_LOOP * 2]; + + for (size_t i = 0; i < BYTES_PER_LOOP; ++i) { + auto byte = Ops::load(data + index++); + chars[i * 2 + 0] = HexDigits[byte >> 4]; + chars[i * 2 + 1] = HexDigits[byte & 0xf]; + } + + sb.infallibleAppend(chars, sizeof(chars)); + } + + // Write the remaining characters. + for (size_t index = alignedLength; index < length;) { + char chars[2]; + + auto byte = Ops::load(data + index++); + chars[0] = HexDigits[byte >> 4]; + chars[1] = HexDigits[byte & 0xf]; + + sb.infallibleAppend(chars, sizeof(chars)); + } +} + /** * Uint8Array.prototype.toHex ( ) * @@ -5375,20 +5418,11 @@ static bool uint8array_toHex(JSContext* cx, const CallArgs& args) { return false; } - // NB: Lower case hex digits. - static constexpr char HexDigits[] = "0123456789abcdef"; - static_assert(std::char_traits<char>::length(HexDigits) == 16); - // Steps 3 and 5. - // - // 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*>(); - for (size_t index = 0; index < *length; index++) { - auto byte = jit::AtomicOperations::loadSafeWhenRacy(data + index); - - sb.infallibleAppend(HexDigits[byte >> 4]); - sb.infallibleAppend(HexDigits[byte & 0xf]); + if (tarray->isSharedMemory()) { + ToHex<SharedOps>(tarray, *length, sb); + } else { + ToHex<UnsharedOps>(tarray, *length, sb); } MOZ_ASSERT(sb.length() == outLength, "all characters were written");