diff --git a/PerfectNumbers.Core/Cpu/MersenneNumberDivisorByDivisorCpuTester.cs b/PerfectNumbers.Core/Cpu/MersenneNumberDivisorByDivisorCpuTester.cs index f5862af..d4d74cd 100644 --- a/PerfectNumbers.Core/Cpu/MersenneNumberDivisorByDivisorCpuTester.cs +++ b/PerfectNumbers.Core/Cpu/MersenneNumberDivisorByDivisorCpuTester.cs @@ -192,6 +192,10 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv Dictionary? factorCache = null; DivisorCycleCache cycleCache = DivisorCycleCache.Shared; + ulong cachedModulus = 0UL; + MontgomeryDivisorData cachedDivisorData = default; + bool hasCachedDivisorData = false; + byte step10 = (byte)(step % 10UL); byte step8 = (byte)(step % 8UL); byte step5 = (byte)(step % 5UL); @@ -222,7 +226,14 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv if (admissible && (remainder8 == 1 || remainder8 == 7) && remainder3 != 0 && remainder5 != 0 && remainder7 != 0 && remainder11 != 0) { - MontgomeryDivisorData divisorData = MontgomeryDivisorDataCache.Get(candidate); + if (!hasCachedDivisorData || cachedModulus != candidate) + { + cachedDivisorData = MontgomeryDivisorData.FromModulus(candidate); + cachedModulus = candidate; + hasCachedDivisorData = true; + } + + MontgomeryDivisorData divisorData = cachedDivisorData; ulong divisorCycle; if (candidate <= PerfectNumberConstants.MaxQForDivisorCycles) @@ -340,6 +351,9 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv { private readonly MersenneNumberDivisorByDivisorCpuTester _owner; private bool _disposed; + private bool _hasCachedDivisorData; + private ulong _cachedDivisor; + private MontgomeryDivisorData _cachedDivisorData; internal DivisorScanSession(MersenneNumberDivisorByDivisorCpuTester owner) { @@ -349,6 +363,9 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv internal void Reset() { _disposed = false; + _hasCachedDivisorData = false; + _cachedDivisor = 0UL; + _cachedDivisorData = default; } public void CheckDivisor(ulong divisor, in MontgomeryDivisorData divisorData, ulong divisorCycle, ReadOnlySpan primes, Span hits) @@ -367,7 +384,14 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv MontgomeryDivisorData cachedData = divisorData; if (cachedData.Modulus != divisor) { - cachedData = MontgomeryDivisorDataCache.Get(divisor); + if (!_hasCachedDivisorData || _cachedDivisor != divisor) + { + _cachedDivisorData = MontgomeryDivisorData.FromModulus(divisor); + _cachedDivisor = divisor; + _hasCachedDivisorData = true; + } + + cachedData = _cachedDivisorData; } if (divisorCycle == 0UL) diff --git a/PerfectNumbers.Core/DivisorCycleCache.cs b/PerfectNumbers.Core/DivisorCycleCache.cs index b01e5e2..c6480ee 100644 --- a/PerfectNumbers.Core/DivisorCycleCache.cs +++ b/PerfectNumbers.Core/DivisorCycleCache.cs @@ -63,7 +63,9 @@ public sealed class DivisorCycleCache int[]? rentedMissing = null; Span missingBuffer = length <= StackBufferThreshold ? stackalloc int[length] - : new Span(rentedMissing = ArrayPool.Shared.Rent(length), 0, length); + : length < PerfectNumberConstants.PooledArrayThreshold + ? new Span(new int[length]) + : new Span(rentedMissing = ThreadLocalArrayPool.Shared.Rent(length), 0, length); int missingCount = 0; @@ -106,7 +108,7 @@ public sealed class DivisorCycleCache if (rentedMissing is not null) { - ArrayPool.Shared.Return(rentedMissing, clearArray: false); + ThreadLocalArrayPool.Shared.Return(rentedMissing, clearArray: false); } } @@ -128,12 +130,16 @@ public sealed class DivisorCycleCache ulong[]? rentedDivisors = null; Span workDivisors = count <= StackBufferThreshold ? stackalloc ulong[count] - : new Span(rentedDivisors = ArrayPool.Shared.Rent(count), 0, count); + : count < PerfectNumberConstants.PooledArrayThreshold + ? new Span(new ulong[count]) + : new Span(rentedDivisors = ThreadLocalArrayPool.Shared.Rent(count), 0, count); ulong[]? rentedResults = null; Span workResults = count <= StackBufferThreshold ? stackalloc ulong[count] - : new Span(rentedResults = ArrayPool.Shared.Rent(count), 0, count); + : count < PerfectNumberConstants.PooledArrayThreshold + ? new Span(new ulong[count]) + : new Span(rentedResults = ThreadLocalArrayPool.Shared.Rent(count), 0, count); for (int i = 0; i < count; i++) { @@ -149,8 +155,12 @@ public sealed class DivisorCycleCache if (rentedDivisors is not null) { - ArrayPool.Shared.Return(rentedDivisors, clearArray: false); - ArrayPool.Shared.Return(rentedResults!, clearArray: false); + ThreadLocalArrayPool.Shared.Return(rentedDivisors, clearArray: false); + } + + if (rentedResults is not null) + { + ThreadLocalArrayPool.Shared.Return(rentedResults, clearArray: false); } } @@ -172,22 +182,30 @@ public sealed class DivisorCycleCache ulong[]? rentedPow = null; Span powSpan = length <= StackBufferThreshold ? stackalloc ulong[length] - : new Span(rentedPow = ArrayPool.Shared.Rent(length), 0, length); + : length < PerfectNumberConstants.PooledArrayThreshold + ? new Span(new ulong[length]) + : new Span(rentedPow = ThreadLocalArrayPool.Shared.Rent(length), 0, length); ulong[]? rentedOrder = null; Span orderSpan = length <= StackBufferThreshold ? stackalloc ulong[length] - : new Span(rentedOrder = ArrayPool.Shared.Rent(length), 0, length); + : length < PerfectNumberConstants.PooledArrayThreshold + ? new Span(new ulong[length]) + : new Span(rentedOrder = ThreadLocalArrayPool.Shared.Rent(length), 0, length); ulong[]? rentedResult = null; Span resultSpan = length <= StackBufferThreshold ? stackalloc ulong[length] - : new Span(rentedResult = ArrayPool.Shared.Rent(length), 0, length); + : length < PerfectNumberConstants.PooledArrayThreshold + ? new Span(new ulong[length]) + : new Span(rentedResult = ThreadLocalArrayPool.Shared.Rent(length), 0, length); byte[]? rentedStatus = null; Span statusSpan = length <= StackBufferThreshold ? stackalloc byte[length] - : new Span(rentedStatus = ArrayPool.Shared.Rent(length), 0, length); + : length < PerfectNumberConstants.PooledArrayThreshold + ? new Span(new byte[length]) + : new Span(rentedStatus = ThreadLocalArrayPool.Shared.Rent(length), 0, length); ref ulong divisorRef = ref MemoryMarshal.GetReference(divisors); divisorBuffer.View.CopyFromCPU(ref divisorRef, length); @@ -234,10 +252,22 @@ public sealed class DivisorCycleCache resultSpan.CopyTo(destination); if (rentedPow is not null) { - ArrayPool.Shared.Return(rentedPow, clearArray: false); - ArrayPool.Shared.Return(rentedOrder!, clearArray: false); - ArrayPool.Shared.Return(rentedResult!, clearArray: false); - ArrayPool.Shared.Return(rentedStatus!, clearArray: false); + ThreadLocalArrayPool.Shared.Return(rentedPow, clearArray: false); + } + + if (rentedOrder is not null) + { + ThreadLocalArrayPool.Shared.Return(rentedOrder, clearArray: false); + } + + if (rentedResult is not null) + { + ThreadLocalArrayPool.Shared.Return(rentedResult, clearArray: false); + } + + if (rentedStatus is not null) + { + ThreadLocalArrayPool.Shared.Return(rentedStatus, clearArray: false); } execution.Dispose(); diff --git a/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs b/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs index 2220711..a67ce8f 100644 --- a/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs +++ b/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs @@ -7,7 +7,6 @@ using System.Numerics; using ILGPU; using ILGPU.Runtime; using MontgomeryDivisorData = PerfectNumbers.Core.MontgomeryDivisorData; -using MontgomeryDivisorDataCache = PerfectNumbers.Core.MontgomeryDivisorDataCache; using CycleRemainderStepper = PerfectNumbers.Core.CycleRemainderStepper; namespace PerfectNumbers.Core.Gpu; diff --git a/PerfectNumbers.Core/MersenneDivisorCycles.cs b/PerfectNumbers.Core/MersenneDivisorCycles.cs index e208ec1..a6b7659 100644 --- a/PerfectNumbers.Core/MersenneDivisorCycles.cs +++ b/PerfectNumbers.Core/MersenneDivisorCycles.cs @@ -567,7 +567,10 @@ public class MersenneDivisorCycles } int count = factorCounts.Count; - ulong[] primes = ArrayPool.Shared.Rent(count); + bool rentFromPool = count >= PerfectNumberConstants.PooledArrayThreshold; + ulong[] primes = rentFromPool + ? ThreadLocalArrayPool.Shared.Rent(count) + : new ulong[count]; try { int index = 0; @@ -607,7 +610,10 @@ public class MersenneDivisorCycles } finally { - ArrayPool.Shared.Return(primes, clearArray: false); + if (rentFromPool) + { + ThreadLocalArrayPool.Shared.Return(primes, clearArray: false); + } } } @@ -793,7 +799,11 @@ public class MersenneDivisorCycles tasks[taskIndex] = Task.Run(() => { ulong rangeLength = localEnd - localStart + 1UL; - (ulong divisor, ulong cycle)[] localCycles = ArrayPool<(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; try @@ -841,7 +851,10 @@ public class MersenneDivisorCycles } finally { - ArrayPool<(ulong, ulong)>.Shared.Return(localCycles, clearArray: false); + if (rentLocalCycles) + { + ThreadLocalArrayPool<(ulong, ulong)>.Shared.Return(localCycles, clearArray: false); + } } }); } @@ -856,7 +869,7 @@ public class MersenneDivisorCycles ulong start = 3UL; var locker = new object(); - var pool = ArrayPool.Shared; + var pool = ThreadLocalArrayPool.Shared; ulong batchSizeUL = (ulong)batchSize, d, end; int count = (int)Math.Min(batchSizeUL, maxDivisor), i, idx; ulong[] divisors, outCycles, validDivisors; diff --git a/PerfectNumbers.Core/MontgomeryDivisorData.cs b/PerfectNumbers.Core/MontgomeryDivisorData.cs index 348471b..187e921 100644 --- a/PerfectNumbers.Core/MontgomeryDivisorData.cs +++ b/PerfectNumbers.Core/MontgomeryDivisorData.cs @@ -45,32 +45,4 @@ public readonly struct MontgomeryDivisorData(ulong modulus, ulong nPrime, ulong montgomeryTwo, montgomeryTwoSquared); } - -} - -internal static class MontgomeryDivisorDataCache -{ - [ThreadStatic] - private static bool s_hasCachedValue; - - [ThreadStatic] - private static ulong s_cachedModulus; - - [ThreadStatic] - private static MontgomeryDivisorData s_cachedData; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static MontgomeryDivisorData Get(ulong modulus) - { - if (s_hasCachedValue && s_cachedModulus == modulus) - { - return s_cachedData; - } - - MontgomeryDivisorData computed = MontgomeryDivisorData.FromModulus(modulus); - s_cachedModulus = modulus; - s_cachedData = computed; - s_hasCachedValue = true; - return computed; - } } diff --git a/PerfectNumbers.Core/PartialFactorResult.cs b/PerfectNumbers.Core/PartialFactorResult.cs index bd5b713..beb36ee 100644 --- a/PerfectNumbers.Core/PartialFactorResult.cs +++ b/PerfectNumbers.Core/PartialFactorResult.cs @@ -1,19 +1,26 @@ using System; -using System.Buffers; namespace PerfectNumbers.Core { public sealed class PartialFactorResult { - [ThreadStatic] - private static PartialFactorResult? s_poolHead; + [ThreadStatic] + private static PartialFactorResult? s_poolHead; + + public static readonly PartialFactorResult Empty = new() + { + Cofactor = 1UL, + FullyFactored = true, + }; public FactorEntry[]? Factors; - private PartialFactorResult? _next; public ulong Cofactor; public bool FullyFactored; public int Count; + private PartialFactorResult? _next; + private bool _factorsFromPool; + private PartialFactorResult() { } @@ -30,52 +37,58 @@ namespace PerfectNumbers.Core s_poolHead = instance._next; } + FactorEntry[]? factorArray = factors; instance._next = null; - instance.Factors = factors; + instance.Factors = factorArray; instance.Cofactor = cofactor; instance.FullyFactored = fullyFactored; instance.Count = count; + instance._factorsFromPool = factorArray is not null && factorArray.Length >= PerfectNumberConstants.PooledArrayThreshold; return instance; } - public static readonly PartialFactorResult Empty = new() - { - Cofactor = 1UL, - FullyFactored = true, - }; - public PartialFactorResult WithAdditionalPrime(ulong prime) { FactorEntry[]? source = Factors; if (source is null || Count == 0) { - FactorEntry[] local = ArrayPool.Shared.Rent(1); - local[0] = new FactorEntry(prime); - return Rent(local, 1UL, true, 1); + FactorEntry[] single = new FactorEntry[1]; + single[0] = new FactorEntry(prime); + return Rent(single, 1UL, true, 1); } - FactorEntry[] extended = ArrayPool.Shared.Rent(Count + 1); + int newCount = Count + 1; + FactorEntry[] extended = newCount >= PerfectNumberConstants.PooledArrayThreshold + ? ThreadLocalArrayPool.Shared.Rent(newCount) + : new FactorEntry[newCount]; + Array.Copy(source, 0, extended, 0, Count); extended[Count] = new FactorEntry(prime); - Span span = extended.AsSpan(0, Count + 1); + Span span = extended.AsSpan(0, newCount); 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() { - if (this != Empty) + if (this == Empty) { - FactorEntry[]? factors = Factors; - if (factors is not null) - { - Factors = null; - ArrayPool.Shared.Return(factors, clearArray: false); - } - - _next = s_poolHead; - s_poolHead = this; + return; } + + FactorEntry[]? factors = Factors; + if (factors is not null) + { + Factors = null; + if (_factorsFromPool) + { + ThreadLocalArrayPool.Shared.Return(factors, clearArray: false); + } + } + + _factorsFromPool = false; + _next = s_poolHead; + s_poolHead = this; } } } diff --git a/PerfectNumbers.Core/PrimeFactorPartialResult.cs b/PerfectNumbers.Core/PrimeFactorPartialResult.cs index 549f401..bdb8d0d 100644 --- a/PerfectNumbers.Core/PrimeFactorPartialResult.cs +++ b/PerfectNumbers.Core/PrimeFactorPartialResult.cs @@ -81,12 +81,12 @@ namespace PerfectNumbers.Core { if (_primes is not null) { - ArrayPool.Shared.Return(_primes, clearArray: false); + ThreadLocalArrayPool.Shared.Return(_primes, clearArray: false); } if (_exponents is not null) { - ArrayPool.Shared.Return(_exponents, clearArray: false); + ThreadLocalArrayPool.Shared.Return(_exponents, clearArray: false); } _arraysFromPool = false; @@ -119,12 +119,12 @@ namespace PerfectNumbers.Core { if (_primes is not null) { - ArrayPool.Shared.Return(_primes, clearArray: false); + ThreadLocalArrayPool.Shared.Return(_primes, clearArray: false); } if (_exponents is not null) { - ArrayPool.Shared.Return(_exponents, clearArray: false); + ThreadLocalArrayPool.Shared.Return(_exponents, clearArray: false); } _arraysFromPool = false; @@ -134,8 +134,8 @@ namespace PerfectNumbers.Core if (capacity >= PerfectNumberConstants.PooledArrayThreshold) { - _primes = ArrayPool.Shared.Rent(capacity); - _exponents = ArrayPool.Shared.Rent(capacity); + _primes = ThreadLocalArrayPool.Shared.Rent(capacity); + _exponents = ThreadLocalArrayPool.Shared.Rent(capacity); _arraysFromPool = true; } else diff --git a/PerfectNumbers.Core/PrimeOrderCalculator.Wide.cs b/PerfectNumbers.Core/PrimeOrderCalculator.Wide.cs index dee949d..206fe70 100644 --- a/PerfectNumbers.Core/PrimeOrderCalculator.Wide.cs +++ b/PerfectNumbers.Core/PrimeOrderCalculator.Wide.cs @@ -162,7 +162,7 @@ internal static partial class PrimeOrderCalculator return order; } - tempArray = ArrayPool.Shared.Rent(length + 1); + tempArray = ThreadLocalArrayPool.Shared.Rent(length + 1); Span buffer = tempArray.AsSpan(0, length); factorSpan.CopyTo(buffer); @@ -200,7 +200,7 @@ internal static partial class PrimeOrderCalculator { if (tempArray is not null) { - ArrayPool.Shared.Return(tempArray, clearArray: false); + ThreadLocalArrayPool.Shared.Return(tempArray, clearArray: false); } } } @@ -664,7 +664,7 @@ internal static partial class PrimeOrderCalculator return new PartialFactorResult128(null, value, false, 0); } - FactorEntry128[]? rented = counts.Count > 0 ? ArrayPool.Shared.Rent(counts.Count) : null; + FactorEntry128[]? rented = counts.Count > 0 ? ThreadLocalArrayPool.Shared.Rent(counts.Count) : null; int index = 0; if (rented is not null) { @@ -682,7 +682,7 @@ internal static partial class PrimeOrderCalculator span.Sort(static (a, b) => a.Value.CompareTo(b.Value)); resultArray = new FactorEntry128[index]; span.CopyTo(resultArray); - ArrayPool.Shared.Return(rented, clearArray: false); + ThreadLocalArrayPool.Shared.Return(rented, clearArray: false); } bool fullyFactored = cofactor == UInt128.One; diff --git a/PerfectNumbers.Core/PrimeOrderCalculator.cs b/PerfectNumbers.Core/PrimeOrderCalculator.cs index d08a13e..1312815 100644 --- a/PerfectNumbers.Core/PrimeOrderCalculator.cs +++ b/PerfectNumbers.Core/PrimeOrderCalculator.cs @@ -1058,7 +1058,10 @@ internal static partial class PrimeOrderCalculator return PartialFactorResult.Rent(null, cofactor, cofactor == 1UL, 0); } - FactorEntry[] factors = ArrayPool.Shared.Rent(counts.Count); + int dictionaryCount = counts.Count; + FactorEntry[] factors = dictionaryCount >= PerfectNumberConstants.PooledArrayThreshold + ? ThreadLocalArrayPool.Shared.Rent(dictionaryCount) + : new FactorEntry[dictionaryCount]; int index = 0; foreach (KeyValuePair entry in counts) { @@ -1092,7 +1095,9 @@ internal static partial class PrimeOrderCalculator return PartialFactorResult.Rent(null, cofactor, cofactor == 1UL, 0); } - FactorEntry[] array = ArrayPool.Shared.Rent(actualCount); + FactorEntry[] array = actualCount >= PerfectNumberConstants.PooledArrayThreshold + ? ThreadLocalArrayPool.Shared.Rent(actualCount) + : new FactorEntry[actualCount]; int arrayIndex = 0; for (int i = 0; i < factorCount; i++) { diff --git a/PerfectNumbers.Core/ThreadLocalArrayPool.cs b/PerfectNumbers.Core/ThreadLocalArrayPool.cs new file mode 100644 index 0000000..28ed389 --- /dev/null +++ b/PerfectNumbers.Core/ThreadLocalArrayPool.cs @@ -0,0 +1,27 @@ +using System; +using System.Buffers; +using System.Runtime.CompilerServices; + +namespace PerfectNumbers.Core; + +internal static class ThreadLocalArrayPool +{ + [ThreadStatic] + private static ArrayPool? s_threadPool; + + public static ArrayPool Shared + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + ArrayPool? pool = s_threadPool; + if (pool is null) + { + pool = ArrayPool.Create(); + s_threadPool = pool; + } + + return pool; + } + } +}