From ced0aa7f28353f13e617873d62877f82e29aceb0 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 13 Jul 2023 13:24:47 +0200 Subject: [PATCH] Remove ObjectPool, port changes from ComputeSharp --- .../Helpers/ImmutableArrayBuilder{T}.cs | 178 +++++++++++------- .../Helpers/ObjectPool{T}.cs | 163 ---------------- 2 files changed, 111 insertions(+), 230 deletions(-) delete mode 100644 components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Helpers/ObjectPool{T}.cs diff --git a/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Helpers/ImmutableArrayBuilder{T}.cs b/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Helpers/ImmutableArrayBuilder{T}.cs index d1cb7161..c8746c1f 100644 --- a/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Helpers/ImmutableArrayBuilder{T}.cs +++ b/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Helpers/ImmutableArrayBuilder{T}.cs @@ -6,6 +6,9 @@ // more info in ThirdPartyNotices.txt in the root of the project. using System; +using System.Buffers; +using System.Collections; +using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; @@ -16,13 +19,8 @@ namespace CommunityToolkit.Extensions.DependencyInjection.SourceGenerators.Helpe /// A helper type to build sequences of values with pooled buffers. /// /// The type of items to create sequences for. -internal struct ImmutableArrayBuilder : IDisposable +internal ref struct ImmutableArrayBuilder { - /// - /// The shared instance to share objects. - /// - private static readonly ObjectPool SharedObjectPool = new(static () => new Writer()); - /// /// The rented instance to use. /// @@ -34,7 +32,7 @@ internal struct ImmutableArrayBuilder : IDisposable /// A instance to write data to. public static ImmutableArrayBuilder Rent() { - return new(SharedObjectPool.Allocate()); + return new(new Writer()); } /// @@ -46,14 +44,21 @@ internal struct ImmutableArrayBuilder : IDisposable this.writer = writer; } - /// - /// Gets the data written to the underlying buffer so far, as a . - /// - [UnscopedRef] - public readonly Span Span + /// + public readonly int Count { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this.writer!.Span; + get => this.writer!.Count; + } + + /// + /// Gets the data written to the underlying buffer so far, as a . + /// + [UnscopedRef] + public readonly ReadOnlySpan WrittenSpan + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => this.writer!.WrittenSpan; } /// @@ -66,7 +71,7 @@ internal struct ImmutableArrayBuilder : IDisposable /// Adds the specified items to the end of the array. /// /// The items to add at the end of the array. - public readonly void AddRange(ReadOnlySpan items) + public readonly void AddRange(scoped ReadOnlySpan items) { this.writer!.AddRange(items); } @@ -74,7 +79,7 @@ internal struct ImmutableArrayBuilder : IDisposable /// public readonly ImmutableArray ToImmutable() { - T[] array = this.writer!.Span.ToArray(); + T[] array = this.writer!.WrittenSpan.ToArray(); return Unsafe.As>(ref array); } @@ -82,39 +87,46 @@ internal struct ImmutableArrayBuilder : IDisposable /// public readonly T[] ToArray() { - return this.writer!.Span.ToArray(); + return this.writer!.WrittenSpan.ToArray(); + } + + /// + /// Gets an instance for the current builder. + /// + /// An instance for the current builder. + /// + /// The builder should not be mutated while an enumerator is in use. + /// + public readonly IEnumerable AsEnumerable() + { + return this.writer!; } /// public override readonly string ToString() { - return this.writer!.Span.ToString(); + return this.writer!.WrittenSpan.ToString(); } - /// + /// public void Dispose() { Writer? writer = this.writer; this.writer = null; - if (writer is not null) - { - writer.Clear(); - - SharedObjectPool.Free(writer); - } + writer?.Dispose(); } /// /// A class handling the actual buffer writing. /// - private sealed class Writer + private sealed class Writer : ICollection, IDisposable { /// /// The underlying array. /// - private T[] array; + private T?[]? array; /// /// The starting offset within . @@ -126,31 +138,33 @@ internal struct ImmutableArrayBuilder : IDisposable /// public Writer() { - if (typeof(T) == typeof(char)) - { - this.array = new T[1024]; - } - else - { - this.array = new T[8]; - } - + this.array = ArrayPool.Shared.Rent(typeof(T) == typeof(char) ? 1024 : 8); this.index = 0; } - /// - public Span Span + /// + public int Count { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => new(this.array, 0, this.index); + get => this.index; } + /// + public ReadOnlySpan WrittenSpan + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => new(this.array!, 0, this.index); + } + + /// + bool ICollection.IsReadOnly => true; + /// public void Add(T value) { EnsureCapacity(1); - this.array[this.index++] = value; + this.array![this.index++] = value; } /// @@ -158,22 +172,64 @@ internal struct ImmutableArrayBuilder : IDisposable { EnsureCapacity(items.Length); - items.CopyTo(this.array.AsSpan(this.index)); + items.CopyTo(this.array.AsSpan(this.index)!); this.index += items.Length; } - /// - /// Clears the items in the current writer. - /// - public void Clear() + /// + public void Dispose() { - if (typeof(T) != typeof(char)) - { - this.array.AsSpan(0, this.index).Clear(); - } + T?[]? array = this.array; - this.index = 0; + this.array = null; + + if (array is not null) + { + ArrayPool.Shared.Return(array, clearArray: typeof(T) != typeof(char)); + } + } + + /// + void ICollection.Clear() + { + throw new NotSupportedException(); + } + + /// + bool ICollection.Contains(T item) + { + throw new NotSupportedException(); + } + + /// + void ICollection.CopyTo(T[] array, int arrayIndex) + { + Array.Copy(this.array!, 0, array, arrayIndex, this.index); + } + + /// + IEnumerator IEnumerable.GetEnumerator() + { + T?[] array = this.array!; + int length = this.index; + + for (int i = 0; i < length; i++) + { + yield return array[i]!; + } + } + + /// + IEnumerator IEnumerable.GetEnumerator() + { + return ((IEnumerable)this).GetEnumerator(); + } + + /// + bool ICollection.Remove(T item) + { + throw new NotSupportedException(); } /// @@ -183,7 +239,7 @@ internal struct ImmutableArrayBuilder : IDisposable [MethodImpl(MethodImplOptions.AggressiveInlining)] private void EnsureCapacity(int requestedSize) { - if (requestedSize > this.array.Length - this.index) + if (requestedSize > this.array!.Length - this.index) { ResizeBuffer(requestedSize); } @@ -197,27 +253,15 @@ internal struct ImmutableArrayBuilder : IDisposable 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.Shared.Rent(minimumSize); - Array.Copy(this.array, newArray, this.index); + Array.Copy(oldArray, newArray, this.index); this.array = newArray; + + ArrayPool.Shared.Return(oldArray, clearArray: typeof(T) != typeof(char)); } } } - -/// -/// Private helpers for the type. -/// -internal static class ImmutableArrayBuilder -{ - /// - /// Throws an for "index". - /// - public static void ThrowArgumentOutOfRangeExceptionForIndex() - { - throw new ArgumentOutOfRangeException("index"); - } -} \ No newline at end of file diff --git a/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Helpers/ObjectPool{T}.cs b/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Helpers/ObjectPool{T}.cs deleted file mode 100644 index eca26ba1..00000000 --- a/components/Extensions.DependencyInjection/CommunityToolkit.Extensions.DependencyInjection.SourceGenerators/Helpers/ObjectPool{T}.cs +++ /dev/null @@ -1,163 +0,0 @@ -// 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.Extensions.DependencyInjection.SourceGenerators.Helpers; - -/// -/// -/// 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. -/// -/// -/// Notes: -/// -/// -/// 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. -/// -/// -/// 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. -/// -/// -/// -/// -/// 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". -/// -/// -/// The type of objects to pool. -internal sealed class ObjectPool - where T : class -{ - /// - /// 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()". - /// - private readonly Func factory; - - /// - /// The array of cached items. - /// - private readonly Element[] items; - - /// - /// 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. - /// - private T? firstItem; - - /// - /// Creates a new instance with the specified parameters. - /// - /// The input factory to produce items. - public ObjectPool(Func factory) - : this(factory, Environment.ProcessorCount * 2) - { - } - - /// - /// Creates a new instance with the specified parameters. - /// - /// The input factory to produce items. - /// The pool size to use. - public ObjectPool(Func factory, int size) - { - this.factory = factory; - this.items = new Element[size - 1]; - } - - /// - /// Produces a instance. - /// - /// The returned item to use. - [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; - } - - /// - /// Returns a given instance to the pool. - /// - /// The instance to return. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Free(T obj) - { - if (this.firstItem is null) - { - this.firstItem = obj; - } - else - { - FreeSlow(obj); - } - } - - /// - /// Allocates a new item. - /// - /// The returned item to use. - [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(); - } - - /// - /// Frees a given item. - /// - /// The item to return to the pool. - [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; - } - } - } - - /// - /// A container for a produced item (using a wrapper to avoid covariance checks). - /// - private struct Element - { - /// - /// The value held at the current element. - /// - internal T? Value; - } -} \ No newline at end of file