未验证 提交 91fdb777 编写于 作者: S Sergio Pedri 提交者: GitHub

Merge pull request #701 from CommunityToolkit/dev/generator-array-pool

Improve ImmutableArrayBuilder<T> type in MVVM Toolkit generators
......@@ -67,7 +67,6 @@
<Compile Include="$(MSBuildThisFileDirectory)Helpers\EquatableArray{T}.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Helpers\HashCode.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Helpers\ImmutableArrayBuilder{T}.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Helpers\ObjectPool{T}.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Input\Models\CanExecuteExpressionType.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Input\Models\CommandInfo.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Input\RelayCommandGenerator.cs" />
......
......@@ -6,7 +6,9 @@
// more info in ThirdPartyNotices.txt in the root of the project.
using System;
using System.Buffers;
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
namespace CommunityToolkit.Mvvm.SourceGenerators.Helpers;
......@@ -15,13 +17,8 @@ namespace CommunityToolkit.Mvvm.SourceGenerators.Helpers;
/// A helper type to build sequences of values with pooled buffers.
/// </summary>
/// <typeparam name="T">The type of items to create sequences for.</typeparam>
internal struct ImmutableArrayBuilder<T> : IDisposable
internal ref struct ImmutableArrayBuilder<T>
{
/// <summary>
/// The shared <see cref="ObjectPool{T}"/> instance to share <see cref="Writer"/> objects.
/// </summary>
private static readonly ObjectPool<Writer> SharedObjectPool = new(static () => new Writer());
/// <summary>
/// The rented <see cref="Writer"/> instance to use.
/// </summary>
......@@ -33,7 +30,7 @@ internal struct ImmutableArrayBuilder<T> : IDisposable
/// <returns>A <see cref="ImmutableArrayBuilder{T}"/> instance to write data to.</returns>
public static ImmutableArrayBuilder<T> Rent()
{
return new(SharedObjectPool.Allocate());
return new(new Writer());
}
/// <summary>
......@@ -46,7 +43,7 @@ private ImmutableArrayBuilder(Writer writer)
}
/// <inheritdoc cref="ImmutableArray{T}.Builder.Count"/>
public int Count
public readonly int Count
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.writer!.Count;
......@@ -55,6 +52,7 @@ public int Count
/// <summary>
/// Gets the data written to the underlying buffer so far, as a <see cref="ReadOnlySpan{T}"/>.
/// </summary>
[UnscopedRef]
public readonly ReadOnlySpan<T> WrittenSpan
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
......@@ -71,7 +69,7 @@ public int Count
/// Adds the specified items to the end of the array.
/// </summary>
/// <param name="items">The items to add at the end of the array.</param>
public readonly void AddRange(ReadOnlySpan<T> items)
public readonly void AddRange(scoped ReadOnlySpan<T> items)
{
this.writer!.AddRange(items);
}
......@@ -96,30 +94,25 @@ public int Count
return this.writer!.WrittenSpan.ToString();
}
/// <inheritdoc/>
/// <inheritdoc cref="IDisposable.Dispose"/>
public void Dispose()
{
Writer? writer = this.writer;
this.writer = null;
if (writer is not null)
{
writer.Clear();
SharedObjectPool.Free(writer);
}
writer?.Dispose();
}
/// <summary>
/// A class handling the actual buffer writing.
/// </summary>
private sealed class Writer
private sealed class Writer : IDisposable
{
/// <summary>
/// The underlying <typeparamref name="T"/> array.
/// </summary>
private T[] array;
private T?[]? array;
/// <summary>
/// The starting offset within <see cref="array"/>.
......@@ -131,15 +124,7 @@ private sealed class Writer
/// </summary>
public Writer()
{
if (typeof(T) == typeof(char))
{
this.array = new T[1024];
}
else
{
this.array = new T[8];
}
this.array = ArrayPool<T?>.Shared.Rent(typeof(T) == typeof(char) ? 1024 : 8);
this.index = 0;
}
......@@ -154,7 +139,7 @@ public int Count
public ReadOnlySpan<T> WrittenSpan
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new(this.array, 0, this.index);
get => new(this.array!, 0, this.index);
}
/// <inheritdoc cref="ImmutableArrayBuilder{T}.Add"/>
......@@ -162,7 +147,7 @@ public void Add(T value)
{
EnsureCapacity(1);
this.array[this.index++] = value;
this.array![this.index++] = value;
}
/// <inheritdoc cref="ImmutableArrayBuilder{T}.AddRange"/>
......@@ -170,22 +155,22 @@ public void AddRange(ReadOnlySpan<T> items)
{
EnsureCapacity(items.Length);
items.CopyTo(this.array.AsSpan(this.index));
items.CopyTo(this.array.AsSpan(this.index)!);
this.index += items.Length;
}
/// <summary>
/// Clears the items in the current writer.
/// </summary>
public void Clear()
/// <inheritdoc/>
public void Dispose()
{
if (typeof(T) != typeof(char))
T?[]? array = this.array;
this.array = null;
if (array is not null)
{
this.array.AsSpan(0, this.index).Clear();
ArrayPool<T?>.Shared.Return(array, clearArray: typeof(T) != typeof(char));
}
this.index = 0;
}
/// <summary>
......@@ -195,7 +180,7 @@ public void Clear()
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void EnsureCapacity(int requestedSize)
{
if (requestedSize > this.array.Length - this.index)
if (requestedSize > this.array!.Length - this.index)
{
ResizeBuffer(requestedSize);
}
......@@ -209,13 +194,15 @@ private void EnsureCapacity(int requestedSize)
private void ResizeBuffer(int sizeHint)
{
int minimumSize = this.index + sizeHint;
int requestedSize = Math.Max(this.array.Length * 2, minimumSize);
T[] newArray = new T[requestedSize];
T?[] oldArray = this.array!;
T?[] newArray = ArrayPool<T?>.Shared.Rent(minimumSize);
Array.Copy(this.array, newArray, this.index);
Array.Copy(oldArray, newArray, this.index);
this.array = newArray;
ArrayPool<T?>.Shared.Return(oldArray, clearArray: typeof(T) != typeof(char));
}
}
}
......
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
// Ported from Roslyn, see: https://github.com/dotnet/roslyn/blob/main/src/Dependencies/PooledObjects/ObjectPool%601.cs.
using System;
using System.Runtime.CompilerServices;
using System.Threading;
namespace CommunityToolkit.Mvvm.SourceGenerators.Helpers;
/// <summary>
/// <para>
/// Generic implementation of object pooling pattern with predefined pool size limit. The main purpose
/// is that limited number of frequently used objects can be kept in the pool for further recycling.
/// </para>
/// <para>
/// Notes:
/// <list type="number">
/// <item>
/// It is not the goal to keep all returned objects. Pool is not meant for storage. If there
/// is no space in the pool, extra returned objects will be dropped.
/// </item>
/// <item>
/// It is implied that if object was obtained from a pool, the caller will return it back in
/// a relatively short time. Keeping checked out objects for long durations is ok, but
/// reduces usefulness of pooling. Just new up your own.
/// </item>
/// </list>
/// </para>
/// <para>
/// Not returning objects to the pool in not detrimental to the pool's work, but is a bad practice.
/// Rationale: if there is no intent for reusing the object, do not use pool - just use "new".
/// </para>
/// </summary>
/// <typeparam name="T">The type of objects to pool.</typeparam>
internal sealed class ObjectPool<T>
where T : class
{
/// <summary>
/// The factory is stored for the lifetime of the pool. We will call this only when pool needs to
/// expand. compared to "new T()", Func gives more flexibility to implementers and faster than "new T()".
/// </summary>
private readonly Func<T> factory;
/// <summary>
/// The array of cached items.
/// </summary>
private readonly Element[] items;
/// <summary>
/// Storage for the pool objects. The first item is stored in a dedicated field
/// because we expect to be able to satisfy most requests from it.
/// </summary>
private T? firstItem;
/// <summary>
/// Creates a new <see cref="ObjectPool{T}"/> instance with the specified parameters.
/// </summary>
/// <param name="factory">The input factory to produce <typeparamref name="T"/> items.</param>
public ObjectPool(Func<T> factory)
: this(factory, Environment.ProcessorCount * 2)
{
}
/// <summary>
/// Creates a new <see cref="ObjectPool{T}"/> instance with the specified parameters.
/// </summary>
/// <param name="factory">The input factory to produce <typeparamref name="T"/> items.</param>
/// <param name="size">The pool size to use.</param>
public ObjectPool(Func<T> factory, int size)
{
this.factory = factory;
this.items = new Element[size - 1];
}
/// <summary>
/// Produces a <typeparamref name="T"/> instance.
/// </summary>
/// <returns>The returned <typeparamref name="T"/> item to use.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public T Allocate()
{
T? item = this.firstItem;
if (item is null || item != Interlocked.CompareExchange(ref this.firstItem, null, item))
{
item = AllocateSlow();
}
return item;
}
/// <summary>
/// Returns a given <typeparamref name="T"/> instance to the pool.
/// </summary>
/// <param name="obj">The <typeparamref name="T"/> instance to return.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Free(T obj)
{
if (this.firstItem is null)
{
this.firstItem = obj;
}
else
{
FreeSlow(obj);
}
}
/// <summary>
/// Allocates a new <typeparamref name="T"/> item.
/// </summary>
/// <returns>The returned <typeparamref name="T"/> item to use.</returns>
[MethodImpl(MethodImplOptions.NoInlining)]
private T AllocateSlow()
{
foreach (ref Element element in this.items.AsSpan())
{
T? instance = element.Value;
if (instance is not null)
{
if (instance == Interlocked.CompareExchange(ref element.Value, null, instance))
{
return instance;
}
}
}
return this.factory();
}
/// <summary>
/// Frees a given <typeparamref name="T"/> item.
/// </summary>
/// <param name="obj">The <typeparamref name="T"/> item to return to the pool.</param>
[MethodImpl(MethodImplOptions.NoInlining)]
private void FreeSlow(T obj)
{
foreach (ref Element element in this.items.AsSpan())
{
if (element.Value is null)
{
element.Value = obj;
break;
}
}
}
/// <summary>
/// A container for a produced item (using a <see langword="struct"/> wrapper to avoid covariance checks).
/// </summary>
private struct Element
{
/// <summary>
/// The value held at the current element.
/// </summary>
internal T? Value;
}
}
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册