mirror of
https://github.com/CommunityToolkit/dotnet.git
synced 2026-10-29 03:27:07 +08:00
41b73b3aa9311f6108731e2593113dd3ca06bfc9
## PR Type What kind of change does this PR introduce? <!-- Please uncomment one or more that apply to this PR. --> <!-- - Bugfix --> - Feature <!-- - Code style update (formatting) --> <!-- - Refactoring (no functional changes, no api changes) --> <!-- - Build or CI related changes --> <!-- - Documentation content changes --> <!-- - Sample app changes --> <!-- - Other... Please describe: --> ## What is the current behavior? <!-- Please describe the current behavior that you are modifying, or link to a relevant issue. --> There is currently no way to interoperate between the `IBufferWriter<T>` interface and the `Stream` class. Many APIs in the BCL and in 3rd party libraries use `Stream` as the standard way to accept an instance that can be written to or read from, and there is no built-in way to have a memory stream that is also using memory pooling, because none of the types in the BCL and in the `HighPerformance` package currently support both features at the same time. This PR fixes that 😄🚀 Consider this example that I saw from a user in the C# Discord server: ```csharp public byte[] Compress(byte[] source) { MemoryStream output = new MemoryStream(); using (DeflateStream dstream = new DeflateStream(output, CompressionLevel.Optimal)) { dstream.Write(source, 0, source.Length); } return output.ToArray(); } public byte[] Decompress(byte[] source) { MemoryStream input = new MemoryStream(source); MemoryStream output = new MemoryStream(); using (DeflateStream dstream = new DeflateStream(input, CompressionMode.Decompress)) { dstream.CopyTo(output); } return output.ToArray(); } ``` You can see how the code is very memory inefficient: the `MemoryStream` type will just `new`-up arrays as it goes, and at the end `ToArray()` is used too, which will duplicate the arrays too. Even by removing that, the main issue within `MemoryStream` remains. With the new extension introduced in this PR, these two APIs can be rewritten much more efficiently, like this: ```csharp public IMemoryOwner<byte> Compress(ReadOnlySpan<byte> span) { ArrayPoolBufferWriter<byte> bufferWriter = new ArrayPoolBufferWriter<byte>(); using DeflateStream deflateStream = new DeflateStream(bufferWriter.AsStream(), CompressionLevel.Optimal); deflateStream.Write(span); return bufferWriter; } public IMemoryOwner<byte> Decompress(ReadOnlyMemory<byte> memory) { ArrayPoolBufferWriter<byte> bufferWriter = new ArrayPoolBufferWriter<byte>(memory.Length); using DeflateStream deflateStream = new DeflateStream(memory.AsStream(), CompressionMode.Decompress); deflateStream.CopyTo(bufferWriter.AsStream()); return bufferWriter; } ``` Which heavily leverages all the various APIs and helpers in the `HighPerformance` package, and gives us the following results: | Method | Categories | Mean | Error | StdDev | Ratio | Gen 0 | Gen 1 | Gen 2 | Allocated | |------- |----------- |------------:|----------:|----------:|------:|---------:|---------:|---------:|----------:| | new[] | COMPRESS | 29,923.5 us | 174.19 us | 162.94 us | 1.00 | 312.5000 | 312.5000 | 312.5000 | 3089853 B | | **pool** | COMPRESS | **29,116.0 us** | 120.55 us | 106.87 us | **0.97** | - | - | - | **297 B** | | | | | | | | | | | | | new[] | DECOMPRESS | 832.9 us | 9.96 us | 8.83 us | 1.00 | 337.8906 | 336.9141 | 336.9141 | 2966680 B | | **pool** | DECOMPRESS | **119.6 us** | 0.70 us | 0.62 us | **0.14** | - | - | - | **392 B** | This benchmark compresses and decompresses a 1MB buffer, using the two methods detailed above. You can see the vastly reduced memory allocations using the pooled writer backed stream 🚀 ## What is the new behavior? <!-- Describe how was this issue resolved or changed? --> This PR introduces this new extension: ```csharp namespace Microsoft.Toolkit.HighPerformance.Extensions { public static class ArrayPoolBufferWriterExtensions { public static Stream AsStream(this ArrayPoolBufferWriter<byte> writer); } public static class IBufferWriterExtensions { public static Stream AsStream(this IBufferWriter<byte> writer); } } ``` Which helps to interoperate between the `IBufferWriter<T>` interface and the `Stream` class. In particular, since the `HighPerformance` package includes the `ArrayPoolBufferWriter<T>` type, this extension allows users to use that as a `Stream`, and then keep working with the resulting `ReadOnlyMemory<T>` produced by that type, as shown above. ## PR Checklist Please check if your PR fulfills the following requirements: - [X] Tested code with current [supported SDKs](../readme.md#supported) - [ ] ~~Pull Request has been submitted to the documentation repository [instructions](..\contributing.md#docs). Link: <!-- docs PR link -->~~ - [ ] ~~Sample in sample app has been added / updated (for bug fixes / features)~~ - [ ] ~~Icon has been created (if new sample) following the [Thumbnail Style Guide and templates](https://github.com/windows-toolkit/WindowsCommunityToolkit-design-assets)~~ - [X] Tests for the changes have been added (for bug fixes / features) (if applicable) - [X] Header has been added to all new source files (run *build/UpdateHeaders.bat*) - [X] Contains **NO** breaking changes
Description
.NET Community Toolkit is a collection of helpers and APIs that work for all .NET developers and are agnostic of any specific UI platform. The toolkit is maintained and published by Microsoft, and part of the .NET Foundation.
6.8 MiB
Languages
C#
100%