fs: improve readFile performance

This commit improves `readFile` performance by
reducing number of closure allocations and using
`FSReqWrap` directly.

PR-URL: https://github.com/iojs/io.js/pull/718

Reviewed-By: Trevor Norris <trev.norris@gmail.com>
This commit is contained in:
Vladimir Kurchatkin 2015-02-03 22:07:02 +03:00
parent 9681fcacf0
commit e65308053c

228
lib/fs.js
View File

@ -3,6 +3,7 @@
'use strict';
const SlowBuffer = require('buffer').SlowBuffer;
const util = require('util');
const pathModule = require('path');
@ -218,102 +219,163 @@ fs.existsSync = function(path) {
fs.readFile = function(path, options, callback_) {
var callback = maybeCallback(arguments[arguments.length - 1]);
if (!options || typeof options === 'function') {
if (!options || typeof options === 'function')
options = { encoding: null, flag: 'r' };
} else if (typeof options === 'string') {
else if (typeof options === 'string')
options = { encoding: options, flag: 'r' };
} else if (typeof options !== 'object') {
else if (typeof options !== 'object')
throw new TypeError('Bad arguments');
}
var encoding = options.encoding;
assertEncoding(encoding);
// first, stat the file, so we know the size.
var size;
var buffer; // single buffer with file data
var buffers; // list for when size is unknown
var pos = 0;
var fd;
var flag = options.flag || 'r';
fs.open(path, flag, 0o666, function(er, fd_) {
if (er) return callback(er);
fd = fd_;
fs.fstat(fd, function(er, st) {
if (er) {
return fs.close(fd, function() {
callback(er);
});
}
if (!nullCheck(path, callback))
return;
size = st.size;
if (size === 0) {
// the kernel lies about many files.
// Go ahead and try to read some bytes.
buffers = [];
return read();
}
var context = new ReadFileContext(callback, encoding);
var req = new FSReqWrap();
req.context = context;
req.oncomplete = readFileAfterOpen;
if (size > kMaxLength) {
var err = new RangeError('File size is greater than possible Buffer: ' +
'0x3FFFFFFF bytes');
return fs.close(fd, function() {
callback(err);
});
}
buffer = new Buffer(size);
read();
});
});
function read() {
if (size === 0) {
buffer = new Buffer(8192);
fs.read(fd, buffer, 0, 8192, -1, afterRead);
} else {
fs.read(fd, buffer, pos, size - pos, -1, afterRead);
}
}
function afterRead(er, bytesRead) {
if (er) {
return fs.close(fd, function(er2) {
return callback(er);
});
}
if (bytesRead === 0) {
return close();
}
pos += bytesRead;
if (size !== 0) {
if (pos === size) close();
else read();
} else {
// unknown size, just read until we don't get bytes.
buffers.push(buffer.slice(0, bytesRead));
read();
}
}
function close() {
fs.close(fd, function(er) {
if (size === 0) {
// collected the data into the buffers list.
buffer = Buffer.concat(buffers, pos);
} else if (pos < size) {
buffer = buffer.slice(0, pos);
}
if (encoding) buffer = buffer.toString(encoding);
return callback(er, buffer);
});
}
binding.open(pathModule._makeLong(path),
stringToFlags(flag),
0o666,
req);
};
const kReadFileBufferLength = 8 * 1024;
function ReadFileContext(callback, encoding) {
this.fd = undefined;
this.size = undefined;
this.callback = callback;
this.buffers = null;
this.buffer = null;
this.pos = 0;
this.encoding = encoding;
this.err = null;
}
ReadFileContext.prototype.read = function() {
var fd = this.fd;
var size = this.size;
var buffer;
var offset;
var length;
if (size === 0) {
buffer = this.buffer = new SlowBuffer(kReadFileBufferLength);
offset = 0;
length = kReadFileBufferLength;
} else {
buffer = this.buffer;
offset = this.pos;
length = size - this.pos;
}
var req = new FSReqWrap();
req.oncomplete = readFileAfterRead;
req.context = this;
binding.read(fd, buffer, offset, length, -1, req);
};
ReadFileContext.prototype.close = function(err) {
var req = new FSReqWrap();
req.oncomplete = readFileAfterClose;
req.context = this;
this.err = err;
binding.close(this.fd, req);
};
function readFileAfterOpen(err, fd) {
var context = this.context;
if (err) {
var callback = context.callback;
callback(err);
return;
}
context.fd = fd;
var req = new FSReqWrap();
req.oncomplete = readFileAfterStat;
req.context = context;
binding.fstat(fd, req);
}
function readFileAfterStat(err, st) {
var context = this.context;
if (err)
return context.close(err);
var size = context.size = st.size;
if (size === 0) {
context.buffers = [];
context.read();
return;
}
if (size > kMaxLength) {
err = new RangeError('File size is greater than possible Buffer: ' +
`0x${kMaxLength.toString(16)} bytes`);
return context.close(err);
}
context.buffer = new SlowBuffer(size);
context.read();
}
function readFileAfterRead(err, bytesRead) {
var context = this.context;
if (err)
return context.close(err);
if (bytesRead === 0)
return context.close();
context.pos += bytesRead;
if (context.size !== 0) {
if (context.pos === context.size)
context.close();
else
context.read();
} else {
// unknown size, just read until we don't get bytes.
context.buffers.push(context.buffer.slice(0, bytesRead));
context.read();
}
}
function readFileAfterClose(err) {
var context = this.context;
var buffer = null;
var callback = context.callback;
if (context.err)
return callback(context.err);
if (context.size === 0)
buffer = Buffer.concat(context.buffers, context.pos);
else if (context.pos < context.size)
buffer = context.buffer.slice(0, context.pos);
else
buffer = context.buffer;
if (context.encoding)
buffer = buffer.toString(context.encoding);
callback(err, buffer);
}
fs.readFileSync = function(path, options) {
if (!options) {
options = { encoding: null, flag: 'r' };