-
Notifications
You must be signed in to change notification settings - Fork 29.8k
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: added _writev (up to x50 faster writes) #2167
Conversation
we should see more efficient data transfer from any program that avoids redundant data copies between intermediate buffers and reduces the number of context switches between user space and kernel space scatter/gather I/O! 👍 |
I noticed that with a very large amount of buffers, we hit a limit and Ideally, libuv would detect uv__fs_write getting too many buffers (through uv__getiovmax) and split up the writes into multiple writev calls. |
After patching libuv (I'll send a PR) I got the problem to disappear. Benchmark on OSX by running ./iojs benchmark/common.js fs write-stream-throughput Before (master):
After:
The impact is (not unexpectedly) much bigger with many buffers (ie: the case where buffer size is small). I must note though that being on a hybrid SSD/HD (fusion drive) makes this not the most scientifically sound benchmark. |
static void WriteBuffers(const FunctionCallbackInfo<Value>& args) { | ||
Environment* env = Environment::GetCurrent(args); | ||
|
||
if (!args[0]->IsInt32()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because this binding isn't accessible to the end user, we typically leave out the well-worded error messages and instead leave in CHECK()
s, like this:
CHECK(args[0]->IsInt32());
CHECK(args[1]->IsArray());
Here is an example.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, I kinda went with what I saw in WriteBuffer
, which is located right above my WriteBuffers
function. It too has a test like:
if (!args[0]->IsInt32())
return env->ThrowTypeError("First argument must be file descriptor");
I just followed that.
Thanks for taking the time to put this together! I left a few small nits. The benchmark does seem to be looking good right now, but we do have to wait for that libuv patch. Hopefully that will work out well and make its way into the code base. |
No worries! I've fixed those few little things (thanks for the review!), so now all attention goes back to libuv. If the original author of that PR does not respond before Monday or so, I will dive in and fix it myself. |
var req = new FSReqWrap(); | ||
req.oncomplete = wrapper; | ||
binding.writeBuffers(fd, chunks, position, req); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
; is not necessary for function declarations
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice catch, I wonder why the linter didn't catch that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ronkorving it is because the related eslint rule (no-extra-semi) is not enabled.
I have a working branch to fix that. @silverwind what do you think about it ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've been meaning to look at enabling semicolon related rules, but didn't find time yet. Go ahead and do a PR if you like. One commit to enable the rule(s), another to fix any issues.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ronkorving Can you remove this ;
?
const char* buf = Buffer::Data(chunk); | ||
size_t len = Buffer::Length(chunk); | ||
|
||
iovs[i] = uv_buf_init(const_cast<char *>(buf), len); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
style nit: <char*>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
note: I see it's done that way in Read()
, but that style originated with Ryan way back in 2010. The style has changed and we just propagate w/ new code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Understood.
Will need docs and tests. Also I would recommend exposing this via Some comments and style nits listed. Looking good. |
I agree it would be interesting to expose this through fs.write or fs.writev, but do you think that should be part of this PR? I don't mind iterating in multiple smaller PRs, especially since I expect some debate over API on that one, and I see little reason for that feature to block this PR. Just my thoughts, but what do you think @trevnorris ? |
I quote: fs: implemented WriteStream#writev |
@ronkorving WTF. Mobile output wasn't showing the extended comment. Thanks. Reads well. |
Streams with writev allow many buffers to be pushed to underlying OS APIs in one batch, in this case improving write throughput by an order of magnitude. This is especially noticeable when writing many (small) buffers. PR-URL: #2167 Reviewed-By: Trevor Norris <[email protected]>
Thanks. Landed on 05d30d5 and removed extra semicolon. |
Streams with writev allow many buffers to be pushed to underlying OS APIs in one batch, in this case improving write throughput by an order of magnitude. This is especially noticeable when writing many (small) buffers. PR-URL: #2167 Reviewed-By: Trevor Norris <[email protected]>
This is |
@Fishrock123 if you've cherry-picked this then the first bump of 4.x will have to be 4.1.0 |
I'm fine either way, but how is this not patch? It introduces internal-only API for improved performance that is not exposed in any way to the outside world. Is patch only allowed to be bugfixes? |
Hmm, perhaps you're right, I saw |
cc @nodejs/tsc, is this minor or patch? I'm currently leaning toward minor. If our first post 4.0.0 is a minor it doesn't bother me. Such is the way of SemVer. |
I really hope so, or else we've all been breaking the rules. :P |
Here's my take on it: this PR adds two publicly accessible things, Our approach to semver shouldn't be to try and pull things down to the minimum but just the opposite. We should be using the maximum part of semver that could possibly make sense for a change. If something might break someone's workflow then it should be semver-major, if something we add might be used by end-users then it should be a semver-minor. semver-patch should only be used reserved for cases where we have a high degree of certainty that it's not going to break for someone and that it's not adding new surface area to our exposed API. So I'm still +1 on this being semver-minor even if there's no documentation added for what's been added. Also, thanks @ronkorving, this a great addition! |
You're welcome :) I'm just happy to see it land, no matter the version (I'll leave that up to your judgement). |
Notable changes: * buffer: - Buffers are now created in JavaScript, rather than C++. This increases the speed of buffer creation (Trevor Norris) nodejs#2866. - `Buffer#slice()` now uses `Uint8Array#subarray()` internally, increasing `slice()` performance (Karl Skomski) nodejs#2777. * fs: - `fs.utimes()` now properly converts numeric strings, `NaN`, and `Infinity` (Yazhong Liu) nodejs#2387. - `fs.WriteStream` now implements `_writev`, allowing for super-fast bulk writes (Ron Korving) nodejs#2167. * http: Fixed an issue with certain `write()` sizes causing errors when using `http.request()` (Fedor Indutny) nodejs#2824. * npm: Upgrade to version 2.14.3, see https://github.com/npm/npm/releases/tag/v2.14.3 for more details (Kat Marchán) nodejs#2822. * src: V8 cpu profiling no longer erroneously shows idle time (Oleksandr Chekhovskyi) nodejs#2324. * v8: Lateral upgrade to 4.5.103.33 from 4.5.103.30, contains minor fixes (Ali Ijaz Sheikh) nodejs#2870. - This fixes a previously known bug where some computed object shorthand properties did not work correctly (nodejs#2507). Refs: nodejs#2844 PR-URL: nodejs#2889
Notable changes: * buffer: - Buffers are now created in JavaScript, rather than C++. This increases the speed of buffer creation (Trevor Norris) #2866. - `Buffer#slice()` now uses `Uint8Array#subarray()` internally, increasing `slice()` performance (Karl Skomski) #2777. * fs: - `fs.utimes()` now properly converts numeric strings, `NaN`, and `Infinity` (Yazhong Liu) #2387. - `fs.WriteStream` now implements `_writev`, allowing for super-fast bulk writes (Ron Korving) #2167. * http: Fixed an issue with certain `write()` sizes causing errors when using `http.request()` (Fedor Indutny) #2824. * npm: Upgrade to version 2.14.3, see https://github.com/npm/npm/releases/tag/v2.14.3 for more details (Kat Marchán) #2822. * src: V8 cpu profiling no longer erroneously shows idle time (Oleksandr Chekhovskyi) #2324. * v8: Lateral upgrade to 4.5.103.33 from 4.5.103.30, contains minor fixes (Ali Ijaz Sheikh) #2870. - This fixes a previously known bug where some computed object shorthand properties did not work correctly (#2507). Refs: #2844 PR-URL: #2889
fs.WriteStream did not yet have a _writev method, so writing many buffers at once could not be optimized by libuv. This adds the function.
I've not yet benchmarked the difference before/after, as I'm not sure how to effectively benchmark disk I/O to be honest (because of disk cache, SSD vs. spinning disk (I'm using a mac fusion drive, that doesn't help)).
Update: benchmarks below
However, with this change:
So I expect throughput to increase a little bit, while at the same time reducing CPU usage.
I didn't add tests, as this was already covered by existing tests (which failed during implementation, but now pass).
Warning: this is my first PR to io.js, so please be rigorous in review.