From 9c474fc241ac5be8cf5e09f198af913d49640f76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Thu, 16 Oct 2025 00:59:00 +0200 Subject: [PATCH] Use stack buffers for small composite collections --- PerfectNumbers.Core/PrimeOrderCalculator.cs | 191 +++++++++++++++++++- 1 file changed, 182 insertions(+), 9 deletions(-) diff --git a/PerfectNumbers.Core/PrimeOrderCalculator.cs b/PerfectNumbers.Core/PrimeOrderCalculator.cs index 8fddc0b..da15d5c 100644 --- a/PerfectNumbers.Core/PrimeOrderCalculator.cs +++ b/PerfectNumbers.Core/PrimeOrderCalculator.cs @@ -799,8 +799,8 @@ internal static partial class PrimeOrderCalculator gpuFactored = PrimeOrderGpuHeuristics.TryPartialFactor(value, limit, primeSlots, exponentSlots, out factorCount, out remaining, out _); } - List pending = ThreadStaticPools.RentUlongList(2); - Stack? compositeStack = null; + Span pendingStorage = stackalloc ulong[4]; + PendingBuffer pending = new PendingBuffer(pendingStorage); PartialFactorResult result; if (!gpuFactored) @@ -822,8 +822,10 @@ internal static partial class PrimeOrderCalculator if (config.PollardRhoMilliseconds > 0 && pending.Count > 0) { + Span compositeStorage = stackalloc ulong[8]; + CompositeStack compositeStack = new CompositeStack(compositeStorage); + long deadlineTimestamp = CreateDeadlineTimestamp(config.PollardRhoMilliseconds); - compositeStack = ThreadStaticPools.RentUlongStack(Math.Max(pending.Count * 2, 4)); compositeStack.Push(remaining); pending.Clear(); @@ -860,13 +862,16 @@ internal static partial class PrimeOrderCalculator compositeStack.Push(factor); compositeStack.Push(quotient); } + + compositeStack.Dispose(); } ulong cofactor = 1UL; - int pendingCount = pending.Count; + ReadOnlySpan pendingSpan = pending.AsSpan(); + int pendingCount = pendingSpan.Length; for (int i = 0; i < pendingCount; i++) { - ulong composite = pending[i]; + ulong composite = pendingSpan[i]; bool isPrime = Open.Numeric.Primes.Prime.Numbers.IsPrime(composite); if (isPrime) { @@ -956,13 +961,181 @@ internal static partial class PrimeOrderCalculator ThreadStaticPools.ReturnUlongIntDictionary(counts); } - ThreadStaticPools.ReturnUlongList(pending); - if (compositeStack is not null) + pending.Dispose(); + return result; + } + + private ref struct PendingBuffer + { + private Span _stack; + private ulong[]? _pooled; + private int _count; + + public PendingBuffer(Span stack) { - ThreadStaticPools.ReturnUlongStack(compositeStack); + _stack = stack; + _pooled = null; + _count = 0; } - return result; + public int Count => _count; + + public void Add(ulong value) + { + if (_pooled is null) + { + if (_count < _stack.Length) + { + _stack[_count] = value; + _count++; + return; + } + + int initialCapacity = _stack.Length == 0 ? 4 : _stack.Length * 2; + ulong[] newArray = ArrayPool.Shared.Rent(initialCapacity); + for (int i = 0; i < _count; i++) + { + newArray[i] = _stack[i]; + } + + _pooled = newArray; + } + + EnsurePooledCapacity(_count + 1); + _pooled![_count] = value; + _count++; + } + + private void EnsurePooledCapacity(int required) + { + ulong[] array = _pooled!; + if (required <= array.Length) + { + return; + } + + int newCapacity = array.Length * 2; + if (newCapacity < required) + { + newCapacity = required; + } + + ulong[] newArray = ArrayPool.Shared.Rent(newCapacity); + Array.Copy(array, 0, newArray, 0, _count); + ArrayPool.Shared.Return(array, clearArray: false); + _pooled = newArray; + } + + public void Clear() + { + _count = 0; + } + + public ReadOnlySpan AsSpan() + { + if (_pooled is null) + { + return _stack.Slice(0, _count); + } + + return new ReadOnlySpan(_pooled, 0, _count); + } + + public void Dispose() + { + if (_pooled is not null) + { + ArrayPool.Shared.Return(_pooled, clearArray: false); + _pooled = null; + } + + _count = 0; + } + } + + private ref struct CompositeStack + { + private Span _stack; + private ulong[]? _pooled; + private int _count; + + public CompositeStack(Span stack) + { + _stack = stack; + _pooled = null; + _count = 0; + } + + public int Count => _count; + + public void Push(ulong value) + { + if (_pooled is null) + { + if (_count < _stack.Length) + { + _stack[_count] = value; + _count++; + return; + } + + int initialCapacity = _stack.Length == 0 ? 4 : _stack.Length * 2; + ulong[] newArray = ArrayPool.Shared.Rent(initialCapacity); + for (int i = 0; i < _count; i++) + { + newArray[i] = _stack[i]; + } + + _pooled = newArray; + } + + EnsurePooledCapacity(_count + 1); + _pooled![_count] = value; + _count++; + } + + private void EnsurePooledCapacity(int required) + { + ulong[] array = _pooled!; + if (required <= array.Length) + { + return; + } + + int newCapacity = array.Length * 2; + if (newCapacity < required) + { + newCapacity = required; + } + + ulong[] newArray = ArrayPool.Shared.Rent(newCapacity); + Array.Copy(array, 0, newArray, 0, _count); + ArrayPool.Shared.Return(array, clearArray: false); + _pooled = newArray; + } + + public ulong Pop() + { + int index = _count - 1; + _count = index; + if (_pooled is null) + { + return _stack[index]; + } + + return _pooled[index]; + } + + public void Dispose() + { + if (_pooled is not null) + { + ArrayPool.Shared.Return(_pooled, clearArray: false); + _pooled = null; + } + + _count = 0; + } } private const int GpuSmallPrimeFactorSlots = 64;