fs: add c++ fast path for writeFileSync utf8

PR-URL: https://github.com/nodejs/node/pull/49884
Reviewed-By: Yagiz Nizipli <yagiz.nizipli@sentry.io>
Reviewed-By: Santiago Gimeno <santiago.gimeno@gmail.com>
Reviewed-By: Stephen Belanger <admin@stephenbelanger.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
This commit is contained in:
CanadaHonk 2023-11-27 00:01:39 +00:00 committed by GitHub
parent f11b2061ea
commit 4466deeb34
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 156 additions and 0 deletions

View file

@ -0,0 +1,43 @@
'use strict';
const common = require('../common.js');
const fs = require('fs');
const tmpdir = require('../../test/common/tmpdir');
tmpdir.refresh();
// Some variants are commented out as they do not show a change and just slow
const bench = common.createBenchmark(main, {
encoding: ['utf8'],
useFd: ['true', 'false'],
length: [1024, 102400, 1024 * 1024],
// useBuffer: ['true', 'false'],
useBuffer: ['false'],
// func: ['appendFile', 'writeFile'],
func: ['writeFile'],
n: [1e3],
});
function main({ n, func, encoding, length, useFd, useBuffer }) {
tmpdir.refresh();
const enc = encoding === 'undefined' ? undefined : encoding;
const path = tmpdir.resolve(`.writefilesync-file-${Date.now()}`);
useFd = useFd === 'true';
const file = useFd ? fs.openSync(path, 'w') : path;
let data = 'a'.repeat(length);
if (useBuffer === 'true') data = Buffer.from(data, encoding);
const fn = fs[func + 'Sync'];
bench.start();
for (let i = 0; i < n; ++i) {
fn(file, data, enc);
}
bench.end(n);
if (useFd) fs.closeSync(file);
}

View file

@ -2343,6 +2343,19 @@ function writeFileSync(path, data, options) {
validateBoolean(flush, 'options.flush');
// C++ fast path for string data and UTF8 encoding
if (typeof data === 'string' && (options.encoding === 'utf8' || options.encoding === 'utf-8')) {
if (!isInt32(path)) {
path = pathModule.toNamespacedPath(getValidatedPath(path));
}
return binding.writeFileUtf8(
path, data,
stringToFlags(options.flag),
parseFileMode(options.mode, 'mode', 0o666),
);
}
if (!isArrayBufferView(data)) {
validateStringAfterArrayBufferView(data, 'data');
data = Buffer.from(data, options.encoding || 'utf8');

View file

@ -2230,6 +2230,84 @@ static void WriteString(const FunctionCallbackInfo<Value>& args) {
}
}
static void WriteFileUtf8(const FunctionCallbackInfo<Value>& args) {
// Fast C++ path for fs.writeFileSync(path, data) with utf8 encoding
// (file, data, options.flag, options.mode)
Environment* env = Environment::GetCurrent(args);
auto isolate = env->isolate();
CHECK_EQ(args.Length(), 4);
BufferValue value(isolate, args[1]);
CHECK_NOT_NULL(*value);
CHECK(args[2]->IsInt32());
const int flags = args[2].As<Int32>()->Value();
CHECK(args[3]->IsInt32());
const int mode = args[3].As<Int32>()->Value();
uv_file file;
bool is_fd = args[0]->IsInt32();
// Check for file descriptor
if (is_fd) {
file = args[0].As<Int32>()->Value();
} else {
BufferValue path(isolate, args[0]);
CHECK_NOT_NULL(*path);
if (CheckOpenPermissions(env, path, flags).IsNothing()) return;
FSReqWrapSync req_open("open", *path);
FS_SYNC_TRACE_BEGIN(open);
file =
SyncCallAndThrowOnError(env, &req_open, uv_fs_open, *path, flags, mode);
FS_SYNC_TRACE_END(open);
if (is_uv_error(file)) {
return;
}
}
int bytesWritten = 0;
uint32_t offset = 0;
const size_t length = value.length();
uv_buf_t uvbuf = uv_buf_init(value.out(), length);
FS_SYNC_TRACE_BEGIN(write);
while (offset < length) {
FSReqWrapSync req_write("write");
bytesWritten = SyncCallAndThrowOnError(
env, &req_write, uv_fs_write, file, &uvbuf, 1, -1);
// Write errored out
if (bytesWritten < 0) {
break;
}
offset += bytesWritten;
DCHECK_LE(offset, length);
uvbuf.base += bytesWritten;
uvbuf.len -= bytesWritten;
}
FS_SYNC_TRACE_END(write);
if (!is_fd) {
FSReqWrapSync req_close("close");
FS_SYNC_TRACE_BEGIN(close);
int result = SyncCallAndThrowOnError(env, &req_close, uv_fs_close, file);
FS_SYNC_TRACE_END(close);
if (is_uv_error(result)) {
return;
}
}
}
/*
* Wrapper for read(2).
@ -3071,6 +3149,7 @@ static void CreatePerIsolateProperties(IsolateData* isolate_data,
SetMethod(isolate, target, "writeBuffer", WriteBuffer);
SetMethod(isolate, target, "writeBuffers", WriteBuffers);
SetMethod(isolate, target, "writeString", WriteString);
SetMethod(isolate, target, "writeFileUtf8", WriteFileUtf8);
SetMethod(isolate, target, "realpath", RealPath);
SetMethod(isolate, target, "copyFile", CopyFile);
@ -3190,6 +3269,7 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
registry->Register(WriteBuffer);
registry->Register(WriteBuffers);
registry->Register(WriteString);
registry->Register(WriteFileUtf8);
registry->Register(RealPath);
registry->Register(CopyFile);

View file

@ -41,17 +41,34 @@ fs.writeSync = function() {
throw new Error('BAM');
};
// Internal fast paths are pure C++, can't error inside write
internalBinding('fs').writeFileUtf8 = function() {
// Fake close
close_called++;
throw new Error('BAM');
};
internalBinding('fs').fstat = function() {
throw new Error('EBADF: bad file descriptor, fstat');
};
let close_called = 0;
ensureThrows(function() {
// Fast path: writeFileSync utf8
fs.writeFileSync('dummy', 'xxx');
}, 'BAM');
ensureThrows(function() {
// Non-fast path
fs.writeFileSync('dummy', 'xxx', { encoding: 'base64' });
}, 'BAM');
ensureThrows(function() {
// Fast path: writeFileSync utf8
fs.appendFileSync('dummy', 'xxx');
}, 'BAM');
ensureThrows(function() {
// Non-fast path
fs.appendFileSync('dummy', 'xxx', { encoding: 'base64' });
}, 'BAM');
function ensureThrows(cb, message) {
let got_exception = false;

View file

@ -230,6 +230,9 @@ declare namespace InternalFSBinding {
function writeString(fd: number, value: string, pos: unknown, encoding: unknown, usePromises: typeof kUsePromises): Promise<number>;
function getFormatOfExtensionlessFile(url: string): ConstantsBinding['fs'];
function writeFileUtf8(path: string, data: string, flag: number, mode: number): void;
function writeFileUtf8(fd: number, data: string, flag: number, mode: number): void;
}
export interface FsBinding {