-
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
fs: improve readFile performance #27063
Conversation
This increases the maximum buffer size per read to 256kb when using `fs.readFile`. This is important to improve the read performance for bigger files. Refs: nodejs#25741
Couldn't we just change |
That would not have the expected behavior: it is now only used in case the |
Maybe we should have two constants then, since |
It would have the behaviour I'd expect from the commit description! :-) Why is this more conservative? What is safer? If we are going to read 1,000K, and we know it, with this change it will be read in larger 256K chunks. But if we don't know that it will be 1,000K, it will be read in the older, smaller, 8K chunks. Maybe there is a good reason for this, but its not immediately obvious. I'm actually not clear on why there are two branches at all. It seems like the branch where size isn't known would work for all cases. AFAICT the only optimization is that if the file happens to be exactly a multiple of kReadFileBufferLength, and .size is known, one extra .... OK, I'm not deleting the above, but I think I just realized why there are two branches. The Still not sure why the larger buf size isn't used in all cases. |
I added another commit that also refactors the code a tiny bit to make it simpler. I also added the constant as suggested.
There is a comment in one branch that checks for Benchmark: https://ci.nodejs.org/view/Node.js%20benchmark/job/benchmark-node-micro-benchmarks/323/ |
There's a slightly different approach we could take on this by either: a. Providing an option to set the read chunk size... e.g. b. Providing an option that is effectively a preallocated buffer to fill on each read... Option a is likely the far better choice and allows users to performance tune on their own. |
I increased the value to 512kb and also doubled the value for unknown file sizes. 256kb already show significant improvements:
@jasnell I believe we should raise this limit with or without the option. So adding the option could be done in a separate PR and this is a quick win. |
master...zbjornson:chunksizeopt this branch has a However, @davisjam and I actually just had a nice chat and agreed that partitioning may be causing more harm than good (in part due to the increased risk of OOMs), and were both open to removing partitioning and instead adding a prominent doc warning. |
So that the buffer would be as big as the whole file? We would have to guard against wrong sizes (and then potentially still manage multiple buffers) and what would be the benefit of b over a? |
As I said, option a is likely better than option b ;-) |
If you mean
That says what, but not why. Since you can read until you don't get bytes whether you know the size or not, without some context, its quite mysterious why the file doesn't just always use the This was not introduced in this PR, so consider it a nit. I can fix, if you want. |
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.
The effect of this is to make the buffer size different when reading from pipes vs. from files (technically, for "seekable" vs "non-seekable" fds). I don't understand why both cases wouldn't want the same performance enhancement.
This is not about using an fd or not but about the file type. We know the file size for regular files (S_IFREG type) but not in case it's another file type. So if we know the file type, it'll allocate the full buffer in advance and then read in chunks into that buffer. For other file types we allocate a small buffer because we could otherwise over allocate and that costs memory and performance and create a new buffer on each read until we find the end of the file. At the end we concat those buffers together. |
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.
LGTM
@nodejs/fs this could use some further reviews. @sam-github @jasnell @zbjornson I suggest to land this, no matter what the latter solution might be to improve the current situation. Are you good with 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.
LGTM. However I’d make both of them 512KB.
I guess it won't make a huge difference as the cases where the file does not have a regular file type is not common but it will definitely take a significantly longer time to allocate 512kb over the current 8kb and if we over allocate, we also do work and use more memory than we should. What about using 64kb or 128kb for unknown file sizes? I guess with that size we cover a lot more files and also improve the performance significantly. |
Mine is not a blocker. I'm ok as it is. Maybe can you add a comment with the above? |
I added a comment to highlight the difference and updated the buffer size to 64kb for unknown file sizes. |
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.
LGTM. This should give a big performance boost while still "taking turns" on the threadpool.
This increases the maximum buffer size per read to 512kb when using `fs.readFile`. This is important to improve the read performance for bigger files. PR-URL: #27063 Refs: #25741 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Jamie Davis <davisjam@vt.edu>
Landed in eb2d416 🎉 Thanks for the reviews! |
This increases the maximum buffer size per read to 256kb when using
fs.readFile
. This is important to improve the read performance forbigger files.
Refs: #25741
Benchmark (512kb):
Checklist
make -j4 test
(UNIX), orvcbuild test
(Windows) passes