Files
node/benchmark/buffers/buffer-write-string-utf8.js
Shelley Vohr 55e4ca33f3 src: use simdutf for two-byte strings in UTF-8 writes
StringBytes::Write() already used simdutf to encode one-byte strings
as UTF-8 but sent every two-byte (UTF-16) string through
v8::String::WriteUtf8V2(), which is several times slower. That path
is behind Buffer.from(string), buf.write(), fs.write*() with string
data and every string written to a libuv stream, and JSON.stringify()
output is a two-byte string as soon as any value in the payload is
outside Latin-1.

Encode two-byte strings with simdutf as well whenever their UTF-8 form
is guaranteed to fit in the target: well-formed input is converted
directly, and input with unpaired surrogates is converted from a copy
passed through simdutf::to_well_formed_utf16(), which replaces each
unpaired surrogate with U+FFFD exactly like kReplaceInvalidUtf8 (this
mirrors what TextEncoder already does). Writes that have to truncate
at a character boundary keep using WriteUtf8V2(), so their output is
byte-for-byte unchanged, and so do strings of up to 32 code units, for
which V8 is already as fast (the same threshold TextEncoder uses).

buf.write() of a 2 KiB two-byte string improves ~5x (astral-heavy and
lone-surrogate strings ~3.5x and ~5x), Buffer.from() of a 64 KiB JSON
string ~2.7x; one-byte strings are unaffected.

Signed-off-by: Shelley Vohr <shelley.vohr@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/65324
Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
Reviewed-By: Gürgün Dayıoğlu <hey@gurgun.day>
Reviewed-By: Daniel Lemire <daniel@lemire.me>
Reviewed-By: James M Snell <jasnell@gmail.com>
2026-08-18 16:34:15 +00:00

37 lines
1.1 KiB
JavaScript

'use strict';
// buf.write(string, 'utf8') for strings whose in-memory representation is
// one-byte (Latin-1) or two-byte (UTF-16), which take different encoder paths.
const common = require('../common.js');
const bench = common.createBenchmark(main, {
chars: ['one-byte', 'two-byte', 'two-byte-astral', 'two-byte-lone-surrogate'],
len: [16, 256, 2048, 65536],
n: [5e5],
});
function makeString(chars, len) {
switch (chars) {
case 'one-byte':
return 'aé'.repeat(len / 2);
case 'two-byte':
return 'aé€日'.repeat(len / 4);
case 'two-byte-astral':
return 'aé€日\u{1F600}'.repeat(len / 6).padEnd(len, 'a');
case 'two-byte-lone-surrogate':
return 'aé€日'.repeat(len / 4 - 1) + 'ab\ud800c';
default:
throw new Error(chars);
}
}
function main({ chars, len, n }) {
const string = makeString(chars, len);
const buf = Buffer.allocUnsafe(Buffer.byteLength(string));
if (len >= 65536) n = Math.floor(n / 32);
bench.start();
for (let i = 0; i < n; ++i) {
buf.write(string, 0, 'utf8');
}
bench.end(n);
}