fix thread safety of Pools, minor FishTable improvements

This commit is contained in:
bbradson
2024-03-10 19:45:02 +01:00
parent af759661bd
commit c584f58dfd
11 changed files with 267 additions and 132 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+15 -14
View File
@@ -12,6 +12,7 @@ using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using FisheryLib.FunctionPointers; using FisheryLib.FunctionPointers;
using FisheryLib.Pools;
using FisheryLib.Utility; using FisheryLib.Utility;
using JetBrains.Annotations; using JetBrains.Annotations;
@@ -227,14 +228,14 @@ public class FishSet<T> : ISet<T>, IReadOnlyCollection<T>, ICollection
if (IsTail(bucket, bucketIndex)) if (IsTail(bucket, bucketIndex))
{ {
var previousEntry = bucket; var previousEntry = bucket;
var tailingEntries = TryGetAndClearTailingEntries(bucketIndex); using var tailingEntries = TryGetAndClearTailingEntries(bucketIndex);
SetParentTail(bucketIndex, Tails.SOLO); SetParentTail(bucketIndex, Tails.SOLO);
SetEntryWithoutTail(bucketIndex, entry); SetEntryWithoutTail(bucketIndex, entry);
InsertEntry(previousEntry, ReplaceBehaviour.Throw, true); InsertEntry(previousEntry, ReplaceBehaviour.Throw, true);
if (tailingEntries != null) if (tailingEntries != default)
InsertEntries(tailingEntries, ReplaceBehaviour.Throw, true); InsertEntries(tailingEntries.List, ReplaceBehaviour.Throw, true);
return true; return true;
} }
@@ -245,9 +246,9 @@ public class FishSet<T> : ISet<T>, IReadOnlyCollection<T>, ICollection
} }
} }
private void InsertEntries(T[] tailingEntries, ReplaceBehaviour replaceBehaviour, bool shifting = false) private void InsertEntries(List<T> tailingEntries, ReplaceBehaviour replaceBehaviour, bool shifting = false)
{ {
for (var i = 0; i < tailingEntries.Length; i++) for (var i = 0; i < tailingEntries.Count; i++)
InsertEntry(tailingEntries[i], replaceBehaviour, shifting); InsertEntry(tailingEntries[i], replaceBehaviour, shifting);
} }
@@ -329,32 +330,32 @@ public class FishSet<T> : ISet<T>, IReadOnlyCollection<T>, ICollection
} }
var previousEntry = _buckets[bucketIndex]; var previousEntry = _buckets[bucketIndex];
var tailingEntries = TryGetAndClearTailingEntries(bucketIndex); using var tailingEntries = TryGetAndClearTailingEntries(bucketIndex);
SetParentTail(bucketIndex, Tails.SOLO); SetParentTail(bucketIndex, Tails.SOLO);
SetEntryAsTail(bucketIndex, entry, parentIndex, offset); SetEntryAsTail(bucketIndex, entry, parentIndex, offset);
InsertEntry(previousEntry!, ReplaceBehaviour.Throw, true); InsertEntry(previousEntry!, ReplaceBehaviour.Throw, true);
if (tailingEntries != null) if (tailingEntries != default)
InsertEntries(tailingEntries, ReplaceBehaviour.Throw, true); InsertEntries(tailingEntries.List, ReplaceBehaviour.Throw, true);
return true; return true;
} }
private T[]? TryGetAndClearTailingEntries(int bucketIndex) private PooledIList<List<T>> TryGetAndClearTailingEntries(int bucketIndex)
{ {
if (!HasTail(bucketIndex)) if (!HasTail(bucketIndex))
return null; return default;
var tailingEntries = new T[GetTailCount(bucketIndex)]; var tailingEntries = new PooledIList<List<T>>();
var i = 0; var tailingEntriesList = tailingEntries.List;
var tailIndex = GetTailIndex(bucketIndex); var tailIndex = GetTailIndex(bucketIndex);
while (true) while (true)
{ {
ref var tailingBucket = ref _buckets[tailIndex]; ref var tailingBucket = ref _buckets[tailIndex];
tailingEntries[i++] = tailingBucket!; tailingEntriesList.Add(tailingBucket!);
tailingBucket = default; tailingBucket = default;
if (HasTail(tailIndex)) if (HasTail(tailIndex))
+18 -18
View File
@@ -172,9 +172,9 @@ public class FishTable<TKey, TValue> : IDictionary<TKey, TValue>, IDictionary, I
IEnumerable<TValue> IReadOnlyDictionary<TKey, TValue>.Values => Values; IEnumerable<TValue> IReadOnlyDictionary<TKey, TValue>.Values => Values;
object? IDictionary.this[object key] object IDictionary.this[object key]
{ {
get => this[AssertKeyType(key)]; get => this[AssertKeyType(key)]!;
set => this[AssertKeyType(key)] = AssertValueType(value); set => this[AssertKeyType(key)] = AssertValueType(value);
} }
@@ -198,7 +198,7 @@ public class FishTable<TKey, TValue> : IDictionary<TKey, TValue>, IDictionary, I
return default!; return default!;
} }
public unsafe TValue? this[TKey key] public unsafe TValue this[TKey key]
{ {
[CollectionAccess(CollectionAccessType.Read)] [CollectionAccess(CollectionAccessType.Read)]
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -211,7 +211,7 @@ public class FishTable<TKey, TValue> : IDictionary<TKey, TValue>, IDictionary, I
ref var bucket = ref _buckets[bucketIndex]; ref var bucket = ref _buckets[bucketIndex];
if (_keyEqualityComparer(key, bucket.Key)) if (_keyEqualityComparer(key, bucket.Key))
return bucket.Value; return bucket.Value!;
ContinueWithTailOrThrow(ref bucketIndex, key); ContinueWithTailOrThrow(ref bucketIndex, key);
} }
@@ -352,14 +352,14 @@ public class FishTable<TKey, TValue> : IDictionary<TKey, TValue>, IDictionary, I
if (IsTail(ref bucket, bucketIndex)) if (IsTail(ref bucket, bucketIndex))
{ {
var previousEntry = bucket; var previousEntry = bucket;
var tailingEntries = TryGetAndClearTailingEntries(bucketIndex); using var tailingEntries = TryGetAndClearTailingEntries(bucketIndex);
SetParentTail(bucketIndex, Tails.SOLO); SetParentTail(bucketIndex, Tails.SOLO);
SetEntryWithoutTail(bucketIndex, entry); SetEntryWithoutTail(bucketIndex, entry);
InsertEntry(previousEntry, ReplaceBehaviour.Throw, true); InsertEntry(previousEntry, ReplaceBehaviour.Throw, true);
if (tailingEntries != null) if (tailingEntries != default)
InsertEntries(tailingEntries, ReplaceBehaviour.Throw, true); InsertEntries(tailingEntries.List, ReplaceBehaviour.Throw, true);
return true; return true;
} }
@@ -370,9 +370,9 @@ public class FishTable<TKey, TValue> : IDictionary<TKey, TValue>, IDictionary, I
} }
} }
private void InsertEntries(Entry[] tailingEntries, ReplaceBehaviour replaceBehaviour, bool shifting = false) private void InsertEntries(List<Entry> tailingEntries, ReplaceBehaviour replaceBehaviour, bool shifting = false)
{ {
for (var i = 0; i < tailingEntries.Length; i++) for (var i = 0; i < tailingEntries.Count; i++)
InsertEntry(tailingEntries[i], replaceBehaviour, shifting); InsertEntry(tailingEntries[i], replaceBehaviour, shifting);
} }
@@ -473,31 +473,31 @@ public class FishTable<TKey, TValue> : IDictionary<TKey, TValue>, IDictionary, I
} }
var previousEntry = _buckets[bucketIndex]; var previousEntry = _buckets[bucketIndex];
var tailingEntries = TryGetAndClearTailingEntries(bucketIndex); using var tailingEntries = TryGetAndClearTailingEntries(bucketIndex);
SetParentTail(bucketIndex, Tails.SOLO); SetParentTail(bucketIndex, Tails.SOLO);
SetEntryAsTail(bucketIndex, entry, parentIndex, offset); SetEntryAsTail(bucketIndex, entry, parentIndex, offset);
InsertEntry(previousEntry, ReplaceBehaviour.Throw, true); InsertEntry(previousEntry, ReplaceBehaviour.Throw, true);
if (tailingEntries != null) if (tailingEntries != default)
InsertEntries(tailingEntries, ReplaceBehaviour.Throw, true); InsertEntries(tailingEntries.List, ReplaceBehaviour.Throw, true);
return true; return true;
} }
private Entry[]? TryGetAndClearTailingEntries(int bucketIndex) private PooledIList<List<Entry>> TryGetAndClearTailingEntries(int bucketIndex)
{ {
if (!HasTail(bucketIndex)) if (!HasTail(bucketIndex))
return null; return default;
var tailingEntries = new Entry[GetTailCount(bucketIndex)]; var tailingEntries = new PooledIList<List<Entry>>();
var i = 0; var tailingEntriesList = tailingEntries.List;
var tailIndex = GetTailIndex(bucketIndex); var tailIndex = GetTailIndex(bucketIndex);
while (true) while (true)
{ {
ref var tailingBucket = ref _buckets[tailIndex]; ref var tailingBucket = ref _buckets[tailIndex];
tailingEntries[i++] = tailingBucket; tailingEntriesList.Add(tailingBucket);
tailingBucket = default; tailingBucket = default;
if (HasTail(tailIndex)) if (HasTail(tailIndex))
@@ -639,7 +639,7 @@ public class FishTable<TKey, TValue> : IDictionary<TKey, TValue>, IDictionary, I
} }
[CollectionAccess(CollectionAccessType.Read | CollectionAccessType.UpdatedContent)] [CollectionAccess(CollectionAccessType.Read | CollectionAccessType.UpdatedContent)]
public unsafe bool TryGetOrAddValue(TKey key, [MaybeNullWhen(false)] out TValue value) public unsafe bool TryGetOrAddValue(TKey key, out TValue value)
{ {
var result = true; var result = true;
+2 -2
View File
@@ -28,11 +28,11 @@ namespace FisheryLib;
[PublicAPI] [PublicAPI]
public static class FisheryLib public static class FisheryLib
{ {
public const decimal VERSION = 0.39995M; public const decimal VERSION = 0.5M;
public static decimal CurrentlyLoadedVersion { get; } = VERSION; public static decimal CurrentlyLoadedVersion { get; } = VERSION;
public const string VERSION_STRING = "0.3.999.5"; public const string VERSION_STRING = "0.5.0.0";
// public static string GetVersion() // public static string GetVersion()
// { // {
+71 -71
View File
@@ -1,81 +1,81 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<RootNamespace>FisheryLib</RootNamespace> <RootNamespace>FisheryLib</RootNamespace>
<AssemblyName>1Fishery</AssemblyName> <AssemblyName>1Fishery</AssemblyName>
<TargetFramework>net48</TargetFramework> <TargetFramework>net48</TargetFramework>
<LangVersion>preview</LangVersion> <LangVersion>preview</LangVersion>
<PlatformTarget>AnyCPU</PlatformTarget> <PlatformTarget>AnyCPU</PlatformTarget>
<Optimize>true</Optimize> <Optimize>true</Optimize>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath> <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<Authors>bradson</Authors> <Authors>bradson</Authors>
<Copyright>Copyright (c) 2022 bradson</Copyright> <Copyright>Copyright (c) 2022 bradson</Copyright>
<PackageLicenseFile>LICENSE</PackageLicenseFile> <PackageLicenseFile>LICENSE</PackageLicenseFile>
<EnableNETAnalyzers>True</EnableNETAnalyzers> <EnableNETAnalyzers>True</EnableNETAnalyzers>
<AnalysisLevel>preview-all</AnalysisLevel> <AnalysisLevel>preview-all</AnalysisLevel>
<DebugSymbols>true</DebugSymbols> <DebugSymbols>true</DebugSymbols>
<DebugType>portable</DebugType> <DebugType>portable</DebugType>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>5</WarningLevel> <WarningLevel>5</WarningLevel>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks> <AllowUnsafeBlocks>True</AllowUnsafeBlocks>
<Configurations>v1.4;v1.3</Configurations> <Configurations>v1.4;v1.3</Configurations>
<GenerateAssemblyVersionAttribute>false</GenerateAssemblyVersionAttribute> <GenerateAssemblyVersionAttribute>false</GenerateAssemblyVersionAttribute>
<GenerateAssemblyFileVersionAttribute>false</GenerateAssemblyFileVersionAttribute> <GenerateAssemblyFileVersionAttribute>false</GenerateAssemblyFileVersionAttribute>
<GenerateAssemblyInformationalVersionAttribute>false</GenerateAssemblyInformationalVersionAttribute> <GenerateAssemblyInformationalVersionAttribute>false</GenerateAssemblyInformationalVersionAttribute>
<!-- Version>0.3.800.85</Version --> <!-- Version>0.3.800.85</Version -->
<!-- Deterministic>False</Deterministic --> <!-- Deterministic>False</Deterministic -->
<NoWarn> <NoWarn>
1701;1702;IL2121;CS8500;CS9082;CA1000;CA1002;CA1003;CA1707;CA1010;CA1715;CA1716;CA1724;CA1031;CA1033; 1701;1702;IL2121;CS8500;CS9082;CA1000;CA1002;CA1003;CA1707;CA1010;CA1715;CA1716;CA1724;CA1031;CA1033;
CA1034;CA1043;CA1051;CA1305;CA1310;CA1710;CA1711;CA1810;CA1812;CA1819;CA1851;CA2002;CA5394;CA1725;CA1062 CA1034;CA1043;CA1051;CA1305;CA1310;CA1710;CA1711;CA1810;CA1812;CA1819;CA1851;CA2002;CA5394;CA1725;CA1062
</NoWarn> </NoWarn>
</PropertyGroup> </PropertyGroup>
<Choose> <Choose>
<When Condition="'$(Configuration)' == 'v1.4'"> <When Condition="'$(Configuration)' == 'v1.4'">
<PropertyGroup> <PropertyGroup>
<OutputPath>..\..\1.4\Assemblies\</OutputPath> <OutputPath>..\..\1.4\Assemblies\</OutputPath>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Krafs.Rimworld.Ref" Version="1.4.*" /> <PackageReference Include="Krafs.Rimworld.Ref" Version="1.4.*" />
</ItemGroup> </ItemGroup>
</When> </When>
<When Condition="'$(Configuration)' == 'v1.3'"> <When Condition="'$(Configuration)' == 'v1.3'">
<PropertyGroup> <PropertyGroup>
<OutputPath>..\..\1.3\Assemblies\</OutputPath> <OutputPath>..\..\1.3\Assemblies\</OutputPath>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Krafs.Rimworld.Ref" Version="1.3.*" /> <PackageReference Include="Krafs.Rimworld.Ref" Version="1.3.*" />
</ItemGroup> </ItemGroup>
</When> </When>
</Choose> </Choose>
<ItemGroup> <ItemGroup>
<PackageReference Include="Lib.Harmony" Version="2.*" ExcludeAssets="runtime" /> <PackageReference Include="Lib.Harmony" Version="2.2.*" ExcludeAssets="runtime" />
<PackageReference Include="PolySharp" Version="1.*" /> <PackageReference Include="PolySharp" Version="1.*" />
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="6.*" /> <PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="6.*" />
<PackageReference Include="Krafs.Publicizer" Version="2.*" /> <PackageReference Include="Krafs.Publicizer" Version="2.*" />
<Publicize Include="Assembly-CSharp;0Harmony" IncludeVirtualMembers="false" /> <Publicize Include="Assembly-CSharp;0Harmony" IncludeVirtualMembers="false" />
<DoNotPublicize Include="0Harmony:System.Runtime.CompilerServices.IgnoresAccessChecksToAttribute" /> <DoNotPublicize Include="0Harmony:System.Runtime.CompilerServices.IgnoresAccessChecksToAttribute" />
<DoNotPublicize Include="0Harmony:Mono.Collections.Generic.Collection`1" /> <DoNotPublicize Include="0Harmony:Mono.Collections.Generic.Collection`1" />
<Publicize Include="mscorlib:System.Collections.Generic.Dictionary`2.buckets" /> <Publicize Include="mscorlib:System.Collections.Generic.Dictionary`2.buckets" />
<Publicize Include="mscorlib:System.Collections.Generic.Dictionary`2.entries" /> <Publicize Include="mscorlib:System.Collections.Generic.Dictionary`2.entries" />
<Publicize Include="mscorlib:System.Collections.Generic.Dictionary`2.version" /> <Publicize Include="mscorlib:System.Collections.Generic.Dictionary`2.version" />
<Publicize Include="mscorlib:System.Collections.Generic.Dictionary`2+Entry" /> <Publicize Include="mscorlib:System.Collections.Generic.Dictionary`2+Entry" />
<Publicize Include="mscorlib:System.Collections.Generic.List`1._items" /> <Publicize Include="mscorlib:System.Collections.Generic.List`1._items" />
<Publicize Include="mscorlib:System.Collections.Generic.List`1._version" /> <Publicize Include="mscorlib:System.Collections.Generic.List`1._version" />
<Publicize Include="mscorlib:System.Collections.Generic.List`1._size" /> <Publicize Include="mscorlib:System.Collections.Generic.List`1._size" />
<Publicize Include="mscorlib:System.Collections.Generic.List`1.EnsureCapacity" /> <Publicize Include="mscorlib:System.Collections.Generic.List`1.EnsureCapacity" />
<Publicize Include="System.Core:System.Collections.Generic.HashSet`1._version" /> <Publicize Include="System.Core:System.Collections.Generic.HashSet`1._version" />
<Publicize Include="mscorlib:System.Runtime.CompilerServices.JitHelpers.UnsafeEnumCast" /> <Publicize Include="mscorlib:System.Runtime.CompilerServices.JitHelpers.UnsafeEnumCast" />
<Publicize Include="mscorlib:System.Runtime.CompilerServices.JitHelpers.UnsafeEnumCastLong" /> <Publicize Include="mscorlib:System.Runtime.CompilerServices.JitHelpers.UnsafeEnumCastLong" />
<Publicize Include="mscorlib:System.Runtime.CompilerServices.JitHelpers.UnsafeCast" /> <Publicize Include="mscorlib:System.Runtime.CompilerServices.JitHelpers.UnsafeCast" />
<Publicize Include="mscorlib:System.Reflection.Emit.Label.label" /> <Publicize Include="mscorlib:System.Reflection.Emit.Label.label" />
<Publicize Include="mscorlib:System.String.m_firstChar" /> <Publicize Include="mscorlib:System.String.m_firstChar" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="..\..\LICENSE"> <None Include="..\..\LICENSE">
<Pack>True</Pack> <Pack>True</Pack>
<PackagePath /> <PackagePath />
</None> </None>
</ItemGroup> </ItemGroup>
</Project> </Project>
+79 -12
View File
@@ -9,29 +9,96 @@ namespace FisheryLib.Pools;
public static class FishPool<T> where T : IFishPoolable public static class FishPool<T> where T : IFishPoolable
{ {
private static T?[] _freeItems = new T[4]; [ThreadStatic]
private static int _freeItemCount; private static T?[]? _itemsThreadStatic;
[ThreadStatic]
private static int _itemCountThreadStatic;
private static object _lockObject = new();
private static T?[] _itemsGlobal = new T[16];
private static int _itemCountGlobal;
public static T Get() public static T Get()
{ {
if (_freeItemCount == 0) if (_itemCountThreadStatic == 0)
return Reflection.New<T>(); FetchThroughLock();
ref var itemBucket = ref _itemsThreadStatic![--_itemCountThreadStatic];
var item = itemBucket;
itemBucket = default;
return item!;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static void FetchThroughLock()
{
var itemsThreadStatic = _itemsThreadStatic ??= InitializeItemsThreadStatic();
var createNew = false;
var freeItem = _freeItems[--_freeItemCount]; lock (_lockObject)
_freeItems[_freeItemCount] = default; {
return freeItem!; if (_itemCountGlobal != 0)
{
for (var i = 8; i-- > 0;)
{
itemsThreadStatic[i] = _itemsGlobal[--_itemCountGlobal];
_itemsGlobal[_itemCountGlobal] = default;
}
}
else
{
createNew = true;
}
}
if (createNew)
{
for (var i = 8; i-- > 0;)
itemsThreadStatic[i] = Reflection.New<T>();
}
_itemCountThreadStatic = 8;
} }
public static void Return(T item) public static void Return(T item)
{ {
item.Reset(); item.Reset();
if (++_freeItemCount >= _freeItems.Length)
ExpandFreeItems();
_freeItems[_freeItemCount - 1] = item; var itemsThreadStatic = _itemsThreadStatic ??= InitializeItemsThreadStatic();
if (_itemCountThreadStatic == 16)
PushThroughLock();
itemsThreadStatic[_itemCountThreadStatic++] = item;
} }
[MethodImpl(MethodImplOptions.NoInlining)] [MethodImpl(MethodImplOptions.NoInlining)]
private static void ExpandFreeItems() => Array.Resize(ref _freeItems, _freeItems.Length << 1); private static void PushThroughLock()
{
var itemsThreadStatic = _itemsThreadStatic!;
lock (_lockObject)
{
if (_itemCountGlobal + 8 > _itemsGlobal.Length)
ExpandGlobalItems();
for (var i = 16; i-- > 8;)
{
ref var itemBucket = ref itemsThreadStatic[i];
var item = itemBucket;
itemBucket = default;
_itemsGlobal[_itemCountGlobal++] = item;
}
}
_itemCountThreadStatic = 8;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static T?[] InitializeItemsThreadStatic() => new T?[16];
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ExpandGlobalItems() => Array.Resize(ref _itemsGlobal, _itemsGlobal.Length << 1);
} }
+80 -13
View File
@@ -12,8 +12,15 @@ public record struct PooledIList<T>() : IDisposable where T : IList, new()
{ {
public T List = Get(); public T List = Get();
private static T?[] _freeItems = new T[4]; [ThreadStatic]
private static int _freeItemCount; private static T?[]? _itemsThreadStatic;
[ThreadStatic]
private static int _itemCountThreadStatic;
private static object _lockObject = new();
private static T?[] _itemsGlobal = new T[16];
private static int _itemCountGlobal;
public void Dispose() public void Dispose()
{ {
@@ -23,27 +30,87 @@ public record struct PooledIList<T>() : IDisposable where T : IList, new()
Return(List); Return(List);
List = default!; List = default!;
} }
public static T Get() public static T Get()
{ {
if (_freeItemCount == 0) if (_itemCountThreadStatic == 0)
return Reflection.New<T>(); FetchThroughLock();
ref var itemBucket = ref _itemsThreadStatic![--_itemCountThreadStatic];
var item = itemBucket;
itemBucket = default;
return item!;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static void FetchThroughLock()
{
var itemsThreadStatic = _itemsThreadStatic ??= InitializeItemsThreadStatic();
var createNew = false;
var freeItem = _freeItems[--_freeItemCount]; lock (_lockObject)
_freeItems[_freeItemCount] = default; {
return freeItem!; if (_itemCountGlobal != 0)
{
for (var i = 8; i-- > 0;)
{
itemsThreadStatic[i] = _itemsGlobal[--_itemCountGlobal];
_itemsGlobal[_itemCountGlobal] = default;
}
}
else
{
createNew = true;
}
}
if (createNew)
{
for (var i = 8; i-- > 0;)
itemsThreadStatic[i] = Reflection.New<T>();
}
_itemCountThreadStatic = 8;
} }
public static void Return(T item) public static void Return(T item)
{ {
item.Clear(); item.Clear();
if (++_freeItemCount >= _freeItems.Length)
ExpandFreeItems();
_freeItems[_freeItemCount - 1] = item; var itemsThreadStatic = _itemsThreadStatic ??= InitializeItemsThreadStatic();
if (_itemCountThreadStatic == 16)
PushThroughLock();
itemsThreadStatic[_itemCountThreadStatic++] = item;
} }
[MethodImpl(MethodImplOptions.NoInlining)] [MethodImpl(MethodImplOptions.NoInlining)]
private static void ExpandFreeItems() => Array.Resize(ref _freeItems, _freeItems.Length << 1); private static void PushThroughLock()
{
var itemsThreadStatic = _itemsThreadStatic!;
lock (_lockObject)
{
if (_itemCountGlobal + 8 > _itemsGlobal.Length)
ExpandGlobalItems();
for (var i = 16; i-- > 8;)
{
ref var itemBucket = ref itemsThreadStatic[i];
var item = itemBucket;
itemBucket = default;
_itemsGlobal[_itemCountGlobal++] = item;
}
}
_itemCountThreadStatic = 8;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static T?[] InitializeItemsThreadStatic() => new T?[16];
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ExpandGlobalItems() => Array.Resize(ref _itemsGlobal, _itemsGlobal.Length << 1);
} }
+2 -2
View File
@@ -40,11 +40,11 @@ public static class SpanExtensions
=> ref Unsafe.Add(ref span.DangerousGetPinnableReference(), i); => ref Unsafe.Add(ref span.DangerousGetPinnableReference(), i);
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Sort<T>(this Span<T> span, IComparer<T>? comparer) public static void Sort<T>(this Span<T> span, IComparer<T>? comparer = null)
=> ArraySortHelper<T>.Default.Sort(span, comparer); => ArraySortHelper<T>.Default.Sort(span, comparer);
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Sort<TKey, TValue>(this Span<TValue> values, Span<TKey> keys, IComparer<TKey>? comparer) public static void Sort<TKey, TValue>(this Span<TValue> values, Span<TKey> keys, IComparer<TKey>? comparer = null)
=> ArraySortHelper<TKey, TValue>.Default.Sort(keys, values, comparer); => ArraySortHelper<TKey, TValue>.Default.Sort(keys, values, comparer);
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]