Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Microsoft.Toolkit.HighPerformance docs page(s) #301

Merged
Merged
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
e4446f4
Added ParallelHelper docs page
Sergio0694 Mar 9, 2020
5d7538a
Fixed a typo
Sergio0694 Mar 9, 2020
4e759c1
Minor tweaks
Sergio0694 Mar 9, 2020
3e7e5ed
Fixed displaying of '<' character
Sergio0694 Mar 12, 2020
9ec4947
Created high-performance section, moved ParallelHelper docs
Sergio0694 Mar 13, 2020
d7ffa6f
Added introduction for the package
Sergio0694 Mar 13, 2020
74ccfba
Improved ParallelHelper docs
Sergio0694 Mar 13, 2020
b4bae08
Added MemoryOwner<T> docs page
Sergio0694 Mar 13, 2020
ec15797
Removed SpanOwner<T> mention from MemoryOwner<T> page
Sergio0694 Mar 13, 2020
96d2342
Added SpanOwner<T> docs page
Sergio0694 Mar 13, 2020
c1df57c
Adjusted docs structure
Sergio0694 Mar 13, 2020
cbb6954
Added ByReference<T> docs page
Sergio0694 Mar 13, 2020
c8e6e87
Minor tweaks
Sergio0694 Mar 14, 2020
0b822c7
Fixed a typo
Sergio0694 Mar 18, 2020
422311d
Minor fixes
Sergio0694 Mar 18, 2020
4662ad1
Added equivalent C# sample for SpanOwner<T>
Sergio0694 Mar 18, 2020
989a52b
Fixed typo in ParallelHelper.md
Sergio0694 May 5, 2020
f335da9
Renamed ByReference<T> APIs to Ref<T>
Sergio0694 May 5, 2020
4d7114f
Added !NOTE and !WARNING blocks
Sergio0694 May 5, 2020
ea6727b
Fixed a typo
Sergio0694 May 5, 2020
1bb7d26
Added bullet list
Sergio0694 May 5, 2020
52a4752
Added link to ImageSharp
Sergio0694 May 5, 2020
df056cd
Updated introduction with multi-targeting info
Sergio0694 May 13, 2020
eb8e2e1
Minor tweaks
Sergio0694 May 13, 2020
2b2e8d1
Improved Ref<T> docs
Sergio0694 May 13, 2020
1a57734
Added MemoryOwner<T> sample and more info
Sergio0694 May 13, 2020
e0b7c06
SpanOwner<T> docs improved
Sergio0694 May 13, 2020
8a0d6b8
Improved code sample for MemoryOwner<T>
Sergio0694 May 14, 2020
4d21229
Fixed <T> display in toc
Sergio0694 May 21, 2020
aafd2ed
Added some suggested APIs to the introduction
Sergio0694 May 21, 2020
7418fd5
Fixed file paths
Sergio0694 May 21, 2020
34abbeb
Update docs/high-performance/ParallelHelper.md
michael-hawker Jun 5, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions docs/high-performance/Introduction.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
---
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 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 (>= 10.0)
- .NET Standard (>= 1.4)
- .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.

![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/windows-toolkit/WindowsCommunityToolkit/tree/master/UnitTests/UnitTests.HighPerformance.Shared) 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:
michael-hawker marked this conversation as resolved.
Show resolved Hide resolved
- [`System.Buffers.ArrayPool<T>`](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!

132 changes: 132 additions & 0 deletions docs/high-performance/MemoryOwner.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
---
title: MemoryOwner&lt;T>
author: Sergio0694
description: A buffer type implementing `IMemoryOwner<T>` 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
---

# MemoryOwner&lt;T>

The [MemoryOwner&lt;T>](https://docs.microsoft.com/dotnet/api/microsoft.toolkit.highperformance.buffers.memoryowner-1) is a buffer type implementing [`IMemoryOwner<T>`](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<T>`](https://docs.microsoft.com/en-us/dotnet/api/system.buffers.arraypool-1) type, with some additional helper utilities.

Sergio0694 marked this conversation as resolved.
Show resolved Hide resolved
## How it works

`MemoryOwner<T>` has the following main features:

- One of the main issues of arrays returned by the `ArrayPool<T>` APIs and of the `IMemoryOwner<T>` instances returned by the `MemoryPool<T>` 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<T>` solves this by also storing the original requested size, so that [`Memory<T>`](https://docs.microsoft.com/en-us/dotnet/api/system.memory-1) and [`Span<T>`](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<T>`, getting a `Span<T>` for the underlying buffer requires first to get a `Memory<T>` instance, and then a `Span<T>`. This is fairly expensive, and often unnecessary, as the intermediate `Memory<T>` might actually not be needed at all. `MemoryOwner<T>` 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<T>` 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<T>`, 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<T>` 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<T>` instance:

```csharp
// Be sure to include this using at the top of the file:
using Microsoft.Toolkit.HighPerformance.Buffers;

using (MemoryOwner<int> buffer = MemoryOwner<int>.Allocate(42))
{
// Both memory and span have exactly 42 items
Memory<int> memory = buffer.Memory;
Span<int> span = buffer.Span;
Sergio0694 marked this conversation as resolved.
Show resolved Hide resolved

// Writing to the span modifies the underlying buffer
span[0] = 42;
}
```

In this example, we used a `using` block to declare the `MemoryOwner<T>` 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<T>` 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<T>` 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, 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)
{
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[]` 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 (byte[] Buffer, int Length) GetBytesFromFile(string path)
{
using Stream stream = File.OpenRead(path);

byte[] buffer = ArrayPool<T>.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<T>` 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<T>`](https://docs.microsoft.com/en-us/dotnet/api/system.buffers.memorypool-1) instead and by returning an `IMemoryOwner<T>` instance, but we still have the problem of rented buffers having a greater size than what we need. Additionally, `IMemoryOwner<T>` has some overhead when retrieving a `Span<T>` to work on, due to it being an interface, and the fact that we always need to get a `Memory<T>` instance first, and then a `Span<T>`.

To solve both these issues, we can refactor this code again by using `MemoryOwner<T>`:

```csharp
public static MemoryOwner<byte> GetBytesFromFile(string path)
{
using Stream stream = File.OpenRead(path);

MemoryOwner<byte> buffer = MemoryOwner<byte>.Allocate((int)stream.Length);

stream.Read(buffer.Span);

return buffer;
}
```

The returned `IMemoryOwner<byte>` 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<T>` or `Span<T>` instance to interact with the loaded data, and then dispose the instance when we no longer need it. Additionally, all the `MemoryOwner<T>` properties (like `MemoryOwner<T>.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

| Property | Return Type | Description |
| -- | -- | -- |
| Length | int | Gets the number of items in the current instance |
| Memory | System.Memory&lt;T> | Gets the memory belonging to this owner |
| Span | System.Span&lt;T> | Gets a span wrapping the memory belonging to the current instance |
| Empty | MemoryOwner&lt;T> | Gets an empty `MemoryOwner<T>` instance |

## Methods

| Method | Return Type | Description |
| -- | -- | -- |
| Allocate(int) | Memory&lt;T> | Creates a new `MemoryOwner<T>` instance with the specified parameters |
| Allocate(int, AllocationMode) | Memory&lt;T> | Creates a new `MemoryOwner<T>` 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&lt;T> | Slices the buffer currently in use and returns a new `MemoryOwner<T>` 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&lt;T> source code](https://github.com/Microsoft/WindowsCommunityToolkit//blob/master/Microsoft.Toolkit.HighPerformance/Buffers)
Loading