From e65308053c871352be948b9001737df01aad1965 Mon Sep 17 00:00:00 2001 From: Vladimir Kurchatkin Date: Tue, 3 Feb 2015 22:07:02 +0300 Subject: [PATCH] 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 --- lib/fs.js | 212 +++++++++++++++++++++++++++++++++++------------------- 1 file changed, 137 insertions(+), 75 deletions(-) diff --git a/lib/fs.js b/lib/fs.js index d967d22f59da8d..18332c7840c8b2 100644 --- a/lib/fs.js +++ b/lib/fs.js @@ -3,6 +3,7 @@ 'use strict'; +const SlowBuffer = require('buffer').SlowBuffer; const util = require('util'); const pathModule = require('path'); @@ -218,101 +219,162 @@ 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(); - }); - }); + binding.open(pathModule._makeLong(path), + stringToFlags(flag), + 0o666, + req); +}; - 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); - } +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; } - function afterRead(er, bytesRead) { - if (er) { - return fs.close(fd, function(er2) { - return callback(er); - }); - } + var req = new FSReqWrap(); + req.oncomplete = readFileAfterRead; + req.context = this; - if (bytesRead === 0) { - return close(); - } + binding.read(fd, buffer, offset, length, -1, req); +}; - 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(); - } +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; } - 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); - } + context.fd = fd; - if (encoding) buffer = buffer.toString(encoding); - return callback(er, buffer); - }); + 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) {