-
Notifications
You must be signed in to change notification settings - Fork 29.6k
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
Support context passing in low-level async APIs #704
Comments
The points raised here are good and interesting - I'm wondering if you can make a benchmark to back them up? Also, to be clear - the suggestion here is to allow low level methods to take a |
What exactly do you want me to benchmark? That allocating a closure is slower than not allocating it? |
@petkaantonov code using a node API (for example the filesystem API) performing better (in a real use case) with context passed than without it. |
As far as I understand you are proposing that instead of var whatever = ....
fs.someFunction(function(err, res) {
console.log(whatever);
}); people should do var whatever = ....
fs.someFunction(function(err, res, context) {
var whatever = context;
console.log(whatever);
}, whatever); |
Not in public facing API... let's take a look at readFile for instance.. it allocates 7 closures when it could simply allocate 1 |
Well, adding context support to |
Well you don't need to rewrite anything, supporting it in FSReqWrap (and equivalent in other modules) is already enough for me. I am just saying that iojs itself could get also a perf win from this for many methods if it wants. You don't need to touch readFile at all. And if you were to refactor readFile you would obviously call internal methods that accept context if you didn't want to expose context passing to users. Here's an example how readFile could be implemented were this supported by FSReqWrap: https://gist.github.com/petkaantonov/0b23d1c18404baf65dec , it reduces object allocations in successful case from 9 (6 JSFunctions+3 Contexts) to 1 (JSObject). |
👍 |
Ok, it seems like we already have everything to do this: var binding = process.binding('fs');
var open = binding.open;
var FSReqWrap = binding.FSReqWrap;
var constants = process.binding('constants');
var req = new FSReqWrap();
req.oncomplete = oncomplete;
req.context = 'whatever';
open('test.js', constants.O_RDONLY, 0o666, req);
function oncomplete(err, fd) {
if (err)
throw err;
console.log(fd);
console.log(this.context)
} |
Yea that works for me, thanks |
I tried this approach out and can't see significant changes in benchmarks before
after
|
Those benchmarks measure file reading speed which is not affected, to measure the call speed you probably want to read a ton of unique short files like 16 bytes long or such. E.g. create different 1000 files of 16 bytes length and then call readFile on them in parallel... |
nope, same thing |
@vkurchatkin I'll construct a relevant benchmark on the weekend when I have more time for this |
The current low-level async APIs have performance-hostile design as each level that passes a callback requires the allocation of a closre (which is in this case the allocation of 2 objects: JSFunction object and a Context object).
For example:
110% of the time
callback
has some context it needs around once it's called, so it can never be a static function that is only allocated once per program. Also any higher level fs abstraction is going to allocate a closure for its own context and so on. These closures are relatively expensive objects, more expensive than e.g. a Promise object.With context passing:
This would enable extremely performant promise-wrappers (given user-land promises):
The above code literally allocates less objects than a
fs.doSomething(1, function closure(){})
.But it would also enable high level fs functions that are build on top of low level fs functions to allocate less closures.
Another example of context-passing include most of the ES5 array methods.
The text was updated successfully, but these errors were encountered: