0
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2025-03-03 17:34:47 -05:00

perf(runtime/fs): optimize readFile by using a single large buffer (#12057)

* perf(runtime/fs): optimize readFile by using a single large buffer
* handle extended/truncated files during read

Allocate an extra byte in our read buffer to detect "overflow" then fallback to unsized readAll for remainder of extended file, this is a slowpath that should rarely happen in practice
This commit is contained in:
Aaron O'Mullan 2021-09-16 20:28:15 +02:00 committed by GitHub
parent 868f38d452
commit 00948a6d68
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 73 additions and 43 deletions

View file

@ -11,6 +11,7 @@
const { const {
Uint8Array, Uint8Array,
ArrayPrototypePush, ArrayPrototypePush,
MathMin,
TypedArrayPrototypeSubarray, TypedArrayPrototypeSubarray,
TypedArrayPrototypeSet, TypedArrayPrototypeSet,
} = window.__bootstrap.primordials; } = window.__bootstrap.primordials;
@ -96,10 +97,7 @@
return nread === 0 ? null : nread; return nread === 0 ? null : nread;
} }
async function read( async function read(rid, buffer) {
rid,
buffer,
) {
if (buffer.length === 0) { if (buffer.length === 0) {
return 0; return 0;
} }
@ -117,10 +115,10 @@
return await core.opAsync("op_write_async", rid, data); return await core.opAsync("op_write_async", rid, data);
} }
const READ_PER_ITER = 32 * 1024; const READ_PER_ITER = 16 * 1024; // 16kb, see https://github.com/denoland/deno/issues/10157
async function readAll(r) { function readAll(r) {
return await readAllInner(r); return readAllInner(r);
} }
async function readAllInner(r, options) { async function readAllInner(r, options) {
const buffers = []; const buffers = [];
@ -138,6 +136,26 @@
throw new DOMException("The read operation was aborted.", "AbortError"); throw new DOMException("The read operation was aborted.", "AbortError");
} }
return concatBuffers(buffers);
}
function readAllSync(r) {
const buffers = [];
while (true) {
const buf = new Uint8Array(READ_PER_ITER);
const read = r.readSync(buf);
if (typeof read == "number") {
ArrayPrototypePush(buffers, buf.subarray(0, read));
} else {
break;
}
}
return concatBuffers(buffers);
}
function concatBuffers(buffers) {
let totalLen = 0; let totalLen = 0;
for (const buf of buffers) { for (const buf of buffers) {
totalLen += buf.byteLength; totalLen += buf.byteLength;
@ -154,33 +172,55 @@
return contents; return contents;
} }
function readAllSync(r) { function readAllSyncSized(r, size) {
const buffers = []; const buf = new Uint8Array(size + 1); // 1B to detect extended files
let cursor = 0;
while (true) { while (cursor < size) {
const buf = new Uint8Array(READ_PER_ITER); const sliceEnd = MathMin(size + 1, cursor + READ_PER_ITER);
const read = r.readSync(buf); const slice = buf.subarray(cursor, sliceEnd);
const read = r.readSync(slice);
if (typeof read == "number") { if (typeof read == "number") {
ArrayPrototypePush(buffers, new Uint8Array(buf.buffer, 0, read)); cursor += read;
} else { } else {
break; break;
} }
} }
let totalLen = 0; // Handle truncated or extended files during read
for (const buf of buffers) { if (cursor > size) {
totalLen += buf.byteLength; // Read remaining and concat
return concatBuffers([buf, readAllSync(r)]);
} else { // cursor == size
return buf.subarray(0, cursor);
}
}
async function readAllInnerSized(r, size, options) {
const buf = new Uint8Array(size + 1); // 1B to detect extended files
let cursor = 0;
const signal = options?.signal ?? null;
while (!signal?.aborted && cursor < size) {
const sliceEnd = MathMin(size + 1, cursor + READ_PER_ITER);
const slice = buf.subarray(cursor, sliceEnd);
const read = await r.read(slice);
if (typeof read == "number") {
cursor += read;
} else {
break;
}
}
if (signal?.aborted) {
throw new DOMException("The read operation was aborted.", "AbortError");
} }
const contents = new Uint8Array(totalLen); // Handle truncated or extended files during read
if (cursor > size) {
let n = 0; // Read remaining and concat
for (const buf of buffers) { return concatBuffers([buf, await readAllInner(r, options)]);
TypedArrayPrototypeSet(contents, buf, n); } else {
n += buf.byteLength; return buf.subarray(0, cursor);
} }
return contents;
} }
window.__bootstrap.io = { window.__bootstrap.io = {
@ -195,5 +235,7 @@
readAll, readAll,
readAllInner, readAllInner,
readAllSync, readAllSync,
readAllSyncSized,
readAllInnerSized,
}; };
})(this); })(this);

View file

@ -4,13 +4,13 @@
((window) => { ((window) => {
const core = window.Deno.core; const core = window.Deno.core;
const { open, openSync } = window.__bootstrap.files; const { open, openSync } = window.__bootstrap.files;
const { readAllInner, readAllSync } = window.__bootstrap.io; const { readAllSyncSized, readAllInnerSized } = window.__bootstrap.io;
function readFileSync(path) { function readFileSync(path) {
const file = openSync(path); const file = openSync(path);
try { try {
const contents = readAllSync(file); const { size } = file.statSync();
return contents; return readAllSyncSized(file, size);
} finally { } finally {
file.close(); file.close();
} }
@ -19,31 +19,19 @@
async function readFile(path, options) { async function readFile(path, options) {
const file = await open(path); const file = await open(path);
try { try {
const contents = await readAllInner(file, options); const { size } = await file.stat();
return contents; return await readAllInnerSized(file, size, options);
} finally { } finally {
file.close(); file.close();
} }
} }
function readTextFileSync(path) { function readTextFileSync(path) {
const file = openSync(path); return core.decode(readFileSync(path));
try {
const contents = readAllSync(file);
return core.decode(contents);
} finally {
file.close();
}
} }
async function readTextFile(path, options) { async function readTextFile(path, options) {
const file = await open(path); return core.decode(await readFile(path, options));
try {
const contents = await readAllInner(file, options);
return core.decode(contents);
} finally {
file.close();
}
} }
window.__bootstrap.readFile = { window.__bootstrap.readFile = {