From 566cbc3599c7678b69ea7f6134284427a8ff1e8b Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Mon, 15 Dec 2025 17:22:55 +0100 Subject: [PATCH] PrimeOrderCalculator.Cpu optimizations. --- PerfectNumbers.Core/IntArrayPool.cs | 97 +++++++++++++--- PerfectNumbers.Core/PartialFactorResult.cs | 4 + .../PrimeOrderCalculator.Cpu.cs | 42 ++----- PerfectNumbers.Core/UlongArrayPool.cs | 108 ++++++++++++++---- 4 files changed, 176 insertions(+), 75 deletions(-) diff --git a/PerfectNumbers.Core/IntArrayPool.cs b/PerfectNumbers.Core/IntArrayPool.cs index 1d87e61..12fbd53 100644 --- a/PerfectNumbers.Core/IntArrayPool.cs +++ b/PerfectNumbers.Core/IntArrayPool.cs @@ -1,40 +1,103 @@ +using System; +using System.Numerics; using System.Runtime.CompilerServices; namespace PerfectNumbers.Core { public struct IntArrayPool(int poolCapacity) { - private readonly int[][] _arrays = new int[poolCapacity][]; - private int _count = 0; + private const int MinBucketPower = 1; + private static readonly int MaxBucketPower = CalculateMaxBucketPower(); + private static readonly int MaxBucketLength = 1 << MaxBucketPower; + private static readonly int BucketCount = MaxBucketPower - MinBucketPower + 1; + + private readonly int[][] _arrays = new int[poolCapacity * BucketCount][]; + private readonly int[] _counts = new int[BucketCount]; + private readonly int _bucketCapacity = poolCapacity; [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - public int[] Rent(int length) + public readonly int[] Rent(int length) { - int available = _count; - if (available == 0) + int bucketIndex = GetBucketIndex(length); + if (bucketIndex < 0) { return new int[length]; } - int[][] arrays = _arrays; - available--; - _count = available; + int[] counts = _counts; + int available = counts[bucketIndex]; + int bucketLength = GetBucketLength(bucketIndex); - int[] candidate = arrays[available]; - return candidate.Length >= length ? candidate : new int[length]; + if (available == 0) + { + return new int[bucketLength]; + } + + int offset = (bucketIndex * _bucketCapacity) + (available - 1); + counts[bucketIndex] = available - 1; + return _arrays[offset]; } [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - public void Return(int[] array) + public readonly void Return(int[] array) { - int[][] arrays = _arrays; - int available = _count; - - if (available < arrays.Length) + int capacity = array.Length; + int bucketIndex = GetBucketIndex(capacity); + if (bucketIndex < 0) { - arrays[available] = array; - _count = available + 1; + return; } + + int bucketLength = GetBucketLength(bucketIndex); + if (capacity < bucketLength) + { + return; + } + + int[] counts = _counts; + int available = counts[bucketIndex]; + int bucketCapacity = _bucketCapacity; + if (available >= bucketCapacity) + { + return; + } + + int offset = (bucketIndex * bucketCapacity) + available; + _arrays[offset] = array; + counts[bucketIndex] = available + 1; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private static int GetBucketIndex(int length) + { + if (length <= 0 || length > MaxBucketLength) + { + return -1; + } + + if (length <= (1 << MinBucketPower)) + { + return 0; + } + + int power = 32 - BitOperations.LeadingZeroCount((uint)(length - 1)); + if (power > MaxBucketPower) + { + return -1; + } + + return power - MinBucketPower; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private static int GetBucketLength(int bucketIndex) => 1 << (bucketIndex + MinBucketPower); + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private static int CalculateMaxBucketPower() + { + int maxLength = Math.Min(int.MaxValue, Array.MaxLength); + int leadingZeros = BitOperations.LeadingZeroCount((uint)maxLength); + return 31 - leadingZeros; } } } diff --git a/PerfectNumbers.Core/PartialFactorResult.cs b/PerfectNumbers.Core/PartialFactorResult.cs index 197f730..57bcec8 100644 --- a/PerfectNumbers.Core/PartialFactorResult.cs +++ b/PerfectNumbers.Core/PartialFactorResult.cs @@ -11,6 +11,10 @@ namespace PerfectNumbers.Core public readonly int[] Exponents = new int[PrimeOrderConstants.GpuSmallPrimeFactorSlots]; public readonly FixedCapacityStack CompositeStack = new (PrimeOrderConstants.GpuSmallPrimeFactorSlots); public readonly List PendingFactors = new (PrimeOrderConstants.GpuSmallPrimeFactorSlots); + public readonly ulong[] TempFactorsArray = new ulong[PrimeOrderConstants.GpuSmallPrimeFactorSlots << 3]; + public readonly int[] TempExponentsArray = new int[PrimeOrderConstants.GpuSmallPrimeFactorSlots << 3]; + public readonly ulong[] StackCandidates = new ulong[PrimeOrderConstants.GpuSmallPrimeFactorSlots << 3]; + public readonly bool[] StackEvaluations = new bool[PrimeOrderConstants.GpuSmallPrimeFactorSlots << 3]; private PartialFactorResult? _next; public ulong Cofactor; public bool FullyFactored; diff --git a/PerfectNumbers.Core/PrimeOrderCalculator.Cpu.cs b/PerfectNumbers.Core/PrimeOrderCalculator.Cpu.cs index ae9de74..fa34d9f 100644 --- a/PerfectNumbers.Core/PrimeOrderCalculator.Cpu.cs +++ b/PerfectNumbers.Core/PrimeOrderCalculator.Cpu.cs @@ -545,12 +545,12 @@ internal static partial class PrimeOrderCalculator private static ulong ExponentLoweringCpu(ulong order, ulong prime, PartialFactorResult factors, in MontgomeryDivisorData divisorData) { int length = factors.Count; - ReadOnlySpan factorSpan = factors.Factors.AsSpan(0, length); - ReadOnlySpan exponentsSpan = factors.Exponents.AsSpan(0, length); + ReadOnlySpan factorSpan = new(factors.Factors, 0, length); + ReadOnlySpan exponentsSpan = new(factors.Exponents, 0, length); int newLength = length + 1; - ulong[] tempFactorsArray = FixedCapacityPools.ExclusiveUlongArray.Rent(newLength); - int[] tempExponentsArray = FixedCapacityPools.ExclusiveIntArray.Rent(newLength); + ulong[] tempFactorsArray = factors.TempFactorsArray; + int[] tempExponentsArray = factors.TempExponentsArray; Span ulongBuffer = tempFactorsArray; factorSpan.CopyTo(ulongBuffer); @@ -568,14 +568,8 @@ internal static partial class PrimeOrderCalculator ExponentRemainderStepperCpu stepper = ThreadStaticPools.RentExponentStepperCpu(divisorData); - const int StackExponentCapacity = 16; - const int ExponentHardLimit = 256; - - Span stackCandidates = stackalloc ulong[StackExponentCapacity]; - Span stackEvaluations = stackalloc bool[StackExponentCapacity]; - - ulong[]? heapCandidateArray = null; - bool[]? heapEvaluationArray = null; + Span stackCandidates = new(factors.StackCandidates); + Span stackEvaluations = new(factors.StackEvaluations); for (int i = 0; i < length; i++) { @@ -587,31 +581,10 @@ internal static partial class PrimeOrderCalculator // continue; // } - if (exponent > ExponentHardLimit) - { - throw new InvalidOperationException($"Prime factor exponent {exponent} exceeds the supported limit of {ExponentHardLimit}."); - } - - if (exponent <= StackExponentCapacity) - { - ProcessExponentLoweringPrime(stackCandidates[..exponent], stackEvaluations[..exponent], ref order, primeFactor, exponent, ref stepper); - continue; - } - - heapCandidateArray ??= FixedCapacityPools.ExclusiveUlongArray.Rent(ExponentHardLimit); - heapEvaluationArray ??= ThreadStaticPools.BoolPool.Rent(ExponentHardLimit); - ProcessExponentLoweringPrime(heapCandidateArray.AsSpan(0, exponent), heapEvaluationArray.AsSpan(0, exponent), ref order, primeFactor, exponent, ref stepper); + ProcessExponentLoweringPrime(stackCandidates[..exponent], stackEvaluations[..exponent], ref order, primeFactor, exponent, ref stepper); } ThreadStaticPools.ReturnExponentStepperCpu(stepper); - - if (heapCandidateArray is not null) - { - FixedCapacityPools.ExclusiveUlongArray.Return(heapCandidateArray); - ThreadStaticPools.BoolPool.Return(heapEvaluationArray!); - } - - FixedCapacityPools.ExclusiveUlongArray.Return(tempFactorsArray); return order; } @@ -1945,4 +1918,3 @@ internal static partial class PrimeOrderCalculator } - diff --git a/PerfectNumbers.Core/UlongArrayPool.cs b/PerfectNumbers.Core/UlongArrayPool.cs index 5f6bffd..0fe68b8 100644 --- a/PerfectNumbers.Core/UlongArrayPool.cs +++ b/PerfectNumbers.Core/UlongArrayPool.cs @@ -1,40 +1,102 @@ +using System.Numerics; using System.Runtime.CompilerServices; namespace PerfectNumbers.Core { - public struct UlongArrayPool(int poolCapacity) - { - private readonly ulong[][] _arrays = new ulong[poolCapacity][]; - private int _count = 0; + public struct UlongArrayPool(int poolCapacity) + { + private const int MinBucketPower = 1; + private static readonly int MaxBucketPower = CalculateMaxBucketPower(); + private static readonly int MaxBucketLength = 1 << MaxBucketPower; + private static readonly int BucketCount = MaxBucketPower - MinBucketPower + 1; - [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - public ulong[] Rent(int length) + private readonly ulong[][] _arrays = new ulong[poolCapacity * BucketCount][]; + private readonly int[] _counts = new int[BucketCount]; + private readonly int _bucketCapacity = poolCapacity; + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public readonly ulong[] Rent(int length) { - int available = _count; - if (available == 0) + int bucketIndex = GetBucketIndex(length); + if (bucketIndex < 0) { return new ulong[length]; } - ulong[][] arrays = _arrays; - available--; - _count = available; + int[] counts = _counts; + int available = counts[bucketIndex]; + int bucketLength = GetBucketLength(bucketIndex); - ulong[] candidate = arrays[available]; - return candidate.Length >= length ? candidate : new ulong[length]; - } + if (available == 0) + { + return new ulong[bucketLength]; + } + + int offset = (bucketIndex * _bucketCapacity) + (available - 1); + counts[bucketIndex] = available - 1; + return _arrays[offset]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public readonly void Return(ulong[] array) + { + int capacity = array.Length; + int bucketIndex = GetBucketIndex(capacity); + if (bucketIndex < 0) + { + return; + } + + int bucketLength = GetBucketLength(bucketIndex); + if (capacity < bucketLength) + { + return; + } + + int[] counts = _counts; + int available = counts[bucketIndex]; + int bucketCapacity = _bucketCapacity; + if (available >= bucketCapacity) + { + return; + } + + int offset = (bucketIndex * bucketCapacity) + available; + _arrays[offset] = array; + counts[bucketIndex] = available + 1; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private static int GetBucketIndex(int length) + { + if (length <= 0 || length > MaxBucketLength) + { + return -1; + } + + if (length <= (1 << MinBucketPower)) + { + return 0; + } + + int power = 32 - BitOperations.LeadingZeroCount((uint)(length - 1)); + if (power > MaxBucketPower) + { + return -1; + } + + return power - MinBucketPower; + } [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - public void Return(ulong[] array) - { - ulong[][] arrays = _arrays; - int available = _count; + private static int GetBucketLength(int bucketIndex) => 1 << (bucketIndex + MinBucketPower); - if (available < arrays.Length) - { - arrays[available] = array; - _count = available + 1; - } + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private static int CalculateMaxBucketPower() + { + int maxLength = Math.Min(int.MaxValue, Array.MaxLength); + int leadingZeros = BitOperations.LeadingZeroCount((uint)maxLength); + return 31 - leadingZeros; } } }