Avoid pooling small buffers in CPU divisor pipeline

This commit is contained in:
Marek Lemańczyk committed 2025-10-11 21:17:18 +02:00
1 parent 8e9390bc5e
commit ad22329814
4 files changed
+86 -25

No files matched your search

+34 -4
View File
@@ -63,6 +63,8 @@ public sealed class DivisorCycleCache
int[]? rentedMissing = null; int[]? rentedMissing = null;
Span<int> missingBuffer = length <= StackBufferThreshold Span<int> missingBuffer = length <= StackBufferThreshold
? stackalloc int[length] ? stackalloc int[length]
: length < PerfectNumberConstants.PooledArrayThreshold
? new Span<int>(new int[length])
: new Span<int>(rentedMissing = ThreadLocalArrayPool<int>.Shared.Rent(length), 0, length); : new Span<int>(rentedMissing = ThreadLocalArrayPool<int>.Shared.Rent(length), 0, length);
int missingCount = 0; int missingCount = 0;
@@ -128,11 +130,15 @@ public sealed class DivisorCycleCache
ulong[]? rentedDivisors = null; ulong[]? rentedDivisors = null;
Span<ulong> workDivisors = count <= StackBufferThreshold Span<ulong> workDivisors = count <= StackBufferThreshold
? stackalloc ulong[count] ? stackalloc ulong[count]
: count < PerfectNumberConstants.PooledArrayThreshold
? new Span<ulong>(new ulong[count])
: new Span<ulong>(rentedDivisors = ThreadLocalArrayPool<ulong>.Shared.Rent(count), 0, count); : new Span<ulong>(rentedDivisors = ThreadLocalArrayPool<ulong>.Shared.Rent(count), 0, count);
ulong[]? rentedResults = null; ulong[]? rentedResults = null;
Span<ulong> workResults = count <= StackBufferThreshold Span<ulong> workResults = count <= StackBufferThreshold
? stackalloc ulong[count] ? stackalloc ulong[count]
: count < PerfectNumberConstants.PooledArrayThreshold
? new Span<ulong>(new ulong[count])
: new Span<ulong>(rentedResults = ThreadLocalArrayPool<ulong>.Shared.Rent(count), 0, count); : new Span<ulong>(rentedResults = ThreadLocalArrayPool<ulong>.Shared.Rent(count), 0, count);
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
@@ -150,7 +156,11 @@ public sealed class DivisorCycleCache
if (rentedDivisors is not null) if (rentedDivisors is not null)
{ {
ThreadLocalArrayPool<ulong>.Shared.Return(rentedDivisors, clearArray: false); ThreadLocalArrayPool<ulong>.Shared.Return(rentedDivisors, clearArray: false);
ThreadLocalArrayPool<ulong>.Shared.Return(rentedResults!, clearArray: false); }
if (rentedResults is not null)
{
ThreadLocalArrayPool<ulong>.Shared.Return(rentedResults, clearArray: false);
} }
} }
@@ -172,21 +182,29 @@ public sealed class DivisorCycleCache
ulong[]? rentedPow = null; ulong[]? rentedPow = null;
Span<ulong> powSpan = length <= StackBufferThreshold Span<ulong> powSpan = length <= StackBufferThreshold
? stackalloc ulong[length] ? stackalloc ulong[length]
: length < PerfectNumberConstants.PooledArrayThreshold
? new Span<ulong>(new ulong[length])
: new Span<ulong>(rentedPow = ThreadLocalArrayPool<ulong>.Shared.Rent(length), 0, length); : new Span<ulong>(rentedPow = ThreadLocalArrayPool<ulong>.Shared.Rent(length), 0, length);
ulong[]? rentedOrder = null; ulong[]? rentedOrder = null;
Span<ulong> orderSpan = length <= StackBufferThreshold Span<ulong> orderSpan = length <= StackBufferThreshold
? stackalloc ulong[length] ? stackalloc ulong[length]
: length < PerfectNumberConstants.PooledArrayThreshold
? new Span<ulong>(new ulong[length])
: new Span<ulong>(rentedOrder = ThreadLocalArrayPool<ulong>.Shared.Rent(length), 0, length); : new Span<ulong>(rentedOrder = ThreadLocalArrayPool<ulong>.Shared.Rent(length), 0, length);
ulong[]? rentedResult = null; ulong[]? rentedResult = null;
Span<ulong> resultSpan = length <= StackBufferThreshold Span<ulong> resultSpan = length <= StackBufferThreshold
? stackalloc ulong[length] ? stackalloc ulong[length]
: length < PerfectNumberConstants.PooledArrayThreshold
? new Span<ulong>(new ulong[length])
: new Span<ulong>(rentedResult = ThreadLocalArrayPool<ulong>.Shared.Rent(length), 0, length); : new Span<ulong>(rentedResult = ThreadLocalArrayPool<ulong>.Shared.Rent(length), 0, length);
byte[]? rentedStatus = null; byte[]? rentedStatus = null;
Span<byte> statusSpan = length <= StackBufferThreshold Span<byte> statusSpan = length <= StackBufferThreshold
? stackalloc byte[length] ? stackalloc byte[length]
: length < PerfectNumberConstants.PooledArrayThreshold
? new Span<byte>(new byte[length])
: new Span<byte>(rentedStatus = ThreadLocalArrayPool<byte>.Shared.Rent(length), 0, length); : new Span<byte>(rentedStatus = ThreadLocalArrayPool<byte>.Shared.Rent(length), 0, length);
ref ulong divisorRef = ref MemoryMarshal.GetReference(divisors); ref ulong divisorRef = ref MemoryMarshal.GetReference(divisors);
@@ -235,9 +253,21 @@ public sealed class DivisorCycleCache
if (rentedPow is not null) if (rentedPow is not null)
{ {
ThreadLocalArrayPool<ulong>.Shared.Return(rentedPow, clearArray: false); ThreadLocalArrayPool<ulong>.Shared.Return(rentedPow, clearArray: false);
ThreadLocalArrayPool<ulong>.Shared.Return(rentedOrder!, clearArray: false); }
ThreadLocalArrayPool<ulong>.Shared.Return(rentedResult!, clearArray: false);
ThreadLocalArrayPool<byte>.Shared.Return(rentedStatus!, clearArray: false); if (rentedOrder is not null)
{
ThreadLocalArrayPool<ulong>.Shared.Return(rentedOrder, clearArray: false);
}
if (rentedResult is not null)
{
ThreadLocalArrayPool<ulong>.Shared.Return(rentedResult, clearArray: false);
}
if (rentedStatus is not null)
{
ThreadLocalArrayPool<byte>.Shared.Return(rentedStatus, clearArray: false);
} }
execution.Dispose(); execution.Dispose();
+15 -2
View File
@@ -567,7 +567,10 @@ public class MersenneDivisorCycles
} }
int count = factorCounts.Count; int count = factorCounts.Count;
ulong[] primes = ThreadLocalArrayPool<ulong>.Shared.Rent(count); bool rentFromPool = count >= PerfectNumberConstants.PooledArrayThreshold;
ulong[] primes = rentFromPool
? ThreadLocalArrayPool<ulong>.Shared.Rent(count)
: new ulong[count];
try try
{ {
int index = 0; int index = 0;
@@ -606,10 +609,13 @@ public class MersenneDivisorCycles
return order; return order;
} }
finally finally
{
if (rentFromPool)
{ {
ThreadLocalArrayPool<ulong>.Shared.Return(primes, clearArray: false); ThreadLocalArrayPool<ulong>.Shared.Return(primes, clearArray: false);
} }
} }
}
private static ulong PollardRho64(ulong n) private static ulong PollardRho64(ulong n)
{ {
@@ -793,7 +799,11 @@ public class MersenneDivisorCycles
tasks[taskIndex] = Task.Run(() => tasks[taskIndex] = Task.Run(() =>
{ {
ulong rangeLength = localEnd - localStart + 1UL; ulong rangeLength = localEnd - localStart + 1UL;
(ulong divisor, ulong cycle)[] localCycles = ThreadLocalArrayPool<(ulong, ulong)>.Shared.Rent(checked((int)rangeLength)); int localLength = checked((int)rangeLength);
bool rentLocalCycles = localLength >= PerfectNumberConstants.PooledArrayThreshold;
(ulong divisor, ulong cycle)[] localCycles = rentLocalCycles
? ThreadLocalArrayPool<(ulong, ulong)>.Shared.Rent(localLength)
: new (ulong divisor, ulong cycle)[localLength];
int localCycleIndex = 0; int localCycleIndex = 0;
try try
@@ -840,9 +850,12 @@ public class MersenneDivisorCycles
} }
} }
finally finally
{
if (rentLocalCycles)
{ {
ThreadLocalArrayPool<(ulong, ulong)>.Shared.Return(localCycles, clearArray: false); ThreadLocalArrayPool<(ulong, ulong)>.Shared.Return(localCycles, clearArray: false);
} }
}
}); });
} }
+30 -17
View File
@@ -1,5 +1,4 @@
using System; using System;
using System.Buffers;
namespace PerfectNumbers.Core namespace PerfectNumbers.Core
{ {
@@ -8,12 +7,20 @@ namespace PerfectNumbers.Core
[ThreadStatic] [ThreadStatic]
private static PartialFactorResult? s_poolHead; private static PartialFactorResult? s_poolHead;
public static readonly PartialFactorResult Empty = new()
{
Cofactor = 1UL,
FullyFactored = true,
};
public FactorEntry[]? Factors; public FactorEntry[]? Factors;
private PartialFactorResult? _next;
public ulong Cofactor; public ulong Cofactor;
public bool FullyFactored; public bool FullyFactored;
public int Count; public int Count;
private PartialFactorResult? _next;
private bool _factorsFromPool;
private PartialFactorResult() private PartialFactorResult()
{ {
} }
@@ -30,52 +37,58 @@ namespace PerfectNumbers.Core
s_poolHead = instance._next; s_poolHead = instance._next;
} }
FactorEntry[]? factorArray = factors;
instance._next = null; instance._next = null;
instance.Factors = factors; instance.Factors = factorArray;
instance.Cofactor = cofactor; instance.Cofactor = cofactor;
instance.FullyFactored = fullyFactored; instance.FullyFactored = fullyFactored;
instance.Count = count; instance.Count = count;
instance._factorsFromPool = factorArray is not null && factorArray.Length >= PerfectNumberConstants.PooledArrayThreshold;
return instance; return instance;
} }
public static readonly PartialFactorResult Empty = new()
{
Cofactor = 1UL,
FullyFactored = true,
};
public PartialFactorResult WithAdditionalPrime(ulong prime) public PartialFactorResult WithAdditionalPrime(ulong prime)
{ {
FactorEntry[]? source = Factors; FactorEntry[]? source = Factors;
if (source is null || Count == 0) if (source is null || Count == 0)
{ {
FactorEntry[] local = ThreadLocalArrayPool<FactorEntry>.Shared.Rent(1); FactorEntry[] single = new FactorEntry[1];
local[0] = new FactorEntry(prime); single[0] = new FactorEntry(prime);
return Rent(local, 1UL, true, 1); return Rent(single, 1UL, true, 1);
} }
FactorEntry[] extended = ThreadLocalArrayPool<FactorEntry>.Shared.Rent(Count + 1); int newCount = Count + 1;
FactorEntry[] extended = newCount >= PerfectNumberConstants.PooledArrayThreshold
? ThreadLocalArrayPool<FactorEntry>.Shared.Rent(newCount)
: new FactorEntry[newCount];
Array.Copy(source, 0, extended, 0, Count); Array.Copy(source, 0, extended, 0, Count);
extended[Count] = new FactorEntry(prime); extended[Count] = new FactorEntry(prime);
Span<FactorEntry> span = extended.AsSpan(0, Count + 1); Span<FactorEntry> span = extended.AsSpan(0, newCount);
span.Sort(static (a, b) => a.Value.CompareTo(b.Value)); span.Sort(static (a, b) => a.Value.CompareTo(b.Value));
return Rent(extended, 1UL, true, Count + 1); return Rent(extended, 1UL, true, newCount);
} }
public void Dispose() public void Dispose()
{ {
if (this != Empty) if (this == Empty)
{ {
return;
}
FactorEntry[]? factors = Factors; FactorEntry[]? factors = Factors;
if (factors is not null) if (factors is not null)
{ {
Factors = null; Factors = null;
if (_factorsFromPool)
{
ThreadLocalArrayPool<FactorEntry>.Shared.Return(factors, clearArray: false); ThreadLocalArrayPool<FactorEntry>.Shared.Return(factors, clearArray: false);
} }
}
_factorsFromPool = false;
_next = s_poolHead; _next = s_poolHead;
s_poolHead = this; s_poolHead = this;
} }
} }
}
} }
+7 -2
View File
@@ -1058,7 +1058,10 @@ internal static partial class PrimeOrderCalculator
return PartialFactorResult.Rent(null, cofactor, cofactor == 1UL, 0); return PartialFactorResult.Rent(null, cofactor, cofactor == 1UL, 0);
} }
FactorEntry[] factors = ThreadLocalArrayPool<FactorEntry>.Shared.Rent(counts.Count); int dictionaryCount = counts.Count;
FactorEntry[] factors = dictionaryCount >= PerfectNumberConstants.PooledArrayThreshold
? ThreadLocalArrayPool<FactorEntry>.Shared.Rent(dictionaryCount)
: new FactorEntry[dictionaryCount];
int index = 0; int index = 0;
foreach (KeyValuePair<ulong, int> entry in counts) foreach (KeyValuePair<ulong, int> entry in counts)
{ {
@@ -1092,7 +1095,9 @@ internal static partial class PrimeOrderCalculator
return PartialFactorResult.Rent(null, cofactor, cofactor == 1UL, 0); return PartialFactorResult.Rent(null, cofactor, cofactor == 1UL, 0);
} }
FactorEntry[] array = ThreadLocalArrayPool<FactorEntry>.Shared.Rent(actualCount); FactorEntry[] array = actualCount >= PerfectNumberConstants.PooledArrayThreshold
? ThreadLocalArrayPool<FactorEntry>.Shared.Rent(actualCount)
: new FactorEntry[actualCount];
int arrayIndex = 0; int arrayIndex = 0;
for (int i = 0; i < factorCount; i++) for (int i = 0; i < factorCount; i++)
{ {