Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fs: improve readFile performance #718

Closed
wants to merge 1 commit into from

Conversation

vkurchatkin
Copy link
Contributor

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

see #704

R=@trevnorris, @petkaantonov

@trevnorris
Copy link
Contributor

Mostly LGTM. I still need to test it. Are we going to slowly move everything over to let?

@trevnorris trevnorris self-assigned this Feb 4, 2015
@trevnorris trevnorris added fs Issues and PRs related to the fs subsystem / file system. enhancement labels Feb 4, 2015
@micnic
Copy link
Contributor

micnic commented Feb 6, 2015

I think we could use var in functions context and global context, but let inside if / for / while statements. Like this we will know for sure what kind of variables are being used, block-scoped or not.

@timoxley
Copy link
Contributor

timoxley commented Feb 6, 2015

@micnic note the messaging seems to me that let & const should be used wherever possible; var is basically deprecated or at least not advised. If you want to initialise something inside an if, just promote the let/constdeclaration to the outer closure.

@timoxley
Copy link
Contributor

timoxley commented Feb 6, 2015

Are we going to slowly move everything over to let?

@trevnorris note that at current, let appears significantly slower than var:

// in io.js 1.1.0 repl
(function() {
  (function() {
    "use strict"
    console.time('let')
    let result = 0
    for (let i = 0; i < 10000000; i++) {
      let lettest = 'foo bar baz';
      let x = i * i
      result += x
    }
    console.timeEnd('let')
    return result
  })();

  (function() {
    "use strict"
    console.time('var')
    var result = 0
    for (var i = 0; i < 10000000; i++) {
      var lettest = 'foo bar baz'
      var x = i * i
      result += x
    }
    console.timeEnd('var')
    return result
  })()
})()
// let: 1183ms
// var: 15ms

Maybe this benchmark is bogus though.

@micnic
Copy link
Contributor

micnic commented Feb 6, 2015

@timoxley , var isn't deprecated or not advised, yes, let can replace var everywhere, but I meant that we can use together var and let for some semantic reasons.

@vkurchatkin
Copy link
Contributor Author

It seems that the only reasons to introduce let is backwards compatibility. Block scoping is something that is wanted in most of situations. Popular example:

for (var i = 0; i < 10; i++) {
  setImmediate(function() {
    console.log(i);
  })
}

Fixed simply by swapping var for let. Using var inside of blocks is generally confusing in the same way function declarations are (and they are prohibited in strict mode).

That said, if let is actually slower than var it's probably to early to use it.

@bnoordhuis
Copy link
Member

@timoxley I wouldn't say it's bogus. You can see with --trace_gc --trace_osr where the difference comes from: manipulating the result let binding from an inner scope seems to prevent on-stack replacement (OSR), stopping the function from getting optimized. That in turn means the heap allocation for i * i isn't optimized away, leading to a ton of garbage collector activity.

I believe that's mostly been fixed in V8 HEAD but for now, we should probably scale back a little on const/let-ification, at least until we upgrade again.

@vkurchatkin
Copy link
Contributor Author

Removed all lets

return callback(er);
});
}
var req = new FSReqWrap();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be even neater to fold the FSReqWrap object into the ReadFileContext object. Something for another time, though.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All the FSReqWrap() object are one use, which means they could be reused for each step. Preventing the need to instantiate more objects. Food for thought.

@bnoordhuis
Copy link
Member

@vkurchatkin vkurchatkin force-pushed the fs-context branch 2 times, most recently from 18ea8d3 to 3875d97 Compare February 6, 2015 12:05
var length;

if (size === 0) {
buffer = this.buffer = new Buffer(8192);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd recommend changing this to new SlowBuffer(8192);. Just do a var SlowBuffer = require('buffer').SlowBuffer; at the top of the file.

@vkurchatkin
Copy link
Contributor Author

I'm not sure that reusing FSReqWrap is safe, but I've tried and everything seems to work fine. Also switched to SlowBuffer and a couple of minor tweaks. PTAL

@trevnorris
Copy link
Contributor

@vkurchatkin Awesome job on the callback hoisting BTW. That had been bothering me for a while.

Changes LGTM.

@vkurchatkin
Copy link
Contributor Author

@trevnorris can you confirm that using one FSReqWrap for everything is ok? a bit worried about this

@trevnorris
Copy link
Contributor

@vkurchatkin ah. no, definitely not safe doing that right now. will require some additional code. let's save that for another PR.

This commit improves `readFile` performance by
reducing number of closure allocations and using
`FSReqWrap` directly.
vkurchatkin added a commit that referenced this pull request Feb 10, 2015
This commit improves `readFile` performance by
reducing number of closure allocations and using
`FSReqWrap` directly.

PR-URL: #718

Reviewed-By: Trevor Norris <trev.norris@gmail.com>
@vkurchatkin
Copy link
Contributor Author

@trevnorris thank! dropped that commit, fixed some style issues, squashed and landed in e653080

@trevnorris
Copy link
Contributor

@vkurchatkin great! thanks for your work on this.

@rvagg
Copy link
Member

rvagg commented Feb 11, 2015

do we have any perf numbers on this for a changelog entry @vkurchatkin? otherwise it's just handwavy.

@trevnorris
Copy link
Contributor

@rvagg Numbers would be nice, but if the performance numbers are the same having hoisted out the callbacks is a big win for doing performance analysis.

@vkurchatkin
Copy link
Contributor Author

it's sort of hard to measure. It doesn't make reading files significantly faster, but it's supposed to block main thread less

@bnoordhuis
Copy link
Member

I'd recommend changing this to new SlowBuffer(8192);. Just do a var SlowBuffer = require('buffer').SlowBuffer; at the top of the file.

@trevnorris I was looking at lib/fs.js and lib/buffer.js today and I confess it's not immediately obvious to me why a SlowBuffer is better than a regular Buffer.

Secondly, I think I spotted a minor bug. This PR calls new SlowBuffer(size). Here is the implementation of SlowBuffer:

function SlowBuffer(length) {
  length = length >>> 0;
  if (length > kMaxLength) {
    throw new RangeError('Attempt to allocate Buffer larger than maximum ' +
                         'size: 0x' + kMaxLength.toString(16) + ' bytes');
  }
  var b = new NativeBuffer(length);
  alloc(b, length);
  return b;
}

I think the new keyword is superfluous?

@vkurchatkin
Copy link
Contributor Author

@bnoordhuis It's better because kReadFileBufferLength === Buffer.poolSize by default, I guess

@trevnorris
Copy link
Contributor

@bnoordhuis hah, yeah. you're right about SlowBuffer(). Guess the new can be omitted. And the use of SlowBuffer is as @vkurchatkin said. Since kReadFileBufferLength > Buffer.poolSize / 2 by default it's faster to just bypass the type checks at the beginning of Buffer() and allocate memory. Not much, but it's there.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
fs Issues and PRs related to the fs subsystem / file system.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants