-
Notifications
You must be signed in to change notification settings - Fork 4.9k
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
Regression in File.PreallocationSize #59705
Comments
I couldn't figure out the best area label to add to this issue. If you have write-permissions please help me learn by adding exactly one area label. |
Introduced by #59338 |
The regression in |
alpine regression - dotnet/perf-autofiling-issues#1590 |
cc: @tmds |
The PR made a functional change in
|
Do we know why it had such an impact? Is the syscall being used now 20% slower? |
The I don't think there is anything we can do about improving the runtime implementation. For the benchmark, it uses I'll run the benchmark locally and see if I learn something from that. I may not get to it immediately. |
@kunalspathak as I said in a previous comment, I don't think they are related. I wonder how the regression in |
Tagging subscribers to this area: @dotnet/area-system-io Issue DetailsRun Information
Regressions in System.IO.Tests.Perf_FileStream
Reprogit clone https://github.com/dotnet/performance.git
python3 .\performance\scripts\benchmarks_ci.py -f net6.0 --filter 'System.IO.Tests.Perf_FileStream*' PayloadsHistogramSystem.IO.Tests.Perf_FileStream.Write_NoBuffering_PreallocationSize(fileSize: 104857600, userBufferSize: 16384, options: None)
DocsProfiling workflow for dotnet/runtime repository
Regressions in System.Text.Json.Tests.Perf_Booleans
Reprogit clone https://github.com/dotnet/performance.git
python3 .\performance\scripts\benchmarks_ci.py -f net6.0 --filter 'System.Text.Json.Tests.Perf_Booleans*' PayloadsHistogramSystem.Text.Json.Tests.Perf_Booleans.WriteBooleans(Formatted: False, SkipValidation: True)
DocsProfiling workflow for dotnet/runtime repository
|
I agree it is hard to believe that, but when I saw the changes that went between the regressions, they were these: The benchmark does memory write using |
@tmds thanks for checking this, let us know if you are unable to solve this in a couple of days as the .NET 6 deadline is close. |
I ran this benchmark on my machine and I could not reproduce the regression. I ran it using the tmpfs at /tmp, and using a folder that is mounted with btfs. FALLOC_FL_KEEP_SIZE, tmpfs
posix_fallocate, tmpfs
FALLOC_FL_KEEP_SIZE, btrfs
posix_fallocate, btrfs
|
I don't have any further action planned here. If I had to take a long guess: maybe tmpfs is suffering some fragmentation issues? I don't think there is much to improve about the implementation. We're making a single syscall with the appropriate flags. I think we can consider to accept this as the new baseline. And maybe, add an additional test that uses a persistent file system. |
@tmds, so just to confirm:
Yes? Assuming that's the case, sounds like we can close this. (And if there are more tests to be added, great.) |
Yes.
No. Actually I can't measure the benefit of the Maybe I'm not properly running these benchmarks. |
This is the benchmark where we claim that I don't see that there is something completely similar in https://github.com/dotnet/performance/blob/main/src/benchmarks/micro/libraries/System.IO.FileSystem/Perf.FileStream.cs as that one directly uses RandomAccess. |
@jozkee The blog post benchmark you mentioned, which verifies @tmds @stephentoub In my Ubuntu WSL, I created two runtime cloned folders, synced each one of them to the commits that were mentioned in the description of this issue. Then I built them, ran the benchmarks against each clone, and compared the results (
I assume the reason why the bot did not report a perf regression for
|
I was able to collect two trace files using perfcollect and identify the source of the regression using ext4 file system: In case of In case of @tmds I've sent you the trace files. If you don't want to use PerfView (and Windows), you can unzip them, go to |
When we were using
after switching to
|
This confirms your hypothesis the additional time is spent in updating the length after each write? And when you add a When I run the benchmark on my system using persistent storage, I don't see a regression. I use Do you think we should change back to update the length when |
I don't think so. From my perspective, the previous behavior is unintuitive and likely to be a bug farm, especially when it's inconsistent across Windows and Linux. Is there still a meaningful benefit (e.g. reliability, some perf, etc.) to having the preallocationSize feature? If yes, we can just keep it as-is. If no, I think the change we should be considering is ripping it out entirely. It seems like the full perf benefits can be restored simply by using SetLength after construction. This brings us back to my original questions around this feature, of why it's needed at all rather than someone just calling SetLength after construction. I think the answer I was given then was reliability, which suggests this feature really isn't about perf at all, it was just a side-benefit. |
After #58726 it was consistent and fast (I've verified that) for every OS. Now it's not consistent for the platforms that don't implement We don't know whether setting EOF would be confusing for the end-users. Some of us believe that it would be, some of use don't. Who should decide?
This feature was supposed to provide both reliability and perf. After recent changes (which got accepted and backported without performance validation) it depends on the target OS and File System.
I disagree.
The purpose of this feature was simple: create a file of certain size when the size is known up-front. Throw an exception when there is not enough space available or if given file system does not support files of such size. Why? To guarantee that subsequent |
Confusion isn't the issue. Files filled with garbage zeros is. These are the kinds of design decisions made in the name of performance that lead to long bug tails.
How is SetLength insufficient for this? Don't your comments above suggest using SetLength makes the perf difference evaporate?
Perf "validation" isn't the issue here, because the functionality was incorrect. When it comes to something being high performance and buggy, perf is irrelevant. (I will also point out that Tom, a bonifide expert in Linux, has been unable to reproduce what you validated, so obviously the validation was limited to only a limited subset of systems.) |
When you ask for a file of certain size to be created, what is the expected content?
It behaved exactly as |
This isn't calling SetLength. If it were, that is in fact asking for a file of a certain size, and it should be filled with zeros. Tthe original issue that motivated this be added in the first place described this as a "hint"; behavior that forces the file to that length is no longer a hint, and "preallocationSize" is a very poor name. If this were instead designed as "initialLength" and a requirement of the implementation rather than a hint, then sure, we would do exactly that, filled with zeros.
Ok. So I can change FileStream.Write to perform a There's nothing in the POSIX standard that dicates what .NET APIs should or should not use specific functions. We choose what functionality to invoke based on what makes sense for the APIs we're exposing and implementing. |
I've asked about the expected behavior, not how it's supposed to be implemented or how does it relate to
I agree. If I could turn back time I would call it
I prefer to follow existing standards and conventions rather that reinventing the wheel on my own. Is that a bad thing? |
You asked about the expected behavior of "asking for a file of a certain size". And my answer is highlighting that "asking for a file of a certain size" is different from "preallocationSize"; if you actually asked for a file of a certain size, as you do with SetLength, then yes, it should be that length and be zero-filled. But that's not the behavior being named or requested.
It's not a bad thing, but it's cherry-picking. Windows exposes both options. Linux exposes both options. How are those any less existing? There are lots of things far from ideal about POSIX, which was standardized decades ago; we don't need to propagate mistakes or inadequacies of the past. There are valid reasons for posix_fallocate, and there are valid reasons other functionality exists in all of these operating systems. Just because posix_fallocate exists doesn't mean it's the right thing for this API.
We haven't shipped yet. We're late in the cycle, but if something new is broken, we should fix it. As it stands, it's called "preallocationSize", and that is not the same as behavior that forces a specific length and fills it with zeros. So we either ship the behavior currently in release/6.0 that matches the current release/6.0 "preallocationSize" naming / hint, we rename it and change the functionality accordingly, or we rip it out and revisit it in another release; those are the available options. (I'm also still unclear as to why SetLength is insufficient.) |
I have the same question. Based on the perf investigation, using
This started as a request to improve performance when the file length is known up-front (#45946). I think that means we should rule out the first option because it doesn't improve performance on Linux. Personally, I prefer to push this out of .NET 6 (option 3). |
I'm onboard with that option as well. @tmds / @adamsitnik -- who between you two can create the PR for main to do this? |
I am not going to do that because I don't agree with that. |
Conceptually this feature (PreallocationSize) is more engaging than SetLength for scenarios like rotated log or transaction files (which have a fixed size and it is not desirable for an application to worry about disk space for every io operation). |
Let's summarize the status of this issue. We have 3 options:
I am inclined to the first option: Revert the latest change, ensure all OS behave the same way, keep the big perf gains, and be very explicit in our documentation about using preallocationSize and warn them that EOF == preallocationSize. I am very much against removing the whole feature. |
#58726 was in main; it never made it to release/6.0.
Performance is not the end-all-be-all here. If you wrote less than the "preallocated size", a term used to describe a hint about how the OS should structure things rather than about the actual file length, you'd end up erroneously with zeros at the end of the file. Additionally, no one has answered the question about if/why SetLength is insufficient to reap similar perf benefits.
PreallocationSize is a hint. It doesn't require the length of the file actually be what was specified. It's like FileOptions.RandomAccess or FileOptions.SequentialScan: it gives the OS information about how you intend to use the file but doesn't otherwise change functionality.
And keep in mind that whatever we ship now will need to be maintained and supported forever. A few months of a preview for a new feature is nothing compared to having to live with the repercussions of it long-term if we ship the wrong thing. I'm speaking from experience here. |
runtime/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.Unix.cs Lines 37 to 38 in 784687f
It gives similar perf benefits, but it's 3-5% slower than
I don't know the implementation details, but my guess is that
We wanted to have:
It depends on how we understand the https://devblogs.microsoft.com/dotnet/file-io-improvements-in-dotnet-6/#preallocation-size
Another option better than removing the feature is just renaming it from |
It's good to know
The difference is that
Maybe something with
Both you and @carlossanlop reacted heavily against the idea of moving this feature out of .NET 6. To me, that is also a sensible, valid option. |
We had an offline discussion where we all agreed that currently the best thing we can do is to accept the regression as by design and keep the current implementation.
With current design we don't risk data loss. For users who want event better perf, they can additionally call We intend to update the docs and be very clear about it. Thank you all for your input! |
Thanks, Adam. |
Run Information
Regressions in System.IO.Tests.Perf_FileStream
Test Report
Repro
Payloads
Baseline
Compare
Histogram
System.IO.Tests.Perf_FileStream.Write_NoBuffering_PreallocationSize(fileSize: 104857600, userBufferSize: 16384, options: None)
Docs
Profiling workflow for dotnet/runtime repository
Benchmarking workflow for dotnet/runtime repository
Regressions in System.Text.Json.Tests.Perf_Booleans
Test Report
Repro
Payloads
Baseline
Compare
Histogram
System.Text.Json.Tests.Perf_Booleans.WriteBooleans(Formatted: False, SkipValidation: True)
Docs
Profiling workflow for dotnet/runtime repository
Benchmarking workflow for dotnet/runtime repository
The text was updated successfully, but these errors were encountered: