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:
parent
868f38d452
commit
00948a6d68
2 changed files with 73 additions and 43 deletions
|
@ -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);
|
||||||
|
|
|
@ -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 = {
|
||||||
|
|
Loading…
Add table
Reference in a new issue