зеркало из https://github.com/dotnet/razor.git
Add object pooling infrastructure
This change adds types for pooling objects, which can be used to reduce allocations of temporary, short-lived objects, such as lists and string builders.
This commit is contained in:
Родитель
bf4e7aa069
Коммит
df4c0bc754
|
@ -0,0 +1,53 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Immutable;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.PooledObjects;
|
||||
|
||||
/// <summary>
|
||||
/// A pool of <see cref="ImmutableArray{T}.Builder"/> instances.
|
||||
/// </summary>
|
||||
///
|
||||
/// <remarks>
|
||||
/// Instances originating from this pool are intended to be short-lived and are suitable
|
||||
/// for temporary work. Do not return them as the results of methods or store them in fields.
|
||||
/// </remarks>
|
||||
internal static class ArrayBuilderPool<T>
|
||||
{
|
||||
private const int Threshold = 512;
|
||||
|
||||
private static readonly Func<ObjectPool<ImmutableArray<T>.Builder>, ImmutableArray<T>.Builder> s_allocate = AllocateAndClear;
|
||||
private static readonly Action<ObjectPool<ImmutableArray<T>.Builder>, ImmutableArray<T>.Builder> s_release = ClearAndFree;
|
||||
|
||||
public static ObjectPool<ImmutableArray<T>.Builder> DefaultPool { get; } = ObjectPool.Default(ImmutableArray.CreateBuilder<T>);
|
||||
|
||||
public static PooledObject<ImmutableArray<T>.Builder> GetPooledObject()
|
||||
=> new(DefaultPool, s_allocate, s_release);
|
||||
|
||||
private static ImmutableArray<T>.Builder AllocateAndClear(ObjectPool<ImmutableArray<T>.Builder> pool)
|
||||
{
|
||||
var builder = pool.Allocate();
|
||||
builder.Clear();
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
private static void ClearAndFree(ObjectPool<ImmutableArray<T>.Builder> pool, ImmutableArray<T>.Builder builder)
|
||||
{
|
||||
if (builder is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
builder.Clear();
|
||||
|
||||
if (builder.Capacity > Threshold)
|
||||
{
|
||||
builder.Capacity = Threshold;
|
||||
}
|
||||
|
||||
pool.Free(builder);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.PooledObjects;
|
||||
|
||||
/// <summary>
|
||||
/// A pool of <see cref="HashSet{T}"/> instances that compares items using default equality.
|
||||
/// </summary>
|
||||
///
|
||||
/// <remarks>
|
||||
/// Instances originating from this pool are intended to be short-lived and are suitable
|
||||
/// for temporary work. Do not return them as the results of methods or store them in fields.
|
||||
/// </remarks>
|
||||
internal static class HashSetPool<T>
|
||||
{
|
||||
private const int Threshold = 512;
|
||||
|
||||
private static readonly Func<ObjectPool<HashSet<T>>, HashSet<T>> s_allocate = AllocateAndClear;
|
||||
private static readonly Action<ObjectPool<HashSet<T>>, HashSet<T>> s_release = ClearAndFree;
|
||||
|
||||
public static ObjectPool<HashSet<T>> DefaultPool { get; } = ObjectPool.Default<HashSet<T>>();
|
||||
|
||||
public static PooledObject<HashSet<T>> GetPooledObject()
|
||||
=> new(DefaultPool, s_allocate, s_release);
|
||||
|
||||
private static HashSet<T> AllocateAndClear(ObjectPool<HashSet<T>> pool)
|
||||
{
|
||||
var set = pool.Allocate();
|
||||
set.Clear();
|
||||
|
||||
return set;
|
||||
}
|
||||
|
||||
private static void ClearAndFree(ObjectPool<HashSet<T>> pool, HashSet<T> set)
|
||||
{
|
||||
if (set is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var count = set.Count;
|
||||
set.Clear();
|
||||
|
||||
if (count > Threshold)
|
||||
{
|
||||
set.TrimExcess();
|
||||
}
|
||||
|
||||
pool.Free(set);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.PooledObjects;
|
||||
|
||||
/// <summary>
|
||||
/// A pool of <see cref="List{T}"/> instances.
|
||||
/// </summary>
|
||||
///
|
||||
/// <remarks>
|
||||
/// Instances originating from this pool are intended to be short-lived and are suitable
|
||||
/// for temporary work. Do not return them as the results of methods or store them in fields.
|
||||
/// </remarks>
|
||||
internal static class ListPool<T>
|
||||
{
|
||||
private const int Threshold = 512;
|
||||
|
||||
private static readonly Func<ObjectPool<List<T>>, List<T>> s_allocate = AllocateAndClear;
|
||||
private static readonly Action<ObjectPool<List<T>>, List<T>> s_release = ClearAndFree;
|
||||
|
||||
public static ObjectPool<List<T>> DefaultPool { get; } = ObjectPool.Default<List<T>>();
|
||||
|
||||
public static PooledObject<List<T>> GetPooledObject()
|
||||
=> new(DefaultPool, s_allocate, s_release);
|
||||
|
||||
private static List<T> AllocateAndClear(ObjectPool<List<T>> pool)
|
||||
{
|
||||
var list = pool.Allocate();
|
||||
list.Clear();
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
private static void ClearAndFree(ObjectPool<List<T>> pool, List<T> list)
|
||||
{
|
||||
if (list is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var count = list.Count;
|
||||
list.Clear();
|
||||
|
||||
if (count > Threshold)
|
||||
{
|
||||
list.TrimExcess();
|
||||
}
|
||||
|
||||
pool.Free(list);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.PooledObjects;
|
||||
|
||||
internal static class ObjectPool
|
||||
{
|
||||
private const int DefaultSize = 20;
|
||||
|
||||
public static ObjectPool<T> Default<T>()
|
||||
where T : class, new()
|
||||
=> DefaultPool<T>.Instance;
|
||||
|
||||
public static ObjectPool<T> Default<T>(Func<T> factory)
|
||||
where T : class
|
||||
=> new(factory, DefaultSize);
|
||||
|
||||
private static class DefaultPool<T>
|
||||
where T : class, new()
|
||||
{
|
||||
public static readonly ObjectPool<T> Instance = new(() => new T(), DefaultSize);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,142 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||
|
||||
// Copied from https://github/dotnet/roslyn
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.PooledObjects;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
///
|
||||
/// <remarks>
|
||||
/// <para>Notes:</para>
|
||||
///
|
||||
/// <list type="number">
|
||||
/// <item>
|
||||
/// It is not the goal to keep all returned objects. The 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 this pool, the caller will return it back in
|
||||
/// a relatively short time. Keeping checked out objects for long durations is fine, but reduces
|
||||
/// the usefulness of pooling. Just new up your own object.
|
||||
/// </item>
|
||||
/// </list>
|
||||
///
|
||||
/// <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>
|
||||
/// </remarks>
|
||||
internal class ObjectPool<T>
|
||||
where T : class
|
||||
{
|
||||
[DebuggerDisplay("{Value,nq}")]
|
||||
private struct Element
|
||||
{
|
||||
public T? _value;
|
||||
}
|
||||
|
||||
// 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;
|
||||
private readonly Element[] _items;
|
||||
|
||||
private readonly Func<T> _factory;
|
||||
|
||||
public ObjectPool(Func<T> factory)
|
||||
: this(factory, Environment.ProcessorCount * 2)
|
||||
{
|
||||
}
|
||||
|
||||
public ObjectPool(Func<T> factory, int size)
|
||||
{
|
||||
_factory = factory;
|
||||
_items = new Element[size - 1];
|
||||
}
|
||||
|
||||
public ObjectPool(Func<ObjectPool<T>, T> factory, int size)
|
||||
{
|
||||
_factory = () => factory(this);
|
||||
_items = new Element[size - 1];
|
||||
}
|
||||
|
||||
private T CreateInstance() => _factory();
|
||||
|
||||
public T Allocate()
|
||||
{
|
||||
// PERF: Examine the first element. If that fails, AllocateSlow will look at the remaining elements.
|
||||
// Note that the initial read is optimistically not synchronized. That is intentional.
|
||||
// We will interlock only when we have a candidate. in a worst case we may miss some
|
||||
// recently returned objects. Not a big deal.
|
||||
|
||||
var item = _firstItem;
|
||||
if (item is null || item != Interlocked.CompareExchange(ref _firstItem, null, item))
|
||||
{
|
||||
item = AllocateSlow();
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
private T AllocateSlow()
|
||||
{
|
||||
var items = _items;
|
||||
|
||||
for (var i = 0; i < items.Length; i++)
|
||||
{
|
||||
// Note that the initial read is optimistically not synchronized. That is intentional.
|
||||
// We will interlock only when we have a candidate. in a worst case we may miss some
|
||||
// recently returned objects. Not a big deal.
|
||||
|
||||
var item = _items[i]._value;
|
||||
if (item is not null &&
|
||||
item == Interlocked.CompareExchange(ref items[i]._value, null, item))
|
||||
{
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
||||
return CreateInstance();
|
||||
}
|
||||
|
||||
public void Free(T obj)
|
||||
{
|
||||
if (_firstItem is null)
|
||||
{
|
||||
// Intentionally not using interlocked here.
|
||||
// In a worst case scenario two objects may be stored into same slot.
|
||||
// It is very unlikely to happen and will only mean that one of the objects will get collected.
|
||||
_firstItem = obj;
|
||||
}
|
||||
else
|
||||
{
|
||||
FreeSlow(obj);
|
||||
}
|
||||
}
|
||||
|
||||
private void FreeSlow(T obj)
|
||||
{
|
||||
var items = _items;
|
||||
|
||||
for (var i = 0; i < items.Length; i++)
|
||||
{
|
||||
if (items[i]._value is null)
|
||||
{
|
||||
// Intentionally not using interlocked here.
|
||||
// In a worst case scenario two objects may be stored into same slot.
|
||||
// It is very unlikely to happen and will only mean that one of the objects will get collected.
|
||||
items[i]._value = obj;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||
|
||||
// Copied from https://github/dotnet/roslyn
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.PooledObjects;
|
||||
|
||||
internal struct PooledObject<T> : IDisposable
|
||||
where T : class
|
||||
{
|
||||
private readonly ObjectPool<T> _pool;
|
||||
private readonly Action<ObjectPool<T>, T> _releaser;
|
||||
private T? _object;
|
||||
|
||||
// Because of how this API is intended to be used, we don't want the consumption code to have
|
||||
// to deal with Object being a nullable reference type. Intead, the guarantee is that this is
|
||||
// non-null until this is disposed.
|
||||
public T Object => _object!;
|
||||
|
||||
public PooledObject(ObjectPool<T> pool, Func<ObjectPool<T>, T> allocator, Action<ObjectPool<T>, T> releaser)
|
||||
: this()
|
||||
{
|
||||
_pool = pool;
|
||||
_object = allocator(pool);
|
||||
_releaser = releaser;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_object is { } obj)
|
||||
{
|
||||
_releaser(_pool, obj);
|
||||
_object = null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Razor.Utilities;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.PooledObjects;
|
||||
|
||||
/// <summary>
|
||||
/// A pool of <see cref="HashSet{T}"/> instances that compares items using reference equality.
|
||||
/// </summary>
|
||||
///
|
||||
/// <remarks>
|
||||
/// Instances originating from this pool are intended to be short-lived and are suitable
|
||||
/// for temporary work. Do not return them as the results of methods or store them in fields.
|
||||
/// </remarks>
|
||||
internal static class ReferenceEqualityHashSetPool<T>
|
||||
where T : class
|
||||
{
|
||||
private const int Threshold = 512;
|
||||
|
||||
private static readonly Func<ObjectPool<HashSet<T>>, HashSet<T>> s_allocate = AllocateAndClear;
|
||||
private static readonly Action<ObjectPool<HashSet<T>>, HashSet<T>> s_release = ClearAndFree;
|
||||
|
||||
public static ObjectPool<HashSet<T>> DefaultPool { get; } = ObjectPool.Default(() => new HashSet<T>(ReferenceEqualityComparer<T>.Instance));
|
||||
|
||||
public static PooledObject<HashSet<T>> GetPooledObject()
|
||||
=> new(DefaultPool, s_allocate, s_release);
|
||||
|
||||
private static HashSet<T> AllocateAndClear(ObjectPool<HashSet<T>> pool)
|
||||
{
|
||||
var set = pool.Allocate();
|
||||
set.Clear();
|
||||
|
||||
return set;
|
||||
}
|
||||
|
||||
private static void ClearAndFree(ObjectPool<HashSet<T>> pool, HashSet<T> set)
|
||||
{
|
||||
if (set is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var count = set.Count;
|
||||
set.Clear();
|
||||
|
||||
if (count > Threshold)
|
||||
{
|
||||
set.TrimExcess();
|
||||
}
|
||||
|
||||
pool.Free(set);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.PooledObjects;
|
||||
|
||||
/// <summary>
|
||||
/// A pool of <see cref="Stack{T}"/> instances.
|
||||
/// </summary>
|
||||
///
|
||||
/// <remarks>
|
||||
/// Instances originating from this pool are intended to be short-lived and are suitable
|
||||
/// for temporary work. Do not return them as the results of methods or store them in fields.
|
||||
/// </remarks>
|
||||
internal static class StackPool<T>
|
||||
{
|
||||
private const int Threshold = 512;
|
||||
|
||||
private static readonly Func<ObjectPool<Stack<T>>, Stack<T>> s_allocate = AllocateAndClear;
|
||||
private static readonly Action<ObjectPool<Stack<T>>, Stack<T>> s_release = ClearAndFree;
|
||||
|
||||
public static ObjectPool<Stack<T>> DefaultPool { get; } = ObjectPool.Default<Stack<T>>();
|
||||
|
||||
public static PooledObject<Stack<T>> GetPooledObject()
|
||||
=> new(DefaultPool, s_allocate, s_release);
|
||||
|
||||
private static Stack<T> AllocateAndClear(ObjectPool<Stack<T>> pool)
|
||||
{
|
||||
var stack = pool.Allocate();
|
||||
stack.Clear();
|
||||
|
||||
return stack;
|
||||
}
|
||||
|
||||
private static void ClearAndFree(ObjectPool<Stack<T>> pool, Stack<T> stack)
|
||||
{
|
||||
if (stack is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var count = stack.Count;
|
||||
stack.Clear();
|
||||
|
||||
if (count > Threshold)
|
||||
{
|
||||
stack.TrimExcess();
|
||||
}
|
||||
|
||||
pool.Free(stack);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.PooledObjects;
|
||||
|
||||
/// <summary>
|
||||
/// A pool of <see cref="StringBuilder"/> instances.
|
||||
/// </summary>
|
||||
///
|
||||
/// <remarks>
|
||||
/// Instances originating from this pool are intended to be short-lived and are suitable
|
||||
/// for temporary work. Do not return them as the results of methods or store them in fields.
|
||||
/// </remarks>
|
||||
internal static class StringBuilderPool
|
||||
{
|
||||
private const int Threshold = 512;
|
||||
|
||||
private static readonly Func<ObjectPool<StringBuilder>, StringBuilder> s_allocate = AllocateAndClear;
|
||||
private static readonly Action<ObjectPool<StringBuilder>, StringBuilder> s_release = ClearAndFree;
|
||||
|
||||
public static ObjectPool<StringBuilder> DefaultPool { get; } = ObjectPool.Default<StringBuilder>();
|
||||
|
||||
public static PooledObject<StringBuilder> GetPooledObject()
|
||||
=> new(DefaultPool, s_allocate, s_release);
|
||||
|
||||
private static StringBuilder AllocateAndClear(ObjectPool<StringBuilder> pool)
|
||||
{
|
||||
var builder = pool.Allocate();
|
||||
builder.Clear();
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
private static void ClearAndFree(ObjectPool<StringBuilder> pool, StringBuilder builder)
|
||||
{
|
||||
if (builder is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
builder.Clear();
|
||||
|
||||
if (builder.Capacity > Threshold)
|
||||
{
|
||||
builder.Capacity = Threshold;
|
||||
}
|
||||
|
||||
pool.Free(builder);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Utilities;
|
||||
|
||||
internal class ReferenceEqualityComparer<T> : IEqualityComparer<T>
|
||||
where T : class
|
||||
{
|
||||
public static readonly ReferenceEqualityComparer<T> Instance = new();
|
||||
|
||||
private ReferenceEqualityComparer()
|
||||
{
|
||||
}
|
||||
|
||||
bool IEqualityComparer<T>.Equals(T? x, T? y)
|
||||
=> ReferenceEquals(x, y);
|
||||
|
||||
int IEqualityComparer<T>.GetHashCode(T obj)
|
||||
=> RuntimeHelpers.GetHashCode(obj);
|
||||
}
|
Загрузка…
Ссылка в новой задаче