Skip to content

Commit

Permalink
buffer: improve btoa performance
Browse files Browse the repository at this point in the history
PR-URL: #52427
Reviewed-By: Daniel Lemire <daniel@lemire.me>
Reviewed-By: James M Snell <jasnell@gmail.com>
  • Loading branch information
anonrig authored Apr 11, 2024
1 parent ee4fa77 commit 21211a3
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 6 deletions.
20 changes: 20 additions & 0 deletions benchmark/buffers/buffer-btoa.js
Original file line number Diff line number Diff line change
@@ -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);
}
11 changes: 5 additions & 6 deletions lib/buffer.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ const {
kMaxLength,
kStringMaxLength,
atob: _atob,
btoa: _btoa,
} = internalBinding('buffer');
const {
constants: {
Expand Down Expand Up @@ -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) {
Expand Down
59 changes: 59 additions & 0 deletions src/node_buffer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1211,6 +1211,63 @@ void DetachArrayBuffer(const FunctionCallbackInfo<Value>& args) {
}
}

static void Btoa(const FunctionCallbackInfo<Value>& args) {
CHECK_EQ(args.Length(), 1);
Environment* env = Environment::GetCurrent(args);
THROW_AND_RETURN_IF_NOT_STRING(env, args[0], "argument");

Local<String> input = args[0].As<String>();
MaybeStackBuffer<char> 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<uint8_t> 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<const char*>(*stack_buf),
input->Length(),
buffer.out());
} else {
String::Value value(env->isolate(), input);
MaybeStackBuffer<char> stack_buf(value.length());
size_t out_len = simdutf::convert_utf16_to_latin1(
reinterpret_cast<const char16_t*>(*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<const uint8_t*>(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,
Expand Down Expand Up @@ -1329,6 +1386,7 @@ void Initialize(Local<Object> 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);
Expand Down Expand Up @@ -1433,6 +1491,7 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
registry->Register(CopyArrayBuffer);

registry->Register(Atob);
registry->Register(Btoa);
}

} // namespace Buffer
Expand Down

0 comments on commit 21211a3

Please sign in to comment.