You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Unfortunately, the `FileStream` used by `StreamReader` is not optimized for modern .NET. For example, when using `FileStream` with asynchronous methods, it should be opened with `useAsync: true` for optimal performance. However, since `StreamReader` has both synchronous and asynchronous methods in its API, false is specified. Additionally, although `StreamReader` itself has a buffer and `FileStream` does not require a buffer, the buffer of `FileStream` is still being utilized.
101
-
102
-
Strictly speaking, [FileStream underwent a major overhaul in .NET 6](https://github.com/dotnet/runtime/issues/40359). The behavior is controlled by an internal `FileStreamStrategy`. For instance, on Windows, `SyncWindowsFileStreamStrategy` is used when useAsync is false, and `AsyncWindowsFileStreamStrategy` is used when useAsync is true. Moreover, if bufferSize is set to 1, the `FileStreamStrategy` is used directly, and it writes directly to the buffer passed via `ReadAsync(Memory<byte>)`. If any other value is specified, it is wrapped in a `BufferedFileStreamStrategy`.
103
-
104
-
Based on these observations of the internal behavior, `Utf8StreamReader` generates a `FileStream` with the following options:
Furthermore, by devising how to call Stream as a whole, we have succeeded in making it function as a thin wrapper for [RandomAccess.ReadAsync](https://learn.microsoft.com/en-us/dotnet/api/system.io.randomaccess.readasync), which is the fastest way to call it.
111
-
112
-
For overloads that accept `FileStreamOptions`, the above settings are not reflected, so please adjust them manually.
113
-
114
89
## Read as `ReadOnlyMemory<char>`
115
90
116
91
You can convert it to a `Utf8TextReader` that extracts `ReadOnlyMemory<char>` or `string`. Although there is a conversion cost, it is still fast and low allocation, so it can be used as an alternative to `StreamReader`.
@@ -137,6 +112,58 @@ You can perform text processing without allocation, such as splitting `ReadOnlyS
137
112
138
113
When a string is needed, you can convert `ReadOnlyMemory<char>` to a string using `ToString()`. Even with the added string conversion, the performance is higher than `StreamReader`, so it can be used as a better alternative.
139
114
115
+
## Optimizing FileStream
116
+
117
+
Similar to `StreamReader`, `Utf8StreamReader` has the ability to open a `FileStream` by accepting a `string path`.
Unfortunately, the `FileStream` used by `StreamReader` is not optimized for modern .NET. For example, when using `FileStream` with asynchronous methods, it should be opened with `useAsync: true` for optimal performance. However, since `StreamReader` has both synchronous and asynchronous methods in its API, false is specified. Additionally, although `StreamReader` itself has a buffer and `FileStream` does not require a buffer, the buffer of `FileStream` is still being utilized.
127
+
128
+
It is difficult to handle `FileStream` correctly with high performance. By specifying a `string path`, the stream is opened with options optimized for `Utf8StreamReader`, so it is recommended to use this overload rather than opening `FileStream` yourself. The following is a benchmark of `FileStream`.
Due to historical reasons, the options for `FileStream` are odd, but by setting `bufferSize` to 1, you can avoid the use of internal buffers. `FileStream` has been significantly revamped in .NET 6, and by controlling the setting of this option and the way `Utf8StreamReader` is called as a whole, it can function as a thin wrapper around the fast [RandomAccess.ReadAsync](https://learn.microsoft.com/en-us/dotnet/api/system.io.randomaccess.readasync), allowing you to avoid most of the overhead of FileStream.
140
+
141
+
`FileOpenMode` is a proprietary option of `Utf8StreamReader`.
142
+
143
+
144
+
```csharp
145
+
publicenumFileOpenMode
146
+
{
147
+
Scalability,
148
+
Throughput
149
+
}
150
+
```
151
+
152
+
In a Windows environment, the table in the [IO section of the Performance Improvements in .NET 6 blog](https://devblogs.microsoft.com/dotnet/performance-improvements-in-net-6/#io) shows that throughput decreases when `useAsync: true` is used.
By setting `Utf8StreamReader` to `FileOpenMode.Scalability`, true async I/O is enabled and scalability is prioritized. If set to `FileOpenMode.Throughput`, it internally becomes sync-over-async and consumes the ThreadPool, but reduces the overhead of asynchronous I/O and improves throughput.
160
+
161
+
If frequently executed within a server application, setting it to `Scalability`, and for batch applications, setting it to `Throughput` will likely yield the best performance characteristics. The default is `Scalability`.
162
+
163
+
In `Utf8StreamReader`, by carefully adjusting the buffer size on the `Utf8StreamReader` side, the performance difference is minimized. Please refer to the above benchmark results image for specific values.
164
+
165
+
For overloads that accept `FileStreamOptions`, the above settings are not reflected, so please adjust them manually.
166
+
140
167
## Reset
141
168
142
169
`Utf8StreamReader` is a class that supports reuse. By calling `Reset()`, the Stream and internal state are released. Using `Reset(Stream)`, it can be reused with a new `Stream`.
@@ -145,7 +172,7 @@ When a string is needed, you can convert `ReadOnlyMemory<char>` to a string usin
145
172
146
173
The constructor accepts `int bufferSize` and `bool leaveOpen` as parameters.
147
174
148
-
`int bufferSize` defaults to 4096, but if the data per line is large, changing the buffer size may improve performance. When the buffer size and the size per line are close, frequent buffer copy operations occur, leading to performance degradation.
175
+
`int bufferSize` defaults to 65536 and the buffer is rented from `ArrayPool<byte>`. If the data per line is large, changing the buffer size may improve performance. When the buffer size and the size per line are close, frequent buffer copy operations occur, leading to performance degradation.
149
176
150
177
`bool leaveOpen` determines whether the internal Stream is also disposed when the object is disposed. The default is `false`, which means the Stream is disposed.
0 commit comments