Skip to content

Commit 4ae0f04

Browse files
committed
fs: improve readFile performance
This commit improves `readFile` performance by reducing number of closure allocations and using `FSReqWrap` directly.
1 parent 9681fca commit 4ae0f04

File tree

1 file changed

+137
-75
lines changed

1 file changed

+137
-75
lines changed

lib/fs.js

+137-75
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
'use strict';
55

6+
const SlowBuffer = require('buffer').SlowBuffer;
67
const util = require('util');
78
const pathModule = require('path');
89

@@ -218,101 +219,162 @@ fs.existsSync = function(path) {
218219
fs.readFile = function(path, options, callback_) {
219220
var callback = maybeCallback(arguments[arguments.length - 1]);
220221

221-
if (!options || typeof options === 'function') {
222+
if (!options || typeof options === 'function')
222223
options = { encoding: null, flag: 'r' };
223-
} else if (typeof options === 'string') {
224+
else if (typeof options === 'string')
224225
options = { encoding: options, flag: 'r' };
225-
} else if (typeof options !== 'object') {
226+
else if (typeof options !== 'object')
226227
throw new TypeError('Bad arguments');
227-
}
228228

229229
var encoding = options.encoding;
230230
assertEncoding(encoding);
231231

232-
// first, stat the file, so we know the size.
233-
var size;
234-
var buffer; // single buffer with file data
235-
var buffers; // list for when size is unknown
236-
var pos = 0;
237-
var fd;
238-
239232
var flag = options.flag || 'r';
240-
fs.open(path, flag, 0o666, function(er, fd_) {
241-
if (er) return callback(er);
242-
fd = fd_;
243233

244-
fs.fstat(fd, function(er, st) {
245-
if (er) {
246-
return fs.close(fd, function() {
247-
callback(er);
248-
});
249-
}
234+
if (!nullCheck(path, callback))
235+
return;
250236

251-
size = st.size;
252-
if (size === 0) {
253-
// the kernel lies about many files.
254-
// Go ahead and try to read some bytes.
255-
buffers = [];
256-
return read();
257-
}
237+
var context = new ReadFileContext(callback, encoding);
238+
var req = new FSReqWrap();
239+
req.context = context;
240+
req.oncomplete = readFileAfterOpen;
258241

259-
if (size > kMaxLength) {
260-
var err = new RangeError('File size is greater than possible Buffer: ' +
261-
'0x3FFFFFFF bytes');
262-
return fs.close(fd, function() {
263-
callback(err);
264-
});
265-
}
266-
buffer = new Buffer(size);
267-
read();
268-
});
269-
});
242+
binding.open(pathModule._makeLong(path),
243+
stringToFlags(flag),
244+
0o666,
245+
req);
246+
};
270247

271-
function read() {
272-
if (size === 0) {
273-
buffer = new Buffer(8192);
274-
fs.read(fd, buffer, 0, 8192, -1, afterRead);
275-
} else {
276-
fs.read(fd, buffer, pos, size - pos, -1, afterRead);
277-
}
248+
const kReadFileBufferLength = 8 * 1024;
249+
250+
function ReadFileContext(callback, encoding) {
251+
this.fd = undefined;
252+
this.size = undefined;
253+
this.callback = callback;
254+
this.buffers = null;
255+
this.buffer = null;
256+
this.pos = 0;
257+
this.encoding = encoding;
258+
this.err = null;
259+
}
260+
261+
ReadFileContext.prototype.read = function() {
262+
var fd = this.fd;
263+
var size = this.size;
264+
var buffer;
265+
var offset;
266+
var length;
267+
268+
if (size === 0) {
269+
buffer = this.buffer = new SlowBuffer(kReadFileBufferLength);
270+
offset = 0;
271+
length = kReadFileBufferLength;
272+
} else {
273+
buffer = this.buffer;
274+
offset = this.pos;
275+
length = size - this.pos;
278276
}
279277

280-
function afterRead(er, bytesRead) {
281-
if (er) {
282-
return fs.close(fd, function(er2) {
283-
return callback(er);
284-
});
285-
}
278+
var req = new FSReqWrap();
279+
req.oncomplete = readFileAfterRead;
280+
req.context = this;
286281

287-
if (bytesRead === 0) {
288-
return close();
289-
}
282+
binding.read(fd, buffer, offset, length, -1, req);
283+
};
290284

291-
pos += bytesRead;
292-
if (size !== 0) {
293-
if (pos === size) close();
294-
else read();
295-
} else {
296-
// unknown size, just read until we don't get bytes.
297-
buffers.push(buffer.slice(0, bytesRead));
298-
read();
299-
}
285+
ReadFileContext.prototype.close = function(err) {
286+
var req = new FSReqWrap();
287+
req.oncomplete = readFileAfterClose;
288+
req.context = this;
289+
this.err = err;
290+
binding.close(this.fd, req);
291+
};
292+
293+
function readFileAfterOpen(err, fd) {
294+
var context = this.context;
295+
296+
if (err) {
297+
var callback = context.callback;
298+
callback(err);
299+
return;
300300
}
301301

302-
function close() {
303-
fs.close(fd, function(er) {
304-
if (size === 0) {
305-
// collected the data into the buffers list.
306-
buffer = Buffer.concat(buffers, pos);
307-
} else if (pos < size) {
308-
buffer = buffer.slice(0, pos);
309-
}
302+
context.fd = fd;
310303

311-
if (encoding) buffer = buffer.toString(encoding);
312-
return callback(er, buffer);
313-
});
304+
var req = new FSReqWrap();
305+
req.oncomplete = readFileAfterStat;
306+
req.context = context;
307+
binding.fstat(fd, req);
308+
}
309+
310+
function readFileAfterStat(err, st) {
311+
var context = this.context;
312+
313+
if (err)
314+
return context.close(err);
315+
316+
var size = context.size = st.size;
317+
318+
if (size === 0) {
319+
context.buffers = [];
320+
context.read();
321+
return;
314322
}
315-
};
323+
324+
if (size > kMaxLength) {
325+
err = new RangeError('File size is greater than possible Buffer: ' +
326+
`0x${kMaxLength.toString(16)} bytes`);
327+
return context.close(err);
328+
}
329+
330+
context.buffer = new SlowBuffer(size);
331+
context.read();
332+
}
333+
334+
function readFileAfterRead(err, bytesRead) {
335+
var context = this.context;
336+
337+
if (err)
338+
return context.close(err);
339+
340+
if (bytesRead === 0)
341+
return context.close();
342+
343+
context.pos += bytesRead;
344+
345+
if (context.size !== 0) {
346+
if (context.pos === context.size)
347+
context.close();
348+
else
349+
context.read();
350+
} else {
351+
// unknown size, just read until we don't get bytes.
352+
context.buffers.push(context.buffer.slice(0, bytesRead));
353+
context.read();
354+
}
355+
}
356+
357+
function readFileAfterClose(err) {
358+
var context = this.context;
359+
var buffer = null;
360+
var callback = context.callback;
361+
362+
if (context.err)
363+
return callback(context.err);
364+
365+
if (context.size === 0)
366+
buffer = Buffer.concat(context.buffers, context.pos);
367+
else if (context.pos < context.size)
368+
buffer = context.buffer.slice(0, context.pos);
369+
else
370+
buffer = context.buffer;
371+
372+
if (context.encoding)
373+
buffer = buffer.toString(context.encoding);
374+
375+
callback(err, buffer);
376+
}
377+
316378

317379
fs.readFileSync = function(path, options) {
318380
if (!options) {

0 commit comments

Comments
 (0)