From 21211a3fa93993b363f25964da5dac3282b3b453 Mon Sep 17 00:00:00 2001 From: Yagiz Nizipli Date: Thu, 11 Apr 2024 07:41:44 -0400 Subject: [PATCH] buffer: improve `btoa` performance PR-URL: https://github.com/nodejs/node/pull/52427 Reviewed-By: Daniel Lemire Reviewed-By: James M Snell --- benchmark/buffers/buffer-btoa.js | 20 +++++++++++ lib/buffer.js | 11 +++--- src/node_buffer.cc | 59 ++++++++++++++++++++++++++++++++ 3 files changed, 84 insertions(+), 6 deletions(-) create mode 100644 benchmark/buffers/buffer-btoa.js diff --git a/benchmark/buffers/buffer-btoa.js b/benchmark/buffers/buffer-btoa.js new file mode 100644 index 00000000000000..3867d5890b1d79 --- /dev/null +++ b/benchmark/buffers/buffer-btoa.js @@ -0,0 +1,20 @@ +'use strict'; +const common = require('../common.js'); +const assert = require('node:assert'); + +const bench = common.createBenchmark(main, { + size: [16, 32, 64, 128, 256, 1024], + n: [1e6], +}); + +function main({ n, size }) { + const input = 'A'.repeat(size); + let out = 0; + + bench.start(); + for (let i = 0; i < n; i++) { + out += btoa(input).length; + } + bench.end(n); + assert(out > 0); +} diff --git a/lib/buffer.js b/lib/buffer.js index ea94ebf24192f9..d66d5650f30e73 100644 --- a/lib/buffer.js +++ b/lib/buffer.js @@ -69,6 +69,7 @@ const { kMaxLength, kStringMaxLength, atob: _atob, + btoa: _btoa, } = internalBinding('buffer'); const { constants: { @@ -1249,13 +1250,11 @@ function btoa(input) { if (arguments.length === 0) { throw new ERR_MISSING_ARGS('input'); } - input = `${input}`; - for (let n = 0; n < input.length; n++) { - if (input[n].charCodeAt(0) > 0xff) - throw lazyDOMException('Invalid character', 'InvalidCharacterError'); + const result = _btoa(`${input}`); + if (result === -1) { + throw lazyDOMException('Invalid character', 'InvalidCharacterError'); } - const buf = Buffer.from(input, 'latin1'); - return buf.toString('base64'); + return result; } function atob(input) { diff --git a/src/node_buffer.cc b/src/node_buffer.cc index b31beada451bc8..e63318b65b2e61 100644 --- a/src/node_buffer.cc +++ b/src/node_buffer.cc @@ -1211,6 +1211,63 @@ void DetachArrayBuffer(const FunctionCallbackInfo& args) { } } +static void Btoa(const FunctionCallbackInfo& args) { + CHECK_EQ(args.Length(), 1); + Environment* env = Environment::GetCurrent(args); + THROW_AND_RETURN_IF_NOT_STRING(env, args[0], "argument"); + + Local input = args[0].As(); + MaybeStackBuffer buffer; + size_t written; + + if (input->IsExternalOneByte()) { // 8-bit case + auto ext = input->GetExternalOneByteStringResource(); + size_t expected_length = simdutf::base64_length_from_binary(ext->length()); + buffer.AllocateSufficientStorage(expected_length + 1); + buffer.SetLengthAndZeroTerminate(expected_length); + written = + simdutf::binary_to_base64(ext->data(), ext->length(), buffer.out()); + } else if (input->IsOneByte()) { + MaybeStackBuffer stack_buf(input->Length()); + input->WriteOneByte(env->isolate(), + stack_buf.out(), + 0, + input->Length(), + String::NO_NULL_TERMINATION); + + size_t expected_length = + simdutf::base64_length_from_binary(input->Length()); + buffer.AllocateSufficientStorage(expected_length + 1); + buffer.SetLengthAndZeroTerminate(expected_length); + written = + simdutf::binary_to_base64(reinterpret_cast(*stack_buf), + input->Length(), + buffer.out()); + } else { + String::Value value(env->isolate(), input); + MaybeStackBuffer stack_buf(value.length()); + size_t out_len = simdutf::convert_utf16_to_latin1( + reinterpret_cast(*value), + value.length(), + stack_buf.out()); + if (out_len == 0) { // error + return args.GetReturnValue().Set(-1); + } + size_t expected_length = simdutf::base64_length_from_binary(out_len); + buffer.AllocateSufficientStorage(expected_length + 1); + buffer.SetLengthAndZeroTerminate(expected_length); + written = simdutf::binary_to_base64(*stack_buf, out_len, buffer.out()); + } + + auto value = + String::NewFromOneByte(env->isolate(), + reinterpret_cast(buffer.out()), + NewStringType::kNormal, + written) + .ToLocalChecked(); + return args.GetReturnValue().Set(value); +} + // In case of success, the decoded string is returned. // In case of error, a negative value is returned: // * -1 indicates a single character remained, @@ -1329,6 +1386,7 @@ void Initialize(Local target, Isolate* isolate = env->isolate(); SetMethodNoSideEffect(context, target, "atob", Atob); + SetMethodNoSideEffect(context, target, "btoa", Btoa); SetMethod(context, target, "setBufferPrototype", SetBufferPrototype); SetMethodNoSideEffect(context, target, "createFromString", CreateFromString); @@ -1433,6 +1491,7 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) { registry->Register(CopyArrayBuffer); registry->Register(Atob); + registry->Register(Btoa); } } // namespace Buffer