From e4446f4af004f5c040d4f39c3921c630b202924f Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 9 Mar 2020 20:11:41 +0100 Subject: [PATCH 01/32] Added ParallelHelper docs page --- docs/helpers/ParallelHelper.md | 94 ++++++++++++++++++++++++++++++++++ docs/toc.md | 1 + 2 files changed, 95 insertions(+) create mode 100644 docs/helpers/ParallelHelper.md diff --git a/docs/helpers/ParallelHelper.md b/docs/helpers/ParallelHelper.md new file mode 100644 index 000000000..38fde4296 --- /dev/null +++ b/docs/helpers/ParallelHelper.md @@ -0,0 +1,94 @@ +--- +title: ParallelHelper +author: sSrgio0694 +description: Helpers to work with parallel code in a highly optimized manner +keywords: windows 10, uwp, windows community toolkit, uwp community toolkit, uwp toolkit, parallel, high performance +dev_langs: + - csharp +--- + +# ParallelHelper + +The [ParallelHelper](https://docs.microsoft.com/dotnet/api/microsoft.toolkit.uwp.helpers.parallelhelper) contains high performance APIs to work with parallel code. It contains performance oriented methods that can be used to quickly setup and execute paralell operations over a given data set or iteration range or area. + +## How it works + +`ParallelHelper` is built around three main concepts: + +- It performs automatic batching over the target iteration range. This means that it automatically schedules the right number of working units based on the number of available CPU cores. This is done to reduce the overhead of invoking the parallel callback once for every single parallel iteration. +- It heavily leverages the way generic types are implemented in C#, and uses `struct` types implementing specific interfaces instead of delegates like `Action`. This is done so that the JIT compiler will be able to "see" each individual callback type being used, which allows it to inline the callback entirely, when possible. This can greatly reduce the overhead of each parallel iteration, especially when using very small callbacks, which would have a trivial cost with respect to the delegate invocation alone. Additionally, using a `struct` type as callback requires developers to manually handle variables that are being captured in the closure, which prevents accidental captures of the `this` pointer from instance methods and other values that could considerably slowdown each callback invocation. This is the same approach that is used in other performance-oriented libraries such as `ImageSharp`. +- It exposes 4 types of APIs that represent 4 different types of iterations: 1D and 2D loops, items iteration with side effect and items iteration without side effect. Each type of action has a corresponding `interface` type that needs to be applied to the `struct` callbacks being passed to the `ParallelHelper` APIs: these are `IAction`, `IAction2D`, `IRefAction` and `IInAction`. This helps developers to write code that is clearer regarding its intent, and allows the APIs to perform further optimizations internally. + +## Syntax + +Here is an example that shows how to initialize all the items of an array in parallel: + +```csharp +// Be sure to include this using at the top of the file: +using Microsoft.Toolkit.HighPerformance; + +// First declare the struct callback +public readonly struct ArrayInitializer : IAction +{ + private int[] array; + + public ArrayInitializer(int[] array) + { + this.array = array; + } + + public void Invoke(int i) + { + this.array[i] = i; + } +} + +// Create an array and run the callback +int[] array = new int[10000]; + +ParallelHelper.For(0, array.Length, new ArrayInitializer(array)); +``` + +In this case, we have used the `IAction` `interface`, as we needed the index as input for each item to process. We also had to manually specify the `int[] array` field in the `struct` type to be able to reuse it in our callback: this is exactly the same thing that the C# compiler does behind the scenes when we declare a lambda function or local function that accesses some local variable as well, we just made it explicit in this case. + +Let's say we're instead interested in processing all the items in some `float[]` array, and to multiply each of them by `2`. In that case we don't actually need to capture any variables: we can just use the `IRefAction` `interface` and `ParallelHelper` will load each item to feed to our callback automatically. + +```csharp +public readonly struct ByTwoMultiplier : IRefAction +{ + public void Invoke(ref float x) => x *= 2; +} + +// Create an array and run the callback +float[] array = new int[10000]; + +ParallelHelper.ForEach(array); +``` + +In this case, just like with a `foreach` loop, we don't even need to specify the iteration ranges: `ParallelHelper` will process each input item automatically. Furthermore, in this specific example we didn't even have to pass our `struct` as an argument: since it didn't contain any fields we needed to initialize, we could just specify its type as a type argument when invoking `ParallelHelper.ForEach`: that API will then create a new instance of that `struct` on its own, and use that to process the various items. + +## Methods + +These are the 4 main APIs exposed by `ParallelHelper`, corresponding to the `IAction`, `IAction2D`, `IRefAction` and `IInAction` interfaces. The `ParallelHelper` type also exposes a number of overloads for these methods, that offer a number of ways to specify the iteration range(s), or the type of input callback. + +| Methods | Return Type | Description | +| -- | -- | -- | +| For(int, int, in TAction) | void | Executes a specified action in an optimized parallel loop | +| For2D(int, int, int, int, in TAction) | void | Executes a specified action in an optimized parallel loop | +| ForEach(Memory, in TAction) | void | Executes a specified action in an optimized parallel loop over the input data | +| ForEach(ReadOnlyMemory, in TAction) | void | Executes a specified action in an optimized parallel loop over the input data | + +## Sample Code + +You can find more examples in our [unit tests](https://github.com/Microsoft/WindowsCommunityToolkit//blob/master/UnitTests/UnitTests.HighPerformance.Shared/Helpers) + +## Requirements + +| Device family | Universal, 10.0.16299.0 or higher | +| --- | --- | +| Namespace | Microsoft.Toolkit.HighPerformance | +| NuGet package | [Microsoft.Toolkit.HighPerformance](https://www.nuget.org/packages/Microsoft.Toolkit.HighPerformance/) | + +## API + +* [ParallelHelper source code](https://github.com/Microsoft/WindowsCommunityToolkit//blob/master/Microsoft.Toolkit.HighPerformance/Helpers) diff --git a/docs/toc.md b/docs/toc.md index 1c9c0cf54..b5f498258 100644 --- a/docs/toc.md +++ b/docs/toc.md @@ -141,6 +141,7 @@ ## [IncrementalLoadingCollection](helpers/IncrementalLoadingCollection.md) ## [NetworkHelper](helpers/NetworkHelper.md) ## [ObjectStorage](helpers/ObjectStorage.md) +## [ParallelHelper](helpers/ParallelHelper.md) ## [PrintHelper](helpers/PrintHelper.md) ## [RemoteDeviceHelper](helpers/RemoteDeviceHelper.md) ## [StorageFileHelper](helpers/StorageFiles.md) From 5d7538ab31d18df9ca8760a46e746f8402b54968 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 9 Mar 2020 20:16:18 +0100 Subject: [PATCH 02/32] Fixed a typo --- docs/helpers/ParallelHelper.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/helpers/ParallelHelper.md b/docs/helpers/ParallelHelper.md index 38fde4296..4515bd19d 100644 --- a/docs/helpers/ParallelHelper.md +++ b/docs/helpers/ParallelHelper.md @@ -1,6 +1,6 @@ --- title: ParallelHelper -author: sSrgio0694 +author: Sergio0694 description: Helpers to work with parallel code in a highly optimized manner keywords: windows 10, uwp, windows community toolkit, uwp community toolkit, uwp toolkit, parallel, high performance dev_langs: From 4e759c1edba302093c53d0d1da2643251ebdb52a Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 9 Mar 2020 21:54:31 +0100 Subject: [PATCH 03/32] Minor tweaks --- docs/helpers/ParallelHelper.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/helpers/ParallelHelper.md b/docs/helpers/ParallelHelper.md index 4515bd19d..7b398e7bd 100644 --- a/docs/helpers/ParallelHelper.md +++ b/docs/helpers/ParallelHelper.md @@ -2,14 +2,14 @@ title: ParallelHelper author: Sergio0694 description: Helpers to work with parallel code in a highly optimized manner -keywords: windows 10, uwp, windows community toolkit, uwp community toolkit, uwp toolkit, parallel, high performance +keywords: windows 10, uwp, windows community toolkit, uwp community toolkit, uwp toolkit, parallel, high performance, net core, net standard dev_langs: - csharp --- # ParallelHelper -The [ParallelHelper](https://docs.microsoft.com/dotnet/api/microsoft.toolkit.uwp.helpers.parallelhelper) contains high performance APIs to work with parallel code. It contains performance oriented methods that can be used to quickly setup and execute paralell operations over a given data set or iteration range or area. +The [ParallelHelper](https://docs.microsoft.com/dotnet/api/microsoft.toolkit.highperformance.helpers.parallelhelper) contains high performance APIs to work with parallel code. It contains performance oriented methods that can be used to quickly setup and execute paralell operations over a given data set or iteration range or area. ## How it works From 3e7e5ed725f12fe2f8624cad385845193c9ed83f Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 12 Mar 2020 18:36:35 +0100 Subject: [PATCH 04/32] Fixed displaying of '<' character --- docs/helpers/ParallelHelper.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/helpers/ParallelHelper.md b/docs/helpers/ParallelHelper.md index 7b398e7bd..58783edad 100644 --- a/docs/helpers/ParallelHelper.md +++ b/docs/helpers/ParallelHelper.md @@ -73,10 +73,10 @@ These are the 4 main APIs exposed by `ParallelHelper`, corresponding to the `IAc | Methods | Return Type | Description | | -- | -- | -- | -| For(int, int, in TAction) | void | Executes a specified action in an optimized parallel loop | -| For2D(int, int, int, int, in TAction) | void | Executes a specified action in an optimized parallel loop | -| ForEach(Memory, in TAction) | void | Executes a specified action in an optimized parallel loop over the input data | -| ForEach(ReadOnlyMemory, in TAction) | void | Executes a specified action in an optimized parallel loop over the input data | +| For<TAction>(int, int, in TAction) | void | Executes a specified action in an optimized parallel loop | +| For2D<TAction>(int, int, int, int, in TAction) | void | Executes a specified action in an optimized parallel loop | +| ForEach<TItem,TAction>(Memory, in TAction) | void | Executes a specified action in an optimized parallel loop over the input data | +| ForEach<TItem,TAction>(ReadOnlyMemory, in TAction) | void | Executes a specified action in an optimized parallel loop over the input data | ## Sample Code From 9ec494793dd7a02298803c70e50c41678be5508e Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 13 Mar 2020 13:17:55 +0100 Subject: [PATCH 05/32] Created high-performance section, moved ParallelHelper docs --- docs/{helpers => high-performance}/ParallelHelper.md | 0 docs/toc.md | 4 +++- 2 files changed, 3 insertions(+), 1 deletion(-) rename docs/{helpers => high-performance}/ParallelHelper.md (100%) diff --git a/docs/helpers/ParallelHelper.md b/docs/high-performance/ParallelHelper.md similarity index 100% rename from docs/helpers/ParallelHelper.md rename to docs/high-performance/ParallelHelper.md diff --git a/docs/toc.md b/docs/toc.md index b5f498258..3646c4882 100644 --- a/docs/toc.md +++ b/docs/toc.md @@ -141,7 +141,6 @@ ## [IncrementalLoadingCollection](helpers/IncrementalLoadingCollection.md) ## [NetworkHelper](helpers/NetworkHelper.md) ## [ObjectStorage](helpers/ObjectStorage.md) -## [ParallelHelper](helpers/ParallelHelper.md) ## [PrintHelper](helpers/PrintHelper.md) ## [RemoteDeviceHelper](helpers/RemoteDeviceHelper.md) ## [StorageFileHelper](helpers/StorageFiles.md) @@ -165,6 +164,9 @@ ## [MarkdownParser](parsers/MarkdownParser.md) ## [RSSParser](parsers/RSSParser.md) +# High performance +## [ParallelHelper](high-performance/ParallelHelper.md) + # Developer tools ## [AlignmentGrid](developer-tools/AlignmentGrid.md) ## [FocusTracker](developer-tools/FocusTracker.md) From d7ffa6f78472a4f3dfa972c713772a8dad12ec0a Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 13 Mar 2020 14:00:34 +0100 Subject: [PATCH 06/32] Added introduction for the package --- docs/high-performance/Introduction.md | 39 +++++++++++++++++++++++++++ docs/toc.md | 1 + 2 files changed, 40 insertions(+) create mode 100644 docs/high-performance/Introduction.md diff --git a/docs/high-performance/Introduction.md b/docs/high-performance/Introduction.md new file mode 100644 index 000000000..6d41dffa1 --- /dev/null +++ b/docs/high-performance/Introduction.md @@ -0,0 +1,39 @@ +--- +title: Introduction to the High Performance package +author: Sergio0694 +description: An overview of how to get started with High Performance package and to the APIs it contains +keywords: windows 10, uwp, windows community toolkit, uwp community toolkit, uwp toolkit, get started, visual studio, high performance, net core, net standard +--- + +# Introduction to the High Performance package + +This package can be installed through NuGet, and it multitargets .NET Standard 2.0 and .NET Standard 2.1. This means that you can use it from both UWP apps, as well as modern .NET Core 3.0 applications. The API surface is almost identical in both cases, and lots of work has been put into backporting as many features as possible to .NET Standard 2.0 as well. Except for some minor differences, you can expect the same APIs to be available on both target frameworks. + +Follow these steps to install the High Performance package: + +1. Open an existing project in Visual studio, targeting any of the following: + - UWP (SDK >= 16299) + - .NET Standard (>= 2.0) + - .NET Core (>= 2.1) + - Any other framework supporting .NET Standard 2.0 and up + +2. In Solution Explorer panel, right click on your project name and select **Manage NuGet Packages**. Search for **Microsoft.Toolkit.HighPerformance** and install it. + + ![NuGet Packages](../resources/images/ManageNugetPackages.png "Manage NuGet Packages Image") + +3. Add a using directive in your C# files to use the new APIs: + + ```c# + using Microsoft.Toolkit.HighPerformance; + ``` + +4. If you want so see some code samples, you can either read through the other docs pages for the High Performance package, or have a look at the various [unit tests](https://github.com/Microsoft/WindowsCommunityToolkit//blob/master/UnitTests/UnitTests.HighPerformance.Shared/Helpers) for the project. + +## When should I use this package? + +As the name suggests, the High Performance package contains a set of APIs that are heavily focused on optimization. All the new APIs have been carefully crafted to achieve the best possible performance when using them, either through reduced memory allocation, micro-optimizations at the assembly level, or by structuring the APIs in a way that facilitates writing performance oriented code in general. + +This package makes heavy use of APIs such as [`System.Buffers.ArrayPool`](https://docs.microsoft.com/en-us/dotnet/api/system.buffers.arraypool-1), helpers from the [`System.Runtime.CompilerServices.Unsafe`](https://docs.microsoft.com/en-us/dotnet/api/system.runtime.compilerservices.unsafe) and [`System.Runtime.InteropServices.MemoryMarshal`](https://docs.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.memorymarshal) classes, APIs from the [`System.Threading.Tasks.Parallel`](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.parallel) class, and more. + +If you are already familiar with these APIs or even if you're just getting started with writing high performance code in C# and want a set of well tested helpers to use in your own projects, have a look at what's included in this package to see how you can use it in your own projects! + diff --git a/docs/toc.md b/docs/toc.md index 3646c4882..04ea5b0e6 100644 --- a/docs/toc.md +++ b/docs/toc.md @@ -165,6 +165,7 @@ ## [RSSParser](parsers/RSSParser.md) # High performance +## [Introduction](high-performance/Introduction.md) ## [ParallelHelper](high-performance/ParallelHelper.md) # Developer tools From 74ccfba6fee91c31a3a05c8b5fc3b1b61d09b9bb Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 13 Mar 2020 16:53:44 +0100 Subject: [PATCH 07/32] Improved ParallelHelper docs --- docs/high-performance/ParallelHelper.md | 62 +++++++++++++++++-------- 1 file changed, 43 insertions(+), 19 deletions(-) diff --git a/docs/high-performance/ParallelHelper.md b/docs/high-performance/ParallelHelper.md index 58783edad..31d04e56f 100644 --- a/docs/high-performance/ParallelHelper.md +++ b/docs/high-performance/ParallelHelper.md @@ -21,12 +21,52 @@ The [ParallelHelper](https://docs.microsoft.com/dotnet/api/microsoft.toolkit.hig ## Syntax -Here is an example that shows how to initialize all the items of an array in parallel: +Let's say we're interested in processing all the items in some `float[]` array, and to multiply each of them by `2`. In this case we don't need to capture any variables: we can just use the `IRefAction` `interface` and `ParallelHelper` will load each item to feed to our callback automatically. All that's needed is to define our callback, that will receive a `ref float` argument and perform the necessary operation: ```csharp // Be sure to include this using at the top of the file: -using Microsoft.Toolkit.HighPerformance; +using Microsoft.Toolkit.HighPerformance.Helpers; +public readonly struct ByTwoMultiplier : IRefAction +{ + public void Invoke(ref float x) => x *= 2; +} + +// Create an array and run the callback +float[] array = new int[10000]; + +ParallelHelper.ForEach(array); +``` + +With the `ForEach` API, we don't need to specify the iteration ranges: `ParallelHelper` will batch the collection and process each input item automatically. Furthermore, in this specific example we didn't even have to pass our `struct` as an argument: since it didn't contain any fields we needed to initialize, we could just specify its type as a type argument when invoking `ParallelHelper.ForEach`: that API will then create a new instance of that `struct` on its own, and use that to process the various items. + +To introduce the concept of closures, suppose we want to multiply the array elements by a value that is specified at runtime. To do so, we need to "capture" that value in our callback `struct` type. We can do that like so: + +```csharp +public readonly struct ItemsMultiplier : IRefAction +{ + private readonly float factor; + + public ItemsMultiplier(float factor) + { + this.factor = factor; + } + + public void Invoke(ref float x) => x *= this.factor; +} + +// ... + +ParallelHelper.ForEach(array, new ItemsMultiplier(3.14f)); +``` + +We can see that the `struct` now contains a field that represents the factor we want to use to multiply elements, instead of using a constant. And when invoking `ForEach`, we're explicitly creating an instance of our callback type, with the factor we're interested in. Furthermore, in this case the C# compiler is also able to automatically recognize the type arguments we're using, so we can omit them together from the method invocation. + +This approach of creating fields for values we need to access from a callback lets us explicitly declare what values we want to capture, which helps makes the code more expressive. This is exactly the same thing that the C# compiler does behind the scenes when we declare a lambda function or local function that accesses some local variable as well. + +Here is another example, this time using the `For` API to initialize all the items of an array in parallel. Note how this time we're capturing the target array directly, and we're using the `IAction` `interface` for our callback, which gives our method the current parallel iteration index as argument: + +```csharp // First declare the struct callback public readonly struct ArrayInitializer : IAction { @@ -49,23 +89,7 @@ int[] array = new int[10000]; ParallelHelper.For(0, array.Length, new ArrayInitializer(array)); ``` -In this case, we have used the `IAction` `interface`, as we needed the index as input for each item to process. We also had to manually specify the `int[] array` field in the `struct` type to be able to reuse it in our callback: this is exactly the same thing that the C# compiler does behind the scenes when we declare a lambda function or local function that accesses some local variable as well, we just made it explicit in this case. - -Let's say we're instead interested in processing all the items in some `float[]` array, and to multiply each of them by `2`. In that case we don't actually need to capture any variables: we can just use the `IRefAction` `interface` and `ParallelHelper` will load each item to feed to our callback automatically. - -```csharp -public readonly struct ByTwoMultiplier : IRefAction -{ - public void Invoke(ref float x) => x *= 2; -} - -// Create an array and run the callback -float[] array = new int[10000]; - -ParallelHelper.ForEach(array); -``` - -In this case, just like with a `foreach` loop, we don't even need to specify the iteration ranges: `ParallelHelper` will process each input item automatically. Furthermore, in this specific example we didn't even have to pass our `struct` as an argument: since it didn't contain any fields we needed to initialize, we could just specify its type as a type argument when invoking `ParallelHelper.ForEach`: that API will then create a new instance of that `struct` on its own, and use that to process the various items. +**NOTE:** since the callback types are `struct`-s, they're passed _by copy_ to each thread running parallel, not by reference. This means that value types being stored as fields in a callback types will be copied as well. A good practice to remember that detail and avoid errors is to mark the callback `struct` as `readonly`, so that the C# compiler will not let us modify the values of its fields. This only applies to _instance_ fields of a value type: if a callback `struct` has a `static` field of any type, or a reference field, then that value will correctly be shared between parallel threads. ## Methods From b4bae087f1df5a7050a951e0153e2e641cda5b28 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 13 Mar 2020 17:27:21 +0100 Subject: [PATCH 08/32] Added MemoryOwner docs page --- docs/high-performance/MemoryOwner.md | 72 +++++++++++++++++++++++++ docs/high-performance/ParallelHelper.md | 2 +- docs/toc.md | 1 + 3 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 docs/high-performance/MemoryOwner.md diff --git a/docs/high-performance/MemoryOwner.md b/docs/high-performance/MemoryOwner.md new file mode 100644 index 000000000..071e4c193 --- /dev/null +++ b/docs/high-performance/MemoryOwner.md @@ -0,0 +1,72 @@ +--- +title: MemoryOwner<T> and SpanOwner<T> +author: Sergio0694 +description: Buffer types that rent memory from a shared pool +keywords: windows 10, uwp, windows community toolkit, uwp community toolkit, uwp toolkit, parallel, high performance, net core, net standard +dev_langs: + - csharp +--- + +# MemoryOwner<T> + +The [MemoryOwner<T>](https://docs.microsoft.com/dotnet/api/microsoft.toolkit.highperformance.buffers.memoryowner-1) is a buffer type implementing `IMemoryOwner`, an embedded length property and a series of performance oriented APIs. It is essentially a lightweight wrapper around the `ArrayPool` type, with some additional helper utilities. + +## How it works + +`MemoryOwner` has the following main features: + +- One of the main issues of arrays returned by the `ArrayPool` APIs and of the `IMemoryOwner` instances returned by the `MemoryPool` APIs is that the size specified by the user is only being used as a _minum_ size: the actual size of the returned buffers might actually be greater. `MemoryOwner` solves this by also storing the original requested size, so that `Memory` and `Span` instances retrieved from it will never need to be manually sliced. +- When using `IMemoryOwner`, getting a `Span` for the underlying buffer requires first to get a `Memory` instance, and then a `Span`. This is fairly expensive, and often unnecessary, as the intermediate `Memory` might actually not be needed at all. `MemoryOwner` instead has an additional `Span` property which is extremely lightweight, as it directly wraps the internal `T[]` array being rented from the pool. +- Buffers rented from the pool are not cleared by default, which means that if they were not cleared when being previous returned to the pool, they might contain garbage data. Normally, users are required to clear these rented buffers manually, which can be verbose especially when done frequently. `MemoryOwner` has a more flexible approach to this, through the `Allocate(int, AllocationMode)` API. This method not only allocates a new instance of exactly the requested size, but can also be used to specify which allocation mode to use: either the same one as `ArrayPool`, or one that automatically clears the rented buffer. +- There are cases where a buffer might be rented with a greater size than what is actually needed, and then resized afterwards. This would normally require users to rent a new buffer and copy the region of interest from the old buffer. Instead, `MemoryOwner` exposes a `Slice(int, int)` API that simply return a new instance wrapping the specified area of interest. This allows to skip renting a new buffer and copying the items entirely. + +## Syntax + +Here is an example of how to rent a buffer and retrieve a `Memory` instance: + +```csharp +// Be sure to include this using at the top of the file: +using Microsoft.Toolkit.HighPerformance.Buffers; + +using (MemoryOwner buffer = MemoryOwner.Allocate(42)) +{ + // Buffer has exactly 42 items + Memory memory = buffer.Memory; + Span span = buffer.Span; +} +``` + +In this example, we used a `using` block to declare the `MemoryOwner` buffer: this is particularly useful as the underlying array will automatically be returned to the pool at the end of the block. If instead we don't have direct control over the lifetime of a `MemoryOwner` instance, the buffer will simply be returned to the pool when the object is finalized by the garbage collector. In both cases, rented buffers will always be correctly returned to the shared pool. + +## Properties + +| Property | Return Type | Description | +| -- | -- | -- | +| Length | int | Gets the number of items in the current instance | +| Memory | System.Memory<T> | Gets the memory belonging to this owner | +| Span | System.Span<T> | Gets a span wrapping the memory belonging to the current instance | +| Empty | MemoryOwner<T> | Gets an empty `MemoryOwner` instance | + +## Methods + +| Method | Return Type | Description | +| -- | -- | -- | +| Allocate(int) | Memory<T> | Creates a new `MemoryOwner` instance with the specified parameters | +| Allocate(int, AllocationMode) | Memory<T> | Creates a new `MemoryOwner` instance with the specified parameters | +| DangerousGetReference() | ref T | Returns a reference to the first element within the current instance, with no bounds check | +| Slice(int, int) | MemoryOwner<T> | Slices the buffer currently in use and returns a new `MemoryOwner` instance | + +## Sample Code + +You can find more examples in our [unit tests](https://github.com/Microsoft/WindowsCommunityToolkit//blob/master/UnitTests/UnitTests.HighPerformance.Shared/Buffers) + +## Requirements + +| Device family | Universal, 10.0.16299.0 or higher | +| --- | --- | +| Namespace | Microsoft.Toolkit.HighPerformance | +| NuGet package | [Microsoft.Toolkit.HighPerformance](https://www.nuget.org/packages/Microsoft.Toolkit.HighPerformance/) | + +## API + +* [MemoryOwner<T> source code](https://github.com/Microsoft/WindowsCommunityToolkit//blob/master/Microsoft.Toolkit.HighPerformance/Buffers) diff --git a/docs/high-performance/ParallelHelper.md b/docs/high-performance/ParallelHelper.md index 31d04e56f..7d69da9a6 100644 --- a/docs/high-performance/ParallelHelper.md +++ b/docs/high-performance/ParallelHelper.md @@ -95,7 +95,7 @@ ParallelHelper.For(0, array.Length, new ArrayInitializer(array)); These are the 4 main APIs exposed by `ParallelHelper`, corresponding to the `IAction`, `IAction2D`, `IRefAction` and `IInAction` interfaces. The `ParallelHelper` type also exposes a number of overloads for these methods, that offer a number of ways to specify the iteration range(s), or the type of input callback. -| Methods | Return Type | Description | +| Method | Return Type | Description | | -- | -- | -- | | For<TAction>(int, int, in TAction) | void | Executes a specified action in an optimized parallel loop | | For2D<TAction>(int, int, int, int, in TAction) | void | Executes a specified action in an optimized parallel loop | diff --git a/docs/toc.md b/docs/toc.md index 04ea5b0e6..f036afff3 100644 --- a/docs/toc.md +++ b/docs/toc.md @@ -166,6 +166,7 @@ # High performance ## [Introduction](high-performance/Introduction.md) +## [MemoryOwner<T> and SpanOwner<T>](high-performance/MemoryOwner.md) ## [ParallelHelper](high-performance/ParallelHelper.md) # Developer tools From ec157972a3572e748bc659f134c7e997e9d96e9c Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 13 Mar 2020 17:29:13 +0100 Subject: [PATCH 09/32] Removed SpanOwner mention from MemoryOwner page --- docs/high-performance/MemoryOwner.md | 4 ++-- docs/toc.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/high-performance/MemoryOwner.md b/docs/high-performance/MemoryOwner.md index 071e4c193..5b5ed0a86 100644 --- a/docs/high-performance/MemoryOwner.md +++ b/docs/high-performance/MemoryOwner.md @@ -1,7 +1,7 @@ --- -title: MemoryOwner<T> and SpanOwner<T> +title: MemoryOwner<T> author: Sergio0694 -description: Buffer types that rent memory from a shared pool +description: A buffer type implementing `IMemoryOwner` that rents memory from a shared pool keywords: windows 10, uwp, windows community toolkit, uwp community toolkit, uwp toolkit, parallel, high performance, net core, net standard dev_langs: - csharp diff --git a/docs/toc.md b/docs/toc.md index f036afff3..cf5499588 100644 --- a/docs/toc.md +++ b/docs/toc.md @@ -166,7 +166,7 @@ # High performance ## [Introduction](high-performance/Introduction.md) -## [MemoryOwner<T> and SpanOwner<T>](high-performance/MemoryOwner.md) +## [MemoryOwner<T>](high-performance/MemoryOwner.md) ## [ParallelHelper](high-performance/ParallelHelper.md) # Developer tools From 96d2342145e8d69e289f4dcd444e34060b401a91 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 13 Mar 2020 17:57:52 +0100 Subject: [PATCH 10/32] Added SpanOwner docs page --- docs/high-performance/SpanOwner.md | 60 ++++++++++++++++++++++++++++++ docs/toc.md | 1 + 2 files changed, 61 insertions(+) create mode 100644 docs/high-performance/SpanOwner.md diff --git a/docs/high-performance/SpanOwner.md b/docs/high-performance/SpanOwner.md new file mode 100644 index 000000000..7a63f12d3 --- /dev/null +++ b/docs/high-performance/SpanOwner.md @@ -0,0 +1,60 @@ +--- +title: Spanowner<T> +author: Sergio0694 +description: A stack-only buffer type renting memory from a shared pool +keywords: windows 10, uwp, windows community toolkit, uwp community toolkit, uwp toolkit, parallel, high performance, net core, net standard +dev_langs: + - csharp +--- + +# SpanOwner<T> + +The [SpanOwner<T>](https://docs.microsoft.com/dotnet/api/microsoft.toolkit.highperformance.buffers.spanowner-1) is a stack-only buffer type that rents buffers from a shared memory pool. It essentially mirrors the functionality of `MemoryOwner`, but as a `ref struct` type. This is particularly useful for short-lived buffers that are only used in synchronous code (that don't require `Memory` instances), as well as code running in a tight loop, as creating `SpanOwner` values will not require memory allocations at all. + +## Syntax + +The same core features of `MemoryOwner` apply to this type as well, with the exception of it being a stack-only `struct`, and the fact that it lacks the `IMemoryOwner` `interface` implementation, as well as the `Memory` property. The syntax is virtually identical to that used with `MemoryOwner` as well: + +```csharp +// Be sure to include this using at the top of the file: +using Microsoft.Toolkit.HighPerformance.Buffers; + +using (SpanOwner buffer = SpanOwner.Allocate(42)) +{ + // Buffer has exactly 42 items + Span span = buffer.Span; +} +``` + +**NOTEL** as this is a stack-only type, it relies on the duck-typed `IDisposable` pattern introduced with C# 8. That is shown in the sample above: the `SpanOwner` type is being used within a `using` block despite the fact that the type doesn't implement the `IDisposable` interface at all, and it's also never boxed. The functionality is just the same: as soon as the buffer goes out of scope, it is automatically disposed. The APIs in `SpanOwner{T}` rely on this pattern for extra performance: they assume that the underlying buffer will never be disposed as long as the `SpanOwner` type is in scope, and they don't perform the additional checks that are done in `MemoryOwner` to ensure that the buffer is in fact still available before returning a `Memory` or `Span` instance from it. As such, this type should always be used with a `using` block or expression. Not doing so will cause the underlying buffer not to be returned to the shared pool. Technically the same can also be achieved by manually calling `Dispose` on the `SpanOwner` type (which doesn't require C# 8), but that is error prone and hence not recommended. + +## Properties + +| Property | Return Type | Description | +| -- | -- | -- | +| Length | int | Gets the number of items in the current instance | +| Span | System.Span<T> | Gets a span wrapping the memory belonging to the current instance | +| Empty | SpanOwner<T> | Gets an empty `SpanOwner` instance | + +## Methods + +| Method | Return Type | Description | +| -- | -- | -- | +| Allocate(int) | Memory<T> | Creates a new `SpanOwner` instance with the specified parameters | +| Allocate(int, AllocationMode) | Memory<T> | Creates a new `SpanOwner` instance with the specified parameters | +| DangerousGetReference() | ref T | Returns a reference to the first element within the current instance, with no bounds check | + +## Sample Code + +You can find more examples in our [unit tests](https://github.com/Microsoft/WindowsCommunityToolkit//blob/master/UnitTests/UnitTests.HighPerformance.Shared/Buffers) + +## Requirements + +| Device family | Universal, 10.0.16299.0 or higher | +| --- | --- | +| Namespace | Microsoft.Toolkit.HighPerformance | +| NuGet package | [Microsoft.Toolkit.HighPerformance](https://www.nuget.org/packages/Microsoft.Toolkit.HighPerformance/) | + +## API + +* [SpanOwner<T> source code](https://github.com/Microsoft/WindowsCommunityToolkit//blob/master/Microsoft.Toolkit.HighPerformance/Buffers) diff --git a/docs/toc.md b/docs/toc.md index cf5499588..474e14742 100644 --- a/docs/toc.md +++ b/docs/toc.md @@ -167,6 +167,7 @@ # High performance ## [Introduction](high-performance/Introduction.md) ## [MemoryOwner<T>](high-performance/MemoryOwner.md) +## [SpanOwner<T>](high-performance/SpanOwner.md) ## [ParallelHelper](high-performance/ParallelHelper.md) # Developer tools From c1df57c4a354166a5d90feed7d8bee6d29e7e158 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 13 Mar 2020 17:58:32 +0100 Subject: [PATCH 11/32] Adjusted docs structure --- docs/toc.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/toc.md b/docs/toc.md index 474e14742..a66e21a0d 100644 --- a/docs/toc.md +++ b/docs/toc.md @@ -166,8 +166,9 @@ # High performance ## [Introduction](high-performance/Introduction.md) -## [MemoryOwner<T>](high-performance/MemoryOwner.md) -## [SpanOwner<T>](high-performance/SpanOwner.md) +## Buffers +### [MemoryOwner<T>](high-performance/MemoryOwner.md) +### [SpanOwner<T>](high-performance/SpanOwner.md) ## [ParallelHelper](high-performance/ParallelHelper.md) # Developer tools From cbb69542c9f8097d8aa926dfd66f15f2d82f64d4 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 13 Mar 2020 19:02:29 +0100 Subject: [PATCH 12/32] Added ByReference docs page --- docs/high-performance/ByReference.md | 106 +++++++++++++++++++++++++++ docs/toc.md | 1 + 2 files changed, 107 insertions(+) create mode 100644 docs/high-performance/ByReference.md diff --git a/docs/high-performance/ByReference.md b/docs/high-performance/ByReference.md new file mode 100644 index 000000000..7412a6de7 --- /dev/null +++ b/docs/high-performance/ByReference.md @@ -0,0 +1,106 @@ +--- +title: ByReference<T> and ReadOnlyByReference<T> +author: Sergio0694 +description: Two stack-only types that can store a reference to a value of a specified type +keywords: windows 10, uwp, windows community toolkit, uwp community toolkit, uwp toolkit, parallel, high performance, net core, net standard +dev_langs: + - csharp +--- + +# ByReference<T> + +The [ByReference<T>](https://docs.microsoft.com/dotnet/api/microsoft.toolkit.highperformance.byreference-1) is a stack-only type that can store a reference to a value of a specified type. It is semantically equivalent to a `ref T` value, with the difference that it can also be used as a type of field in another stack-only `struct` type. It can be used in place of proper `ref T` fields, which are currently not supported in C#. + +## How it works + +Due to how it's implemented on different .NET Standard contracts, the `ByReference` has some minor differences on .NET Standard 2.0 and .NET Standard 2.1. In particular, on .NET Standard 2.0 it can only be used to reference fields _within an object_, instead of arbitrary values pointed by a managed reference. This is because the underlying APIs being used on .NET Standard 2.0 don't have built-in support for the `Span` type. + +### `ByReference` on .NET Standard 2.0 + +As mentioned before, on .NET Standard 2.0, `ByReference` can only point to locations within a given `object`. Consider the following code: + +```csharp +// Be sure to include this using at the top of the file: +using Microsoft.Toolkit.HighPerformance; + +// Define a sample model +class MyModel +{ + public int Number = 42; +} + +var model = new MyModel(); + +// Create a ByReference pointing to the MyModel.Number field +ByReference byRef = new ByReference(model, ref model.Number); + +// Modify the field indirectly +byRef.Value++; + +Console.WriteLine(model.Number); // Prints 43! +``` + +**NOTE:** the `ByReference` constructor doesn't validate the input arguments, meaning that it is your responsability to pass a valid `object` and a reference to a field within that object. + +### `ByReference` on .NET Standard 2.1 + +On .NET Standard 2.1, `ByReference` can point to any `ref T` value: + +```csharp +int number = 42; + +// Create a ByReference pointing to 'number' +ByReference byRef1 = new ByReference(ref number); + +// Modify 'number' +byRef1.Value++; + +Console.WriteLine(number); // Prints 43! +``` + +**NOTE:** this type comes with a few caveats and should be used carefully, as it can lead to runtime crashes if a `ByReference` instance is created with an invalid reference. In particular, you should only create a `ByReference` instance pointing to values that have a lifetime that is greater than that of the `ByReference` in use. Consider the following snippet: + +```csharp +public static ref int GetDummyReference() +{ + int number = 42; + + ByReference byRef = new ByReference(ref number); + + return ref byRef.Value; +} +``` + +This will compile and run fine, but the returned `ref int` will be invalid (as in, it will not point to a valid location) and could cause crashes if it's dereferenced. It is your responsability to track the lifetime of values being referenced by new `ByReference` values. + +## Properties + +| Property | Return Type | Description | +| -- | -- | -- | +| Value | ref T | Gets the `T` reference represented by the current `ByReference{T}` instance | + +# ReadOnlyByReference<T> + +The [ReadOnlyByReference<T>](https://docs.microsoft.com/dotnet/api/microsoft.toolkit.highperformance.readonlybyreference-1) is a stack-only type that mirrors `ByReference`, with the exception that its constructor takes an `in T` parameter (a readonly reference), instead of a `ref T` one. Similarly, its `Value` property has a `ref readonly T` return type instead of `ref T`. + +## Properties + +| Property | Return Type | Description | +| -- | -- | -- | +| Value | ref readonly T | Gets the `T` reference represented by the current `ReadOnlyByReference{T}` instance | + +## Sample Code + +You can find more examples in our [unit tests](https://github.com/Microsoft/WindowsCommunityToolkit//blob/master/UnitTests/UnitTests.HighPerformance.Shared) + +## Requirements + +| Device family | Universal, 10.0.16299.0 or higher | +| --- | --- | +| Namespace | Microsoft.Toolkit.HighPerformance | +| NuGet package | [Microsoft.Toolkit.HighPerformance](https://www.nuget.org/packages/Microsoft.Toolkit.HighPerformance/) | + +## API + +* [ByReference<T> source code](https://github.com/Microsoft/WindowsCommunityToolkit//blob/master/Microsoft.Toolkit.HighPerformance) +* [ReadOnlyByReference<T> source code](https://github.com/Microsoft/WindowsCommunityToolkit//blob/master/Microsoft.Toolkit.HighPerformance) diff --git a/docs/toc.md b/docs/toc.md index a66e21a0d..1112bec19 100644 --- a/docs/toc.md +++ b/docs/toc.md @@ -170,6 +170,7 @@ ### [MemoryOwner<T>](high-performance/MemoryOwner.md) ### [SpanOwner<T>](high-performance/SpanOwner.md) ## [ParallelHelper](high-performance/ParallelHelper.md) +## [ByReference<T> and ReadOnlyByReference<T>](high-performance/ByReference.md) # Developer tools ## [AlignmentGrid](developer-tools/AlignmentGrid.md) From c8e6e87d32714cbd5b239cbccf675db53d2feadd Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sat, 14 Mar 2020 02:55:53 +0100 Subject: [PATCH 13/32] Minor tweaks --- docs/high-performance/ParallelHelper.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/high-performance/ParallelHelper.md b/docs/high-performance/ParallelHelper.md index 7d69da9a6..bac01ffa6 100644 --- a/docs/high-performance/ParallelHelper.md +++ b/docs/high-performance/ParallelHelper.md @@ -27,6 +27,7 @@ Let's say we're interested in processing all the items in some `float[]` array, // Be sure to include this using at the top of the file: using Microsoft.Toolkit.HighPerformance.Helpers; +// First declare the struct callback public readonly struct ByTwoMultiplier : IRefAction { public void Invoke(ref float x) => x *= 2; @@ -67,7 +68,6 @@ This approach of creating fields for values we need to access from a callback le Here is another example, this time using the `For` API to initialize all the items of an array in parallel. Note how this time we're capturing the target array directly, and we're using the `IAction` `interface` for our callback, which gives our method the current parallel iteration index as argument: ```csharp -// First declare the struct callback public readonly struct ArrayInitializer : IAction { private int[] array; @@ -83,8 +83,7 @@ public readonly struct ArrayInitializer : IAction } } -// Create an array and run the callback -int[] array = new int[10000]; +// ... ParallelHelper.For(0, array.Length, new ArrayInitializer(array)); ``` From 0b822c731c189dc474d027d083636add28db8db7 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 18 Mar 2020 15:14:48 +0100 Subject: [PATCH 14/32] Fixed a typo --- docs/high-performance/SpanOwner.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/high-performance/SpanOwner.md b/docs/high-performance/SpanOwner.md index 7a63f12d3..01d6bce22 100644 --- a/docs/high-performance/SpanOwner.md +++ b/docs/high-performance/SpanOwner.md @@ -26,7 +26,7 @@ using (SpanOwner buffer = SpanOwner.Allocate(42)) } ``` -**NOTEL** as this is a stack-only type, it relies on the duck-typed `IDisposable` pattern introduced with C# 8. That is shown in the sample above: the `SpanOwner` type is being used within a `using` block despite the fact that the type doesn't implement the `IDisposable` interface at all, and it's also never boxed. The functionality is just the same: as soon as the buffer goes out of scope, it is automatically disposed. The APIs in `SpanOwner{T}` rely on this pattern for extra performance: they assume that the underlying buffer will never be disposed as long as the `SpanOwner` type is in scope, and they don't perform the additional checks that are done in `MemoryOwner` to ensure that the buffer is in fact still available before returning a `Memory` or `Span` instance from it. As such, this type should always be used with a `using` block or expression. Not doing so will cause the underlying buffer not to be returned to the shared pool. Technically the same can also be achieved by manually calling `Dispose` on the `SpanOwner` type (which doesn't require C# 8), but that is error prone and hence not recommended. +**NOTE:** as this is a stack-only type, it relies on the duck-typed `IDisposable` pattern introduced with C# 8. That is shown in the sample above: the `SpanOwner` type is being used within a `using` block despite the fact that the type doesn't implement the `IDisposable` interface at all, and it's also never boxed. The functionality is just the same: as soon as the buffer goes out of scope, it is automatically disposed. The APIs in `SpanOwner{T}` rely on this pattern for extra performance: they assume that the underlying buffer will never be disposed as long as the `SpanOwner` type is in scope, and they don't perform the additional checks that are done in `MemoryOwner` to ensure that the buffer is in fact still available before returning a `Memory` or `Span` instance from it. As such, this type should always be used with a `using` block or expression. Not doing so will cause the underlying buffer not to be returned to the shared pool. Technically the same can also be achieved by manually calling `Dispose` on the `SpanOwner` type (which doesn't require C# 8), but that is error prone and hence not recommended. ## Properties From 422311d3b756590d74bc38c301b483b164c06990 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 18 Mar 2020 15:22:30 +0100 Subject: [PATCH 15/32] Minor fixes --- docs/high-performance/MemoryOwner.md | 6 +++--- docs/high-performance/SpanOwner.md | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/high-performance/MemoryOwner.md b/docs/high-performance/MemoryOwner.md index 5b5ed0a86..6a98a17de 100644 --- a/docs/high-performance/MemoryOwner.md +++ b/docs/high-performance/MemoryOwner.md @@ -28,11 +28,11 @@ Here is an example of how to rent a buffer and retrieve a `Memory` instance: // Be sure to include this using at the top of the file: using Microsoft.Toolkit.HighPerformance.Buffers; -using (MemoryOwner buffer = MemoryOwner.Allocate(42)) +using (MemoryOwner buffer = MemoryOwner.Allocate(42)) { // Buffer has exactly 42 items - Memory memory = buffer.Memory; - Span span = buffer.Span; + Memory memory = buffer.Memory; + Span span = buffer.Span; } ``` diff --git a/docs/high-performance/SpanOwner.md b/docs/high-performance/SpanOwner.md index 01d6bce22..7c407e1c9 100644 --- a/docs/high-performance/SpanOwner.md +++ b/docs/high-performance/SpanOwner.md @@ -19,10 +19,10 @@ The same core features of `MemoryOwner` apply to this type as well, with the // Be sure to include this using at the top of the file: using Microsoft.Toolkit.HighPerformance.Buffers; -using (SpanOwner buffer = SpanOwner.Allocate(42)) +using (SpanOwner buffer = SpanOwner.Allocate(42)) { // Buffer has exactly 42 items - Span span = buffer.Span; + Span span = buffer.Span; } ``` From 4662ad10bd1489d203dad19604f506aba141b84b Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 18 Mar 2020 15:22:51 +0100 Subject: [PATCH 16/32] Added equivalent C# sample for SpanOwner --- docs/high-performance/SpanOwner.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/docs/high-performance/SpanOwner.md b/docs/high-performance/SpanOwner.md index 7c407e1c9..68c64322c 100644 --- a/docs/high-performance/SpanOwner.md +++ b/docs/high-performance/SpanOwner.md @@ -28,6 +28,27 @@ using (SpanOwner buffer = SpanOwner.Allocate(42)) **NOTE:** as this is a stack-only type, it relies on the duck-typed `IDisposable` pattern introduced with C# 8. That is shown in the sample above: the `SpanOwner` type is being used within a `using` block despite the fact that the type doesn't implement the `IDisposable` interface at all, and it's also never boxed. The functionality is just the same: as soon as the buffer goes out of scope, it is automatically disposed. The APIs in `SpanOwner{T}` rely on this pattern for extra performance: they assume that the underlying buffer will never be disposed as long as the `SpanOwner` type is in scope, and they don't perform the additional checks that are done in `MemoryOwner` to ensure that the buffer is in fact still available before returning a `Memory` or `Span` instance from it. As such, this type should always be used with a `using` block or expression. Not doing so will cause the underlying buffer not to be returned to the shared pool. Technically the same can also be achieved by manually calling `Dispose` on the `SpanOwner` type (which doesn't require C# 8), but that is error prone and hence not recommended. +The `SpanOwner` type can also be seen as a lightweight wrapper around the `ArrayPool` APIs, which makes them both more compact and easier to use, reducing the amount of code that needs to be written to properly rent and dispose short lived buffers. For reference, the previous code sample is conceptually equivalent to this snippet: + +```csharp +// Using directive to access the ArrayPool type +using System.Buffers; + +int[] buffer = ArrayPool.Shared.Rent(42); + +try +{ + // The span needs to manually be sliced to have the right length + Span span = buffer.AsSpan(0, 42); +} +finally +{ + ArrayPool.Shared.Return(buffer); +} +``` + +You can see how using `SpanOwner` makes the code much shorter and more straightforward. + ## Properties | Property | Return Type | Description | From 989a52b37d81714dd052efe846ddc9bcb76e4ef6 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 5 May 2020 22:33:30 +0200 Subject: [PATCH 17/32] Fixed typo in ParallelHelper.md Co-authored-by: Michael Hawker MSFT (XAML Llama) --- docs/high-performance/ParallelHelper.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/high-performance/ParallelHelper.md b/docs/high-performance/ParallelHelper.md index bac01ffa6..e9dea3ec8 100644 --- a/docs/high-performance/ParallelHelper.md +++ b/docs/high-performance/ParallelHelper.md @@ -34,7 +34,7 @@ public readonly struct ByTwoMultiplier : IRefAction } // Create an array and run the callback -float[] array = new int[10000]; +float[] array = new float[10000]; ParallelHelper.ForEach(array); ``` From f335da938903a229f18f5798b7cf3379b11bc12d Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 5 May 2020 22:42:16 +0200 Subject: [PATCH 18/32] Renamed ByReference APIs to Ref --- docs/high-performance/ByReference.md | 106 --------------------------- docs/high-performance/Ref.md | 106 +++++++++++++++++++++++++++ docs/toc.md | 2 +- 3 files changed, 107 insertions(+), 107 deletions(-) delete mode 100644 docs/high-performance/ByReference.md create mode 100644 docs/high-performance/Ref.md diff --git a/docs/high-performance/ByReference.md b/docs/high-performance/ByReference.md deleted file mode 100644 index 7412a6de7..000000000 --- a/docs/high-performance/ByReference.md +++ /dev/null @@ -1,106 +0,0 @@ ---- -title: ByReference<T> and ReadOnlyByReference<T> -author: Sergio0694 -description: Two stack-only types that can store a reference to a value of a specified type -keywords: windows 10, uwp, windows community toolkit, uwp community toolkit, uwp toolkit, parallel, high performance, net core, net standard -dev_langs: - - csharp ---- - -# ByReference<T> - -The [ByReference<T>](https://docs.microsoft.com/dotnet/api/microsoft.toolkit.highperformance.byreference-1) is a stack-only type that can store a reference to a value of a specified type. It is semantically equivalent to a `ref T` value, with the difference that it can also be used as a type of field in another stack-only `struct` type. It can be used in place of proper `ref T` fields, which are currently not supported in C#. - -## How it works - -Due to how it's implemented on different .NET Standard contracts, the `ByReference` has some minor differences on .NET Standard 2.0 and .NET Standard 2.1. In particular, on .NET Standard 2.0 it can only be used to reference fields _within an object_, instead of arbitrary values pointed by a managed reference. This is because the underlying APIs being used on .NET Standard 2.0 don't have built-in support for the `Span` type. - -### `ByReference` on .NET Standard 2.0 - -As mentioned before, on .NET Standard 2.0, `ByReference` can only point to locations within a given `object`. Consider the following code: - -```csharp -// Be sure to include this using at the top of the file: -using Microsoft.Toolkit.HighPerformance; - -// Define a sample model -class MyModel -{ - public int Number = 42; -} - -var model = new MyModel(); - -// Create a ByReference pointing to the MyModel.Number field -ByReference byRef = new ByReference(model, ref model.Number); - -// Modify the field indirectly -byRef.Value++; - -Console.WriteLine(model.Number); // Prints 43! -``` - -**NOTE:** the `ByReference` constructor doesn't validate the input arguments, meaning that it is your responsability to pass a valid `object` and a reference to a field within that object. - -### `ByReference` on .NET Standard 2.1 - -On .NET Standard 2.1, `ByReference` can point to any `ref T` value: - -```csharp -int number = 42; - -// Create a ByReference pointing to 'number' -ByReference byRef1 = new ByReference(ref number); - -// Modify 'number' -byRef1.Value++; - -Console.WriteLine(number); // Prints 43! -``` - -**NOTE:** this type comes with a few caveats and should be used carefully, as it can lead to runtime crashes if a `ByReference` instance is created with an invalid reference. In particular, you should only create a `ByReference` instance pointing to values that have a lifetime that is greater than that of the `ByReference` in use. Consider the following snippet: - -```csharp -public static ref int GetDummyReference() -{ - int number = 42; - - ByReference byRef = new ByReference(ref number); - - return ref byRef.Value; -} -``` - -This will compile and run fine, but the returned `ref int` will be invalid (as in, it will not point to a valid location) and could cause crashes if it's dereferenced. It is your responsability to track the lifetime of values being referenced by new `ByReference` values. - -## Properties - -| Property | Return Type | Description | -| -- | -- | -- | -| Value | ref T | Gets the `T` reference represented by the current `ByReference{T}` instance | - -# ReadOnlyByReference<T> - -The [ReadOnlyByReference<T>](https://docs.microsoft.com/dotnet/api/microsoft.toolkit.highperformance.readonlybyreference-1) is a stack-only type that mirrors `ByReference`, with the exception that its constructor takes an `in T` parameter (a readonly reference), instead of a `ref T` one. Similarly, its `Value` property has a `ref readonly T` return type instead of `ref T`. - -## Properties - -| Property | Return Type | Description | -| -- | -- | -- | -| Value | ref readonly T | Gets the `T` reference represented by the current `ReadOnlyByReference{T}` instance | - -## Sample Code - -You can find more examples in our [unit tests](https://github.com/Microsoft/WindowsCommunityToolkit//blob/master/UnitTests/UnitTests.HighPerformance.Shared) - -## Requirements - -| Device family | Universal, 10.0.16299.0 or higher | -| --- | --- | -| Namespace | Microsoft.Toolkit.HighPerformance | -| NuGet package | [Microsoft.Toolkit.HighPerformance](https://www.nuget.org/packages/Microsoft.Toolkit.HighPerformance/) | - -## API - -* [ByReference<T> source code](https://github.com/Microsoft/WindowsCommunityToolkit//blob/master/Microsoft.Toolkit.HighPerformance) -* [ReadOnlyByReference<T> source code](https://github.com/Microsoft/WindowsCommunityToolkit//blob/master/Microsoft.Toolkit.HighPerformance) diff --git a/docs/high-performance/Ref.md b/docs/high-performance/Ref.md new file mode 100644 index 000000000..e487fe29c --- /dev/null +++ b/docs/high-performance/Ref.md @@ -0,0 +1,106 @@ +--- +title: Ref<T> and ReadOnlyRef<T> +author: Sergio0694 +description: Two stack-only types that can store a reference to a value of a specified type +keywords: windows 10, uwp, windows community toolkit, uwp community toolkit, uwp toolkit, parallel, high performance, net core, net standard +dev_langs: + - csharp +--- + +# Ref<T> + +The [Ref<T>](https://docs.microsoft.com/dotnet/api/microsoft.toolkit.highperformance.ref-1) is a stack-only type that can store a reference to a value of a specified type. It is semantically equivalent to a `ref T` value, with the difference that it can also be used as a type of field in another stack-only `struct` type. It can be used in place of proper `ref T` fields, which are currently not supported in C#. + +## How it works + +Due to how it's implemented on different .NET Standard contracts, the `Ref` has some minor differences on .NET Standard 2.0 and .NET Standard 2.1. In particular, on .NET Standard 2.0 it can only be used to reference fields _within an object_, instead of arbitrary values pointed by a managed reference. This is because the underlying APIs being used on .NET Standard 2.0 don't have built-in support for the `Span` type. + +### `Ref` on .NET Standard 2.0 + +As mentioned before, on .NET Standard 2.0, `Ref` can only point to locations within a given `object`. Consider the following code: + +```csharp +// Be sure to include this using at the top of the file: +using Microsoft.Toolkit.HighPerformance; + +// Define a sample model +class MyModel +{ + public int Number = 42; +} + +var model = new MyModel(); + +// Create a Ref pointing to the MyModel.Number field +Ref byRef = new Ref(model, ref model.Number); + +// Modify the field indirectly +byRef.Value++; + +Console.WriteLine(model.Number); // Prints 43! +``` + +**NOTE:** the `Ref` constructor doesn't validate the input arguments, meaning that it is your responsability to pass a valid `object` and a reference to a field within that object. + +### `Ref` on .NET Standard 2.1 + +On .NET Standard 2.1, `Ref` can point to any `ref T` value: + +```csharp +int number = 42; + +// Create a Ref pointing to 'number' +Ref byRef1 = new Ref(ref number); + +// Modify 'number' +byRef1.Value++; + +Console.WriteLine(number); // Prints 43! +``` + +**NOTE:** this type comes with a few caveats and should be used carefully, as it can lead to runtime crashes if a `Ref` instance is created with an invalid reference. In particular, you should only create a `Ref` instance pointing to values that have a lifetime that is greater than that of the `Ref` in use. Consider the following snippet: + +```csharp +public static ref int GetDummyReference() +{ + int number = 42; + + Ref byRef = new Ref(ref number); + + return ref byRef.Value; +} +``` + +This will compile and run fine, but the returned `ref int` will be invalid (as in, it will not point to a valid location) and could cause crashes if it's dereferenced. It is your responsability to track the lifetime of values being referenced by new `Ref` values. + +## Properties + +| Property | Return Type | Description | +| -- | -- | -- | +| Value | ref T | Gets the `T` reference represented by the current `Ref{T}` instance | + +# ReadOnlyRef<T> + +The [ReadOnlyRef<T>](https://docs.microsoft.com/dotnet/api/microsoft.toolkit.highperformance.readonlyref-1) is a stack-only type that mirrors `Ref`, with the exception that its constructor takes an `in T` parameter (a readonly reference), instead of a `ref T` one. Similarly, its `Value` property has a `ref readonly T` return type instead of `ref T`. + +## Properties + +| Property | Return Type | Description | +| -- | -- | -- | +| Value | ref readonly T | Gets the `T` reference represented by the current `ReadOnlyRef{T}` instance | + +## Sample Code + +You can find more examples in our [unit tests](https://github.com/Microsoft/WindowsCommunityToolkit//blob/master/UnitTests/UnitTests.HighPerformance.Shared) + +## Requirements + +| Device family | Universal, 10.0.16299.0 or higher | +| --- | --- | +| Namespace | Microsoft.Toolkit.HighPerformance | +| NuGet package | [Microsoft.Toolkit.HighPerformance](https://www.nuget.org/packages/Microsoft.Toolkit.HighPerformance/) | + +## API + +* [Ref<T> source code](https://github.com/Microsoft/WindowsCommunityToolkit//blob/master/Microsoft.Toolkit.HighPerformance) +* [ReadOnlyRef<T> source code](https://github.com/Microsoft/WindowsCommunityToolkit//blob/master/Microsoft.Toolkit.HighPerformance) diff --git a/docs/toc.md b/docs/toc.md index 1112bec19..923736475 100644 --- a/docs/toc.md +++ b/docs/toc.md @@ -170,7 +170,7 @@ ### [MemoryOwner<T>](high-performance/MemoryOwner.md) ### [SpanOwner<T>](high-performance/SpanOwner.md) ## [ParallelHelper](high-performance/ParallelHelper.md) -## [ByReference<T> and ReadOnlyByReference<T>](high-performance/ByReference.md) +## [Ref<T> and ReadOnlyRef<T>](high-performance/Ref.md) # Developer tools ## [AlignmentGrid](developer-tools/AlignmentGrid.md) From 4d7114f6bbd514b8051f9ca4130945d2611022dc Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 5 May 2020 23:12:35 +0200 Subject: [PATCH 19/32] Added !NOTE and !WARNING blocks --- docs/high-performance/Ref.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/docs/high-performance/Ref.md b/docs/high-performance/Ref.md index e487fe29c..a52eac2e5 100644 --- a/docs/high-performance/Ref.md +++ b/docs/high-performance/Ref.md @@ -13,7 +13,8 @@ The [Ref<T>](https://docs.microsoft.com/dotnet/api/microsoft.toolkit.highperf ## How it works -Due to how it's implemented on different .NET Standard contracts, the `Ref` has some minor differences on .NET Standard 2.0 and .NET Standard 2.1. In particular, on .NET Standard 2.0 it can only be used to reference fields _within an object_, instead of arbitrary values pointed by a managed reference. This is because the underlying APIs being used on .NET Standard 2.0 don't have built-in support for the `Span` type. +> [!NOTE] +> Due to how it's implemented on different .NET Standard contracts, the `Ref` has some minor differences on .NET Standard 2.0 and .NET Standard 2.1. In particular, on .NET Standard 2.0 it can only be used to reference fields _within an object_, instead of arbitrary values pointed by a managed reference. This is because the underlying APIs being used on .NET Standard 2.0 don't have built-in support for the `Span` type. ### `Ref` on .NET Standard 2.0 @@ -40,7 +41,8 @@ byRef.Value++; Console.WriteLine(model.Number); // Prints 43! ``` -**NOTE:** the `Ref` constructor doesn't validate the input arguments, meaning that it is your responsability to pass a valid `object` and a reference to a field within that object. +> [!WARNING] +> The `Ref` constructor doesn't validate the input arguments, meaning that it is your responsability to pass a valid `object` and a reference to a field within that object. ### `Ref` on .NET Standard 2.1 @@ -58,7 +60,8 @@ byRef1.Value++; Console.WriteLine(number); // Prints 43! ``` -**NOTE:** this type comes with a few caveats and should be used carefully, as it can lead to runtime crashes if a `Ref` instance is created with an invalid reference. In particular, you should only create a `Ref` instance pointing to values that have a lifetime that is greater than that of the `Ref` in use. Consider the following snippet: +> [!WARNING] +> This type comes with a few caveats and should be used carefully, as it can lead to runtime crashes if a `Ref` instance is created with an invalid reference. In particular, you should only create a `Ref` instance pointing to values that have a lifetime that is greater than that of the `Ref` in use. Consider the following snippet: ```csharp public static ref int GetDummyReference() From ea6727babcd679bb9c24ca2007b4f99af566976e Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 5 May 2020 23:12:59 +0200 Subject: [PATCH 20/32] Fixed a typo --- docs/high-performance/Introduction.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/high-performance/Introduction.md b/docs/high-performance/Introduction.md index 6d41dffa1..7e3971703 100644 --- a/docs/high-performance/Introduction.md +++ b/docs/high-performance/Introduction.md @@ -7,7 +7,7 @@ keywords: windows 10, uwp, windows community toolkit, uwp community toolkit, uwp # Introduction to the High Performance package -This package can be installed through NuGet, and it multitargets .NET Standard 2.0 and .NET Standard 2.1. This means that you can use it from both UWP apps, as well as modern .NET Core 3.0 applications. The API surface is almost identical in both cases, and lots of work has been put into backporting as many features as possible to .NET Standard 2.0 as well. Except for some minor differences, you can expect the same APIs to be available on both target frameworks. +This package can be installed through NuGet, and it multi-targets .NET Standard 2.0 and .NET Standard 2.1. This means that you can use it from both UWP apps, as well as modern .NET Core 3.0 applications. The API surface is almost identical in both cases, and lots of work has been put into backporting as many features as possible to .NET Standard 2.0 as well. Except for some minor differences, you can expect the same APIs to be available on both target frameworks. Follow these steps to install the High Performance package: From 1bb7d26e4545d4d48a75e6768cec124fac14f364 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 5 May 2020 23:15:37 +0200 Subject: [PATCH 21/32] Added bullet list --- docs/high-performance/Introduction.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/high-performance/Introduction.md b/docs/high-performance/Introduction.md index 7e3971703..0e3560410 100644 --- a/docs/high-performance/Introduction.md +++ b/docs/high-performance/Introduction.md @@ -33,7 +33,11 @@ Follow these steps to install the High Performance package: As the name suggests, the High Performance package contains a set of APIs that are heavily focused on optimization. All the new APIs have been carefully crafted to achieve the best possible performance when using them, either through reduced memory allocation, micro-optimizations at the assembly level, or by structuring the APIs in a way that facilitates writing performance oriented code in general. -This package makes heavy use of APIs such as [`System.Buffers.ArrayPool`](https://docs.microsoft.com/en-us/dotnet/api/system.buffers.arraypool-1), helpers from the [`System.Runtime.CompilerServices.Unsafe`](https://docs.microsoft.com/en-us/dotnet/api/system.runtime.compilerservices.unsafe) and [`System.Runtime.InteropServices.MemoryMarshal`](https://docs.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.memorymarshal) classes, APIs from the [`System.Threading.Tasks.Parallel`](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.parallel) class, and more. +This package makes heavy use of APIs such as: +- [`System.Buffers.ArrayPool`](https://docs.microsoft.com/en-us/dotnet/api/system.buffers.arraypool-1) +- [`System.Runtime.CompilerServices.Unsafe`](https://docs.microsoft.com/en-us/dotnet/api/system.runtime.compilerservices.unsafe) +- [`System.Runtime.InteropServices.MemoryMarshal`](https://docs.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.memorymarshal) +- [`System.Threading.Tasks.Parallel`](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.parallel) If you are already familiar with these APIs or even if you're just getting started with writing high performance code in C# and want a set of well tested helpers to use in your own projects, have a look at what's included in this package to see how you can use it in your own projects! From 52a4752e90f8bf65e0da350cd886266780e28006 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 5 May 2020 23:17:42 +0200 Subject: [PATCH 22/32] Added link to ImageSharp --- docs/high-performance/ParallelHelper.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/high-performance/ParallelHelper.md b/docs/high-performance/ParallelHelper.md index e9dea3ec8..08f9ca16e 100644 --- a/docs/high-performance/ParallelHelper.md +++ b/docs/high-performance/ParallelHelper.md @@ -16,7 +16,7 @@ The [ParallelHelper](https://docs.microsoft.com/dotnet/api/microsoft.toolkit.hig `ParallelHelper` is built around three main concepts: - It performs automatic batching over the target iteration range. This means that it automatically schedules the right number of working units based on the number of available CPU cores. This is done to reduce the overhead of invoking the parallel callback once for every single parallel iteration. -- It heavily leverages the way generic types are implemented in C#, and uses `struct` types implementing specific interfaces instead of delegates like `Action`. This is done so that the JIT compiler will be able to "see" each individual callback type being used, which allows it to inline the callback entirely, when possible. This can greatly reduce the overhead of each parallel iteration, especially when using very small callbacks, which would have a trivial cost with respect to the delegate invocation alone. Additionally, using a `struct` type as callback requires developers to manually handle variables that are being captured in the closure, which prevents accidental captures of the `this` pointer from instance methods and other values that could considerably slowdown each callback invocation. This is the same approach that is used in other performance-oriented libraries such as `ImageSharp`. +- It heavily leverages the way generic types are implemented in C#, and uses `struct` types implementing specific interfaces instead of delegates like `Action`. This is done so that the JIT compiler will be able to "see" each individual callback type being used, which allows it to inline the callback entirely, when possible. This can greatly reduce the overhead of each parallel iteration, especially when using very small callbacks, which would have a trivial cost with respect to the delegate invocation alone. Additionally, using a `struct` type as callback requires developers to manually handle variables that are being captured in the closure, which prevents accidental captures of the `this` pointer from instance methods and other values that could considerably slowdown each callback invocation. This is the same approach that is used in other performance-oriented libraries such as [`ImageSharp`](https://github.com/SixLabors/ImageSharp). - It exposes 4 types of APIs that represent 4 different types of iterations: 1D and 2D loops, items iteration with side effect and items iteration without side effect. Each type of action has a corresponding `interface` type that needs to be applied to the `struct` callbacks being passed to the `ParallelHelper` APIs: these are `IAction`, `IAction2D`, `IRefAction` and `IInAction`. This helps developers to write code that is clearer regarding its intent, and allows the APIs to perform further optimizations internally. ## Syntax From df056cd5fae979aeca54b6842a6f857cb901e142 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 13 May 2020 12:19:43 +0200 Subject: [PATCH 23/32] Updated introduction with multi-targeting info --- docs/high-performance/Introduction.md | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/docs/high-performance/Introduction.md b/docs/high-performance/Introduction.md index 0e3560410..70ec322e5 100644 --- a/docs/high-performance/Introduction.md +++ b/docs/high-performance/Introduction.md @@ -7,15 +7,24 @@ keywords: windows 10, uwp, windows community toolkit, uwp community toolkit, uwp # Introduction to the High Performance package -This package can be installed through NuGet, and it multi-targets .NET Standard 2.0 and .NET Standard 2.1. This means that you can use it from both UWP apps, as well as modern .NET Core 3.0 applications. The API surface is almost identical in both cases, and lots of work has been put into backporting as many features as possible to .NET Standard 2.0 as well. Except for some minor differences, you can expect the same APIs to be available on both target frameworks. +This package can be installed through NuGet, and it has the following multi-targets: +- .NET Standard 1.4 +- .NET Standard 2.0 +- .NET Standard 2.1 +- .NET Core 2.1 +- .NET Core 3.1 + +This means that you can use it from anything from UWP or legacy .NET Framework applications, games written in Unity, cross-platform mobile applications using Xamarin, to .NET Standard libraries and modern .NET Core 2.1 or .NET Core 3.1 applications. The API surface is almost identical in all cases, and lots of work has been put into backporting as many features as possible to older targets like .NET Standard 1.4 and .NET Standard 2.0. Except for some minor differences, you can expect the same APIs to be available on all target frameworks. + +The reason why multi-targeting has been used is to allow the package to leverage all the latest APIs on modern runtimes (like .NET Core 3.1) whenever possible, while still offering most of its functionalities to all target platforms. It also makes it possible for .NET Core 2.1 to use all the APIs that it has in common with .NET Standard 2.1, even though the runtime itself doesn't fully implement .NET Standard 2.1. All of this would not have been possible without multi-targeting to both .NET Standard as well as individual runtimes. Follow these steps to install the High Performance package: 1. Open an existing project in Visual studio, targeting any of the following: - - UWP (SDK >= 16299) - - .NET Standard (>= 2.0) + - UWP (>= 10.0) + - .NET Standard (>= 1.4) - .NET Core (>= 2.1) - - Any other framework supporting .NET Standard 2.0 and up + - Any other framework supporting .NET Standard 1.4 and up 2. In Solution Explorer panel, right click on your project name and select **Manage NuGet Packages**. Search for **Microsoft.Toolkit.HighPerformance** and install it. From eb8e2e1a7281f374db86191591870488741be1a7 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 13 May 2020 12:22:30 +0200 Subject: [PATCH 24/32] Minor tweaks --- docs/high-performance/Introduction.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/high-performance/Introduction.md b/docs/high-performance/Introduction.md index 70ec322e5..85befe184 100644 --- a/docs/high-performance/Introduction.md +++ b/docs/high-performance/Introduction.md @@ -23,7 +23,7 @@ Follow these steps to install the High Performance package: 1. Open an existing project in Visual studio, targeting any of the following: - UWP (>= 10.0) - .NET Standard (>= 1.4) - - .NET Core (>= 2.1) + - .NET Core (>= 1.0) - Any other framework supporting .NET Standard 1.4 and up 2. In Solution Explorer panel, right click on your project name and select **Manage NuGet Packages**. Search for **Microsoft.Toolkit.HighPerformance** and install it. @@ -36,7 +36,7 @@ Follow these steps to install the High Performance package: using Microsoft.Toolkit.HighPerformance; ``` -4. If you want so see some code samples, you can either read through the other docs pages for the High Performance package, or have a look at the various [unit tests](https://github.com/Microsoft/WindowsCommunityToolkit//blob/master/UnitTests/UnitTests.HighPerformance.Shared/Helpers) for the project. +4. If you want so see some code samples, you can either read through the other docs pages for the High Performance package, or have a look at the various [unit tests](https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/UnitTests/UnitTests.HighPerformance.Shared) for the project. ## When should I use this package? From 2b2e8d1109cac519999f4d4d565e8caa259f6e7e Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 13 May 2020 13:39:53 +0200 Subject: [PATCH 25/32] Improved Ref docs --- docs/high-performance/Ref.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/docs/high-performance/Ref.md b/docs/high-performance/Ref.md index a52eac2e5..21299cd35 100644 --- a/docs/high-performance/Ref.md +++ b/docs/high-performance/Ref.md @@ -14,11 +14,11 @@ The [Ref<T>](https://docs.microsoft.com/dotnet/api/microsoft.toolkit.highperf ## How it works > [!NOTE] -> Due to how it's implemented on different .NET Standard contracts, the `Ref` has some minor differences on .NET Standard 2.0 and .NET Standard 2.1. In particular, on .NET Standard 2.0 it can only be used to reference fields _within an object_, instead of arbitrary values pointed by a managed reference. This is because the underlying APIs being used on .NET Standard 2.0 don't have built-in support for the `Span` type. +> Due to how it's implemented on different .NET Standard contracts, the `Ref` has some minor differences on .NET Standard 1.4 and .NET Standard 2.1 (and equivalent .NET Core runtimes). In particular, on .NET Standard 1.4 it can only be used to reference fields _within an object_, instead of arbitrary values pointed by a managed reference. This is because the underlying APIs being used on .NET Standard 1.4 don't have built-in support for the `Span` type. -### `Ref` on .NET Standard 2.0 +### `Ref` on .NET Standard 1.4 -As mentioned before, on .NET Standard 2.0, `Ref` can only point to locations within a given `object`. Consider the following code: +As mentioned before, on .NET Standard 1.4, `Ref` can only point to locations within a given `object`. Consider the following code: ```csharp // Be sure to include this using at the top of the file: @@ -42,7 +42,7 @@ Console.WriteLine(model.Number); // Prints 43! ``` > [!WARNING] -> The `Ref` constructor doesn't validate the input arguments, meaning that it is your responsability to pass a valid `object` and a reference to a field within that object. +> The `Ref` constructor doesn't validate the input arguments, meaning that it is your responsability to pass a valid `object` and a reference to a field within that object. Failing to do so might cause the `Ref.Value` property to return an invalid reference. ### `Ref` on .NET Standard 2.1 @@ -76,6 +76,9 @@ public static ref int GetDummyReference() This will compile and run fine, but the returned `ref int` will be invalid (as in, it will not point to a valid location) and could cause crashes if it's dereferenced. It is your responsability to track the lifetime of values being referenced by new `Ref` values. +> [!NOTE] +> Although it is possible to create a `Ref` value wrapping a `null` reference, by using the `default(Ref)` expression, the `Ref` type is not designed to be used with nullable references and does not include proper features to validate the internal reference. If you need to return a reference that can be set to `null`, use the `NullableRef` and `NullableReadOnlyRef` types. + ## Properties | Property | Return Type | Description | @@ -98,7 +101,7 @@ You can find more examples in our [unit tests](https://github.com/Microsoft/Wind ## Requirements -| Device family | Universal, 10.0.16299.0 or higher | +| Device family | Universal, 10.0 or higher | | --- | --- | | Namespace | Microsoft.Toolkit.HighPerformance | | NuGet package | [Microsoft.Toolkit.HighPerformance](https://www.nuget.org/packages/Microsoft.Toolkit.HighPerformance/) | From 1a57734bd1d853d4b44364a467af8f4d2cc0ba44 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 13 May 2020 14:47:09 +0200 Subject: [PATCH 26/32] Added MemoryOwner sample and more info --- docs/high-performance/MemoryOwner.md | 47 ++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 3 deletions(-) diff --git a/docs/high-performance/MemoryOwner.md b/docs/high-performance/MemoryOwner.md index 6a98a17de..e6d9fe21a 100644 --- a/docs/high-performance/MemoryOwner.md +++ b/docs/high-performance/MemoryOwner.md @@ -9,13 +9,13 @@ dev_langs: # MemoryOwner<T> -The [MemoryOwner<T>](https://docs.microsoft.com/dotnet/api/microsoft.toolkit.highperformance.buffers.memoryowner-1) is a buffer type implementing `IMemoryOwner`, an embedded length property and a series of performance oriented APIs. It is essentially a lightweight wrapper around the `ArrayPool` type, with some additional helper utilities. +The [MemoryOwner<T>](https://docs.microsoft.com/dotnet/api/microsoft.toolkit.highperformance.buffers.memoryowner-1) is a buffer type implementing [`IMemoryOwner`](https://docs.microsoft.com/en-us/dotnet/api/system.buffers.imemoryowner-1), an embedded length property and a series of performance oriented APIs. It is essentially a lightweight wrapper around the [`ArrayPool`](https://docs.microsoft.com/en-us/dotnet/api/system.buffers.arraypool-1) type, with some additional helper utilities. ## How it works `MemoryOwner` has the following main features: -- One of the main issues of arrays returned by the `ArrayPool` APIs and of the `IMemoryOwner` instances returned by the `MemoryPool` APIs is that the size specified by the user is only being used as a _minum_ size: the actual size of the returned buffers might actually be greater. `MemoryOwner` solves this by also storing the original requested size, so that `Memory` and `Span` instances retrieved from it will never need to be manually sliced. +- One of the main issues of arrays returned by the `ArrayPool` APIs and of the `IMemoryOwner` instances returned by the `MemoryPool` APIs is that the size specified by the user is only being used as a _minum_ size: the actual size of the returned buffers might actually be greater. `MemoryOwner` solves this by also storing the original requested size, so that [`Memory`](https://docs.microsoft.com/en-us/dotnet/api/system.memory-1) and [`Span`](https://docs.microsoft.com/en-us/dotnet/api/system.span-1) instances retrieved from it will never need to be manually sliced. - When using `IMemoryOwner`, getting a `Span` for the underlying buffer requires first to get a `Memory` instance, and then a `Span`. This is fairly expensive, and often unnecessary, as the intermediate `Memory` might actually not be needed at all. `MemoryOwner` instead has an additional `Span` property which is extremely lightweight, as it directly wraps the internal `T[]` array being rented from the pool. - Buffers rented from the pool are not cleared by default, which means that if they were not cleared when being previous returned to the pool, they might contain garbage data. Normally, users are required to clear these rented buffers manually, which can be verbose especially when done frequently. `MemoryOwner` has a more flexible approach to this, through the `Allocate(int, AllocationMode)` API. This method not only allocates a new instance of exactly the requested size, but can also be used to specify which allocation mode to use: either the same one as `ArrayPool`, or one that automatically clears the rented buffer. - There are cases where a buffer might be rented with a greater size than what is actually needed, and then resized afterwards. This would normally require users to rent a new buffer and copy the region of interest from the old buffer. Instead, `MemoryOwner` exposes a `Slice(int, int)` API that simply return a new instance wrapping the specified area of interest. This allows to skip renting a new buffer and copying the items entirely. @@ -30,14 +30,55 @@ using Microsoft.Toolkit.HighPerformance.Buffers; using (MemoryOwner buffer = MemoryOwner.Allocate(42)) { - // Buffer has exactly 42 items + // Both memory and span have exactly 42 items Memory memory = buffer.Memory; Span span = buffer.Span; + + // Writing to the span modifies the underlying buffer + span[0] = 42; } ``` In this example, we used a `using` block to declare the `MemoryOwner` buffer: this is particularly useful as the underlying array will automatically be returned to the pool at the end of the block. If instead we don't have direct control over the lifetime of a `MemoryOwner` instance, the buffer will simply be returned to the pool when the object is finalized by the garbage collector. In both cases, rented buffers will always be correctly returned to the shared pool. +## When should this be used? + +`MemoryOwner` can be used as a general purpose buffer type, which has the advantage of minimizing the number of allocations done over time, as it internally reuses the same arrays from a shared pool. A common use case is to replace `new T[]` array allocations, especially when doing repeated operations that either require a temporary buffer to work on, or that produce a buffer as a result. + +Suppose we have a dataset consisting of a series of binary files, each of size 1024, and that we need to read all these files and process them in some way. To properly separate our code, we might end up writing a method that simply reads one binary file, which might look like this: + +```csharp +public static byte[] GetBytesFromFile(string path) +{ + byte[] buffer = new byte[1024]; + + using Stream stream = File.OpenRead(path); + + stream.Read(buffer, 0, buffer.Length); + + return buffer; +} +``` + +Note that `new byte[1024]` expression. If we read a large number of files, we'll end up allocating a lot of new arrays, which will put a lot of pressure over the garbage collector. If we refactor this code to use `MemoryOwner` instead, we end up with the following: + +```csharp +public static IMemoryOwner GetBytesFromFile(string path) +{ + MemoryOwner buffer = MemoryOwner.Allocate(1024); + + using Stream stream = File.OpenRead(path); + + stream.Read(buffer.Span); + + return buffer; +} +``` + +Using this approach, buffers are now rented from a pool, which means that in most cases we're able to skip an allocation. Additionally, since rented buffers are not cleared by default, we can also save the time needed to fill them with zeros, which gives us another small performance improvement. In the example above, loading 1000 files would bring the total allocation size from around 1MB down to just 1024 bytes - just a single buffer would effectively be allocated, and then reused automatically. + +The returned `IMemoryOwner` instance will take care of disposing the underlying buffer and returning it to the pool when its [`IDisposable.Dispose`](https://docs.microsoft.com/en-us/dotnet/api/system.idisposable.dispose) method is invoked. We can use it to get a `Memory` or `Span` instance to interact with the loaded data, and then dispose the instance when we no longer need it. + ## Properties | Property | Return Type | Description | From e0b7c0679bafcbbc2335ec6361d6d1670ed79249 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 13 May 2020 15:37:00 +0200 Subject: [PATCH 27/32] SpanOwner docs improved --- docs/high-performance/SpanOwner.md | 43 +++++++++++++++++++----------- 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/docs/high-performance/SpanOwner.md b/docs/high-performance/SpanOwner.md index 68c64322c..8f7625a56 100644 --- a/docs/high-performance/SpanOwner.md +++ b/docs/high-performance/SpanOwner.md @@ -13,33 +13,30 @@ The [SpanOwner<T>](https://docs.microsoft.com/dotnet/api/microsoft.toolkit.hi ## Syntax -The same core features of `MemoryOwner` apply to this type as well, with the exception of it being a stack-only `struct`, and the fact that it lacks the `IMemoryOwner` `interface` implementation, as well as the `Memory` property. The syntax is virtually identical to that used with `MemoryOwner` as well: +The same core features of `MemoryOwner` apply to this type as well, with the exception of it being a stack-only `struct`, and the fact that it lacks the `IMemoryOwner` `interface` implementation, as well as the `Memory` property. The syntax is virtually identical to that used with `MemoryOwner` as well, except for the differences mentioned above. + +As an example, suppose we have a method where we need to allocate a temporary buffer of a specified size (let's call this value `length`), and then use it to perform some work. A first, inefficient version might look like this: ```csharp -// Be sure to include this using at the top of the file: -using Microsoft.Toolkit.HighPerformance.Buffers; +byte[] buffer = new byte[length]; -using (SpanOwner buffer = SpanOwner.Allocate(42)) -{ - // Buffer has exactly 42 items - Span span = buffer.Span; -} +// Use buffer here ``` -**NOTE:** as this is a stack-only type, it relies on the duck-typed `IDisposable` pattern introduced with C# 8. That is shown in the sample above: the `SpanOwner` type is being used within a `using` block despite the fact that the type doesn't implement the `IDisposable` interface at all, and it's also never boxed. The functionality is just the same: as soon as the buffer goes out of scope, it is automatically disposed. The APIs in `SpanOwner{T}` rely on this pattern for extra performance: they assume that the underlying buffer will never be disposed as long as the `SpanOwner` type is in scope, and they don't perform the additional checks that are done in `MemoryOwner` to ensure that the buffer is in fact still available before returning a `Memory` or `Span` instance from it. As such, this type should always be used with a `using` block or expression. Not doing so will cause the underlying buffer not to be returned to the shared pool. Technically the same can also be achieved by manually calling `Dispose` on the `SpanOwner` type (which doesn't require C# 8), but that is error prone and hence not recommended. - -The `SpanOwner` type can also be seen as a lightweight wrapper around the `ArrayPool` APIs, which makes them both more compact and easier to use, reducing the amount of code that needs to be written to properly rent and dispose short lived buffers. For reference, the previous code sample is conceptually equivalent to this snippet: +This is not ideal, as we're allocating a new buffer every time this code is used, and then throwing it away immediately (as mentioned in the `MemoryOwner` docs as well), which puts more pressure on the garbage collector. We can optimize the code above by using [`ArrayPool`](https://docs.microsoft.com/en-us/dotnet/api/system.buffers.arraypool-1): ```csharp // Using directive to access the ArrayPool type using System.Buffers; -int[] buffer = ArrayPool.Shared.Rent(42); +int[] buffer = ArrayPool.Shared.Rent(length); try { - // The span needs to manually be sliced to have the right length - Span span = buffer.AsSpan(0, 42); + // Slice the span, as it might be larger than the requested size + Span span = buffer.AsSpan(0, length); + + // Use the span here } finally { @@ -47,7 +44,23 @@ finally } ``` -You can see how using `SpanOwner` makes the code much shorter and more straightforward. +The code above rents a buffer from an array pool, but it's more verbose and error-prone: we need to be careful with the `try/finally` block to make sure to always return the rented buffer to the pool. We can rewrite it by using the `SpanOwner` type, like so: + +```csharp +// Be sure to include this using at the top of the file: +using Microsoft.Toolkit.HighPerformance.Buffers; + +using SpanOwner buffer = SpanOwner.Allocate(length); + +Span span = buffer.Span; + +// Use the span here, no slicing necessary +``` + +The `SpanOwner` instance will internally rent an array, and will take care of returning it to the pool when it goes out of scope. We no longer need to use a `try/finally` block either, as the C# compiler will add that automatically when expanding that `using` statement. As such, the `SpanOwner` type can be seen as a lightweight wrapper around the `ArrayPool` APIs, which makes them both more compact and easier to use, reducing the amount of code that needs to be written to properly rent and dispose short lived buffers. You can see how using `SpanOwner` makes the code much shorter and more straightforward. + +> [!NOTE] +> As this is a stack-only type, it relies on the duck-typed `IDisposable` pattern introduced with C# 8. That is shown in the sample above: the `SpanOwner` type is being used within a `using` block despite the fact that the type doesn't implement the `IDisposable` interface at all, and it's also never boxed. The functionality is just the same: as soon as the buffer goes out of scope, it is automatically disposed. The APIs in `SpanOwner{T}` rely on this pattern for extra performance: they assume that the underlying buffer will never be disposed as long as the `SpanOwner` type is in scope, and they don't perform the additional checks that are done in `MemoryOwner` to ensure that the buffer is in fact still available before returning a `Memory` or `Span` instance from it. As such, this type should always be used with a `using` block or expression. Not doing so will cause the underlying buffer not to be returned to the shared pool. Technically the same can also be achieved by manually calling `Dispose` on the `SpanOwner` type (which doesn't require C# 8), but that is error prone and hence not recommended. ## Properties From 8a0d6b8f2147cdb778f08e1a79916dc0023c3be6 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 14 May 2020 15:16:06 +0200 Subject: [PATCH 28/32] Improved code sample for MemoryOwner --- docs/high-performance/MemoryOwner.md | 37 +++++++++++++++++++++------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/docs/high-performance/MemoryOwner.md b/docs/high-performance/MemoryOwner.md index e6d9fe21a..5b1b5960f 100644 --- a/docs/high-performance/MemoryOwner.md +++ b/docs/high-performance/MemoryOwner.md @@ -45,39 +45,58 @@ In this example, we used a `using` block to declare the `MemoryOwner` buffer: `MemoryOwner` can be used as a general purpose buffer type, which has the advantage of minimizing the number of allocations done over time, as it internally reuses the same arrays from a shared pool. A common use case is to replace `new T[]` array allocations, especially when doing repeated operations that either require a temporary buffer to work on, or that produce a buffer as a result. -Suppose we have a dataset consisting of a series of binary files, each of size 1024, and that we need to read all these files and process them in some way. To properly separate our code, we might end up writing a method that simply reads one binary file, which might look like this: +Suppose we have a dataset consisting of a series of binary files, and that we need to read all these files and process them in some way. To properly separate our code, we might end up writing a method that simply reads one binary file, which might look like this: ```csharp public static byte[] GetBytesFromFile(string path) { - byte[] buffer = new byte[1024]; - using Stream stream = File.OpenRead(path); + byte[] buffer = new byte[(int)stream.Length]; + stream.Read(buffer, 0, buffer.Length); return buffer; } ``` -Note that `new byte[1024]` expression. If we read a large number of files, we'll end up allocating a lot of new arrays, which will put a lot of pressure over the garbage collector. If we refactor this code to use `MemoryOwner` instead, we end up with the following: +Note that `new byte[]` expression. If we read a large number of files, we'll end up allocating a lot of new arrays, which will put a lot of pressure over the garbage collector. We might want to refactor this code using buffers rented from a pool, like so: ```csharp -public static IMemoryOwner GetBytesFromFile(string path) +public static (byte[] Buffer, int Length) GetBytesFromFile(string path) { - MemoryOwner buffer = MemoryOwner.Allocate(1024); + using Stream stream = File.OpenRead(path); + + byte[] buffer = ArrayPool.Shared.Rent((int)stream.Length); + + stream.Read(buffer, 0, (int)stream.Length); + return (buffer, (int)stream.Length); +} +``` + +Using this approach, buffers are now rented from a pool, which means that in most cases we're able to skip an allocation. Additionally, since rented buffers are not cleared by default, we can also save the time needed to fill them with zeros, which gives us another small performance improvement. In the example above, loading 1000 files would bring the total allocation size from around 1MB down to just 1024 bytes - just a single buffer would effectively be allocated, and then reused automatically. + +There are two main issues with the code above: +- `ArrayPool` might return buffers that have a size greater than the requested one. To work around this issue, we need to return a tuple which also indicates the actual used size into our rented buffer. +- By simply returning an array, we need to be extra careful to properly track its lifetime and to return it to the appropriate pool. We might work around this issue by using [`MemoryPool`](https://docs.microsoft.com/en-us/dotnet/api/system.buffers.memorypool-1) instead and by returning an `IMemoryOwner` instance, but we still have the problem of rented buffers having a greater size than what we need. Additionally, `IMemoryOwner` has some overhead when retrieving a `Span` to work on, due to it being an interface, and the fact that we always need to get a `Memory` instance first, and then a `Span`. + +To solve both these issues, we can refactor this code again by using `MemoryOwner`: + +```csharp +public static MemoryOwner GetBytesFromFile(string path) +{ using Stream stream = File.OpenRead(path); + MemoryOwner buffer = MemoryOwner.Allocate((int)stream.Length); + stream.Read(buffer.Span); return buffer; } ``` -Using this approach, buffers are now rented from a pool, which means that in most cases we're able to skip an allocation. Additionally, since rented buffers are not cleared by default, we can also save the time needed to fill them with zeros, which gives us another small performance improvement. In the example above, loading 1000 files would bring the total allocation size from around 1MB down to just 1024 bytes - just a single buffer would effectively be allocated, and then reused automatically. - -The returned `IMemoryOwner` instance will take care of disposing the underlying buffer and returning it to the pool when its [`IDisposable.Dispose`](https://docs.microsoft.com/en-us/dotnet/api/system.idisposable.dispose) method is invoked. We can use it to get a `Memory` or `Span` instance to interact with the loaded data, and then dispose the instance when we no longer need it. +The returned `IMemoryOwner` instance will take care of disposing the underlying buffer and returning it to the pool when its [`IDisposable.Dispose`](https://docs.microsoft.com/en-us/dotnet/api/system.idisposable.dispose) method is invoked. We can use it to get a `Memory` or `Span` instance to interact with the loaded data, and then dispose the instance when we no longer need it. Additionally, all the `MemoryOwner` properties (like `MemoryOwner.Span`) respect the initial requested size we used, so we no longer need to manually keep track of the effective size within the rented buffer. ## Properties From 4d21229463c7fa67675c898e4cac9cf07c9bfa18 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 21 May 2020 12:56:15 +0200 Subject: [PATCH 29/32] Fixed display in toc --- docs/toc.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/toc.md b/docs/toc.md index 923736475..eaea76ca2 100644 --- a/docs/toc.md +++ b/docs/toc.md @@ -167,10 +167,10 @@ # High performance ## [Introduction](high-performance/Introduction.md) ## Buffers -### [MemoryOwner<T>](high-performance/MemoryOwner.md) -### [SpanOwner<T>](high-performance/SpanOwner.md) +### [MemoryOwner](high-performance/MemoryOwner.md) +### [SpanOwner](high-performance/SpanOwner.md) ## [ParallelHelper](high-performance/ParallelHelper.md) -## [Ref<T> and ReadOnlyRef<T>](high-performance/Ref.md) +## [Ref and ReadOnlyRef](high-performance/Ref.md) # Developer tools ## [AlignmentGrid](developer-tools/AlignmentGrid.md) From aafd2edb890e14aa4ae6e80efc117d7ce93723b4 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 21 May 2020 15:41:30 +0200 Subject: [PATCH 30/32] Added some suggested APIs to the introduction --- docs/high-performance/Introduction.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/high-performance/Introduction.md b/docs/high-performance/Introduction.md index 85befe184..6f5fef209 100644 --- a/docs/high-performance/Introduction.md +++ b/docs/high-performance/Introduction.md @@ -50,3 +50,8 @@ This package makes heavy use of APIs such as: If you are already familiar with these APIs or even if you're just getting started with writing high performance code in C# and want a set of well tested helpers to use in your own projects, have a look at what's included in this package to see how you can use it in your own projects! +## Where to start? + +Here are some APIs you could look at first, if you were already using one of those types mentioned above: +- [MemoryOwner](high-performance/MemoryOwner.md) and [SpanOwner](high-performance/SpanOwner.md), if you were using `System.Buffers.ArrayPool`. +- [ParallelHelper](high-performance/ParallelHelper.md), if you were using `System.Threading.Tasks.Parallel`. \ No newline at end of file From 7418fd55372cb84d98b4ff36e3d74226387127c5 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 21 May 2020 15:59:35 +0200 Subject: [PATCH 31/32] Fixed file paths --- docs/high-performance/Introduction.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/high-performance/Introduction.md b/docs/high-performance/Introduction.md index 6f5fef209..b359b6e35 100644 --- a/docs/high-performance/Introduction.md +++ b/docs/high-performance/Introduction.md @@ -53,5 +53,5 @@ If you are already familiar with these APIs or even if you're just getting start ## Where to start? Here are some APIs you could look at first, if you were already using one of those types mentioned above: -- [MemoryOwner](high-performance/MemoryOwner.md) and [SpanOwner](high-performance/SpanOwner.md), if you were using `System.Buffers.ArrayPool`. -- [ParallelHelper](high-performance/ParallelHelper.md), if you were using `System.Threading.Tasks.Parallel`. \ No newline at end of file +- [MemoryOwner](MemoryOwner.md) and [SpanOwner](SpanOwner.md), if you were using `System.Buffers.ArrayPool`. +- [ParallelHelper](ParallelHelper.md), if you were using `System.Threading.Tasks.Parallel`. \ No newline at end of file From 34abbebf2ee0bf94b29c9e11b8012d32055641ae Mon Sep 17 00:00:00 2001 From: "Michael Hawker MSFT (XAML Llama)" <24302614+michael-hawker@users.noreply.github.com> Date: Fri, 5 Jun 2020 14:54:26 -0700 Subject: [PATCH 32/32] Update docs/high-performance/ParallelHelper.md --- docs/high-performance/ParallelHelper.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/high-performance/ParallelHelper.md b/docs/high-performance/ParallelHelper.md index 08f9ca16e..64e89727a 100644 --- a/docs/high-performance/ParallelHelper.md +++ b/docs/high-performance/ParallelHelper.md @@ -88,7 +88,8 @@ public readonly struct ArrayInitializer : IAction ParallelHelper.For(0, array.Length, new ArrayInitializer(array)); ``` -**NOTE:** since the callback types are `struct`-s, they're passed _by copy_ to each thread running parallel, not by reference. This means that value types being stored as fields in a callback types will be copied as well. A good practice to remember that detail and avoid errors is to mark the callback `struct` as `readonly`, so that the C# compiler will not let us modify the values of its fields. This only applies to _instance_ fields of a value type: if a callback `struct` has a `static` field of any type, or a reference field, then that value will correctly be shared between parallel threads. +> [!NOTE] +> Since the callback types are `struct`-s, they're passed _by copy_ to each thread running parallel, not by reference. This means that value types being stored as fields in a callback types will be copied as well. A good practice to remember that detail and avoid errors is to mark the callback `struct` as `readonly`, so that the C# compiler will not let us modify the values of its fields. This only applies to _instance_ fields of a value type: if a callback `struct` has a `static` field of any type, or a reference field, then that value will correctly be shared between parallel threads. ## Methods