Skip to content

Commit 328bded

Browse files
anonrigmarco-ippolito
authored andcommitted
buffer: improve btoa performance
PR-URL: #52427 Reviewed-By: Daniel Lemire <daniel@lemire.me> Reviewed-By: James M Snell <jasnell@gmail.com>
1 parent 1f7a28c commit 328bded

File tree

3 files changed

+84
-6
lines changed

3 files changed

+84
-6
lines changed

benchmark/buffers/buffer-btoa.js

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
'use strict';
2+
const common = require('../common.js');
3+
const assert = require('node:assert');
4+
5+
const bench = common.createBenchmark(main, {
6+
size: [16, 32, 64, 128, 256, 1024],
7+
n: [1e6],
8+
});
9+
10+
function main({ n, size }) {
11+
const input = 'A'.repeat(size);
12+
let out = 0;
13+
14+
bench.start();
15+
for (let i = 0; i < n; i++) {
16+
out += btoa(input).length;
17+
}
18+
bench.end(n);
19+
assert(out > 0);
20+
}

lib/buffer.js

+5-6
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ const {
6969
kMaxLength,
7070
kStringMaxLength,
7171
atob: _atob,
72+
btoa: _btoa,
7273
} = internalBinding('buffer');
7374
const {
7475
constants: {
@@ -1249,13 +1250,11 @@ function btoa(input) {
12491250
if (arguments.length === 0) {
12501251
throw new ERR_MISSING_ARGS('input');
12511252
}
1252-
input = `${input}`;
1253-
for (let n = 0; n < input.length; n++) {
1254-
if (input[n].charCodeAt(0) > 0xff)
1255-
throw lazyDOMException('Invalid character', 'InvalidCharacterError');
1253+
const result = _btoa(`${input}`);
1254+
if (result === -1) {
1255+
throw lazyDOMException('Invalid character', 'InvalidCharacterError');
12561256
}
1257-
const buf = Buffer.from(input, 'latin1');
1258-
return buf.toString('base64');
1257+
return result;
12591258
}
12601259

12611260
function atob(input) {

src/node_buffer.cc

+59
Original file line numberDiff line numberDiff line change
@@ -1211,6 +1211,63 @@ void DetachArrayBuffer(const FunctionCallbackInfo<Value>& args) {
12111211
}
12121212
}
12131213

1214+
static void Btoa(const FunctionCallbackInfo<Value>& args) {
1215+
CHECK_EQ(args.Length(), 1);
1216+
Environment* env = Environment::GetCurrent(args);
1217+
THROW_AND_RETURN_IF_NOT_STRING(env, args[0], "argument");
1218+
1219+
Local<String> input = args[0].As<String>();
1220+
MaybeStackBuffer<char> buffer;
1221+
size_t written;
1222+
1223+
if (input->IsExternalOneByte()) { // 8-bit case
1224+
auto ext = input->GetExternalOneByteStringResource();
1225+
size_t expected_length = simdutf::base64_length_from_binary(ext->length());
1226+
buffer.AllocateSufficientStorage(expected_length + 1);
1227+
buffer.SetLengthAndZeroTerminate(expected_length);
1228+
written =
1229+
simdutf::binary_to_base64(ext->data(), ext->length(), buffer.out());
1230+
} else if (input->IsOneByte()) {
1231+
MaybeStackBuffer<uint8_t> stack_buf(input->Length());
1232+
input->WriteOneByte(env->isolate(),
1233+
stack_buf.out(),
1234+
0,
1235+
input->Length(),
1236+
String::NO_NULL_TERMINATION);
1237+
1238+
size_t expected_length =
1239+
simdutf::base64_length_from_binary(input->Length());
1240+
buffer.AllocateSufficientStorage(expected_length + 1);
1241+
buffer.SetLengthAndZeroTerminate(expected_length);
1242+
written =
1243+
simdutf::binary_to_base64(reinterpret_cast<const char*>(*stack_buf),
1244+
input->Length(),
1245+
buffer.out());
1246+
} else {
1247+
String::Value value(env->isolate(), input);
1248+
MaybeStackBuffer<char> stack_buf(value.length());
1249+
size_t out_len = simdutf::convert_utf16_to_latin1(
1250+
reinterpret_cast<const char16_t*>(*value),
1251+
value.length(),
1252+
stack_buf.out());
1253+
if (out_len == 0) { // error
1254+
return args.GetReturnValue().Set(-1);
1255+
}
1256+
size_t expected_length = simdutf::base64_length_from_binary(out_len);
1257+
buffer.AllocateSufficientStorage(expected_length + 1);
1258+
buffer.SetLengthAndZeroTerminate(expected_length);
1259+
written = simdutf::binary_to_base64(*stack_buf, out_len, buffer.out());
1260+
}
1261+
1262+
auto value =
1263+
String::NewFromOneByte(env->isolate(),
1264+
reinterpret_cast<const uint8_t*>(buffer.out()),
1265+
NewStringType::kNormal,
1266+
written)
1267+
.ToLocalChecked();
1268+
return args.GetReturnValue().Set(value);
1269+
}
1270+
12141271
// In case of success, the decoded string is returned.
12151272
// In case of error, a negative value is returned:
12161273
// * -1 indicates a single character remained,
@@ -1329,6 +1386,7 @@ void Initialize(Local<Object> target,
13291386
Isolate* isolate = env->isolate();
13301387

13311388
SetMethodNoSideEffect(context, target, "atob", Atob);
1389+
SetMethodNoSideEffect(context, target, "btoa", Btoa);
13321390

13331391
SetMethod(context, target, "setBufferPrototype", SetBufferPrototype);
13341392
SetMethodNoSideEffect(context, target, "createFromString", CreateFromString);
@@ -1433,6 +1491,7 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
14331491
registry->Register(CopyArrayBuffer);
14341492

14351493
registry->Register(Atob);
1494+
registry->Register(Btoa);
14361495
}
14371496

14381497
} // namespace Buffer

0 commit comments

Comments
 (0)