PrimeOrderCalculator.Cpu optimizations.

This commit is contained in:
mlemanczyk committed 2025-12-15 17:22:55 +01:00
1 parent 5523469e8a
commit 566cbc3599
4 files changed
+167 -66

No files matched your search

+79 -16
View File
@@ -1,40 +1,103 @@
using System;
using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
namespace PerfectNumbers.Core namespace PerfectNumbers.Core
{ {
public struct IntArrayPool(int poolCapacity) public struct IntArrayPool(int poolCapacity)
{ {
private readonly int[][] _arrays = new int[poolCapacity][]; private const int MinBucketPower = 1;
private int _count = 0; 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)] [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public int[] Rent(int length) public readonly int[] Rent(int length)
{ {
int available = _count; int bucketIndex = GetBucketIndex(length);
if (available == 0) if (bucketIndex < 0)
{ {
return new int[length]; return new int[length];
} }
int[][] arrays = _arrays; int[] counts = _counts;
available--; int available = counts[bucketIndex];
_count = available; int bucketLength = GetBucketLength(bucketIndex);
int[] candidate = arrays[available]; if (available == 0)
return candidate.Length >= length ? candidate : new int[length]; {
return new int[bucketLength];
}
int offset = (bucketIndex * _bucketCapacity) + (available - 1);
counts[bucketIndex] = available - 1;
return _arrays[offset];
} }
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public void Return(int[] array) public readonly void Return(int[] array)
{ {
int[][] arrays = _arrays; int capacity = array.Length;
int available = _count; int bucketIndex = GetBucketIndex(capacity);
if (bucketIndex < 0)
{
return;
}
if (available < arrays.Length) int bucketLength = GetBucketLength(bucketIndex);
if (capacity < bucketLength)
{ {
arrays[available] = array; return;
_count = available + 1;
} }
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;
} }
} }
} }
@@ -11,6 +11,10 @@ namespace PerfectNumbers.Core
public readonly int[] Exponents = new int[PrimeOrderConstants.GpuSmallPrimeFactorSlots]; public readonly int[] Exponents = new int[PrimeOrderConstants.GpuSmallPrimeFactorSlots];
public readonly FixedCapacityStack<ulong> CompositeStack = new (PrimeOrderConstants.GpuSmallPrimeFactorSlots); public readonly FixedCapacityStack<ulong> CompositeStack = new (PrimeOrderConstants.GpuSmallPrimeFactorSlots);
public readonly List<PartialFactorPendingEntry> PendingFactors = new (PrimeOrderConstants.GpuSmallPrimeFactorSlots); public readonly List<PartialFactorPendingEntry> 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; private PartialFactorResult? _next;
public ulong Cofactor; public ulong Cofactor;
public bool FullyFactored; public bool FullyFactored;
@@ -545,12 +545,12 @@ internal static partial class PrimeOrderCalculator
private static ulong ExponentLoweringCpu(ulong order, ulong prime, PartialFactorResult factors, in MontgomeryDivisorData divisorData) private static ulong ExponentLoweringCpu(ulong order, ulong prime, PartialFactorResult factors, in MontgomeryDivisorData divisorData)
{ {
int length = factors.Count; int length = factors.Count;
ReadOnlySpan<ulong> factorSpan = factors.Factors.AsSpan(0, length); ReadOnlySpan<ulong> factorSpan = new(factors.Factors, 0, length);
ReadOnlySpan<int> exponentsSpan = factors.Exponents.AsSpan(0, length); ReadOnlySpan<int> exponentsSpan = new(factors.Exponents, 0, length);
int newLength = length + 1; int newLength = length + 1;
ulong[] tempFactorsArray = FixedCapacityPools.ExclusiveUlongArray.Rent(newLength); ulong[] tempFactorsArray = factors.TempFactorsArray;
int[] tempExponentsArray = FixedCapacityPools.ExclusiveIntArray.Rent(newLength); int[] tempExponentsArray = factors.TempExponentsArray;
Span<ulong> ulongBuffer = tempFactorsArray; Span<ulong> ulongBuffer = tempFactorsArray;
factorSpan.CopyTo(ulongBuffer); factorSpan.CopyTo(ulongBuffer);
@@ -568,14 +568,8 @@ internal static partial class PrimeOrderCalculator
ExponentRemainderStepperCpu stepper = ThreadStaticPools.RentExponentStepperCpu(divisorData); ExponentRemainderStepperCpu stepper = ThreadStaticPools.RentExponentStepperCpu(divisorData);
const int StackExponentCapacity = 16; Span<ulong> stackCandidates = new(factors.StackCandidates);
const int ExponentHardLimit = 256; Span<bool> stackEvaluations = new(factors.StackEvaluations);
Span<ulong> stackCandidates = stackalloc ulong[StackExponentCapacity];
Span<bool> stackEvaluations = stackalloc bool[StackExponentCapacity];
ulong[]? heapCandidateArray = null;
bool[]? heapEvaluationArray = null;
for (int i = 0; i < length; i++) for (int i = 0; i < length; i++)
{ {
@@ -587,31 +581,10 @@ internal static partial class PrimeOrderCalculator
// continue; // 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); 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);
} }
ThreadStaticPools.ReturnExponentStepperCpu(stepper); ThreadStaticPools.ReturnExponentStepperCpu(stepper);
if (heapCandidateArray is not null)
{
FixedCapacityPools.ExclusiveUlongArray.Return(heapCandidateArray);
ThreadStaticPools.BoolPool.Return(heapEvaluationArray!);
}
FixedCapacityPools.ExclusiveUlongArray.Return(tempFactorsArray);
return order; return order;
} }
@@ -1945,4 +1918,3 @@ internal static partial class PrimeOrderCalculator
} }
+78 -16
View File
@@ -1,40 +1,102 @@
using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
namespace PerfectNumbers.Core namespace PerfectNumbers.Core
{ {
public struct UlongArrayPool(int poolCapacity) public struct UlongArrayPool(int poolCapacity)
{ {
private readonly ulong[][] _arrays = new ulong[poolCapacity][]; private const int MinBucketPower = 1;
private int _count = 0; private static readonly int MaxBucketPower = CalculateMaxBucketPower();
private static readonly int MaxBucketLength = 1 << MaxBucketPower;
private static readonly int BucketCount = MaxBucketPower - MinBucketPower + 1;
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)] [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public ulong[] Rent(int length) public readonly ulong[] Rent(int length)
{ {
int available = _count; int bucketIndex = GetBucketIndex(length);
if (available == 0) if (bucketIndex < 0)
{ {
return new ulong[length]; return new ulong[length];
} }
ulong[][] arrays = _arrays; int[] counts = _counts;
available--; int available = counts[bucketIndex];
_count = available; int bucketLength = GetBucketLength(bucketIndex);
ulong[] candidate = arrays[available]; if (available == 0)
return candidate.Length >= length ? candidate : new ulong[length]; {
return new ulong[bucketLength];
}
int offset = (bucketIndex * _bucketCapacity) + (available - 1);
counts[bucketIndex] = available - 1;
return _arrays[offset];
} }
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public void Return(ulong[] array) public readonly void Return(ulong[] array)
{ {
ulong[][] arrays = _arrays; int capacity = array.Length;
int available = _count; int bucketIndex = GetBucketIndex(capacity);
if (bucketIndex < 0)
{
return;
}
if (available < arrays.Length) int bucketLength = GetBucketLength(bucketIndex);
if (capacity < bucketLength)
{ {
arrays[available] = array; return;
_count = available + 1;
} }
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;
} }
} }
} }