Hybrid device mode implemented as the current --mersenne=bydivisor CPU execution path. CPU path is now limited to CPU calculations, only.
This commit is contained in:
1 parent
353a121d03
commit
941733905b
49 files changed
+4965
-1726
No files matched your search
@@ -39,12 +39,9 @@ public sealed class ModResidueTracker
|
||||
/// (number must be non-decreasing across calls). Adds and initializes new
|
||||
/// divisors on first use.
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool IsDivisible(PrimeOrderCalculatorAccelerator gpu, UInt128 number, UInt128 divisor)
|
||||
public bool IsDivisibleCpu(UInt128 number, UInt128 divisor)
|
||||
{
|
||||
if (divisor == UInt128.Zero)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(divisor));
|
||||
}
|
||||
ArgumentOutOfRangeException.ThrowIfEqual(divisor, UInt128.Zero);
|
||||
|
||||
EnsureAt(number);
|
||||
|
||||
@@ -52,7 +49,75 @@ public sealed class ModResidueTracker
|
||||
int idx = LowerBound(divisors, divisor);
|
||||
if (idx == divisors.Count || divisors[idx] != divisor)
|
||||
{
|
||||
UInt128 residue = ComputeInitialResidue(gpu, number, divisor);
|
||||
UInt128 residue = ComputeInitialResidueCpu(number, divisor);
|
||||
divisors.Insert(idx, divisor);
|
||||
_residues.Insert(idx, residue);
|
||||
if (_hasLastAppended && idx == divisors.Count - 1 && divisor >= _lastAppended)
|
||||
{
|
||||
_lastAppended = divisor;
|
||||
}
|
||||
else if (!_hasLastAppended)
|
||||
{
|
||||
_hasLastAppended = true;
|
||||
_lastAppended = divisor;
|
||||
}
|
||||
|
||||
return residue == UInt128.Zero;
|
||||
}
|
||||
|
||||
return _residues[idx] == UInt128.Zero;
|
||||
}
|
||||
|
||||
/// Returns true if the provided number (either p or M_p depending on model)
|
||||
/// is divisible by the divisor. Advances internal state forward if needed
|
||||
/// (number must be non-decreasing across calls). Adds and initializes new
|
||||
/// divisors on first use.
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool IsDivisibleGpu(PrimeOrderCalculatorAccelerator gpu, UInt128 number, UInt128 divisor)
|
||||
{
|
||||
ArgumentOutOfRangeException.ThrowIfEqual(divisor, UInt128.Zero);
|
||||
|
||||
EnsureAt(number);
|
||||
|
||||
List<UInt128> divisors = _divisors;
|
||||
int idx = LowerBound(divisors, divisor);
|
||||
if (idx == divisors.Count || divisors[idx] != divisor)
|
||||
{
|
||||
UInt128 residue = ComputeInitialResidueGpu(gpu, number, divisor);
|
||||
divisors.Insert(idx, divisor);
|
||||
_residues.Insert(idx, residue);
|
||||
if (_hasLastAppended && idx == divisors.Count - 1 && divisor >= _lastAppended)
|
||||
{
|
||||
_lastAppended = divisor;
|
||||
}
|
||||
else if (!_hasLastAppended)
|
||||
{
|
||||
_hasLastAppended = true;
|
||||
_lastAppended = divisor;
|
||||
}
|
||||
|
||||
return residue == UInt128.Zero;
|
||||
}
|
||||
|
||||
return _residues[idx] == UInt128.Zero;
|
||||
}
|
||||
|
||||
/// Returns true if the provided number (either p or M_p depending on model)
|
||||
/// is divisible by the divisor. Advances internal state forward if needed
|
||||
/// (number must be non-decreasing across calls). Adds and initializes new
|
||||
/// divisors on first use.
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool IsDivisibleHybrid(PrimeOrderCalculatorAccelerator gpu, UInt128 number, UInt128 divisor)
|
||||
{
|
||||
ArgumentOutOfRangeException.ThrowIfEqual(divisor, UInt128.Zero);
|
||||
|
||||
EnsureAt(number);
|
||||
|
||||
List<UInt128> divisors = _divisors;
|
||||
int idx = LowerBound(divisors, divisor);
|
||||
if (idx == divisors.Count || divisors[idx] != divisor)
|
||||
{
|
||||
UInt128 residue = ComputeInitialResidueHybrid(gpu, number, divisor);
|
||||
divisors.Insert(idx, divisor);
|
||||
_residues.Insert(idx, residue);
|
||||
if (_hasLastAppended && idx == divisors.Count - 1 && divisor >= _lastAppended)
|
||||
@@ -81,7 +146,7 @@ public sealed class ModResidueTracker
|
||||
// Merge or append current candidate divisor (ascending order required across calls).
|
||||
// Returns true and sets 'divisible' based on residue at 'number'.
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool MergeOrAppend(PrimeOrderCalculatorAccelerator gpu, UInt128 number, UInt128 divisor, out bool divisible)
|
||||
public bool MergeOrAppendCpu(UInt128 number, UInt128 divisor, out bool divisible)
|
||||
{
|
||||
// Assume number is non-decreasing; advance residues lazily when number changes.
|
||||
List<UInt128> divisors = _divisors;
|
||||
@@ -97,7 +162,7 @@ public sealed class ModResidueTracker
|
||||
List<UInt128> residues = _residues;
|
||||
if (divisors.Count == 0 || (_hasLastAppended && divisor >= _lastAppended && divisors[^1] <= divisor))
|
||||
{
|
||||
residue = ComputeInitialResidue(gpu, number, divisor);
|
||||
residue = ComputeInitialResidueCpu(number, divisor);
|
||||
divisors.Add(divisor);
|
||||
residues.Add(residue);
|
||||
_hasLastAppended = true;
|
||||
@@ -123,7 +188,129 @@ public sealed class ModResidueTracker
|
||||
|
||||
|
||||
// Insert at cursor to keep order
|
||||
residue = ComputeInitialResidue(gpu, number, divisor);
|
||||
residue = ComputeInitialResidueCpu(number, divisor);
|
||||
divisors.Insert(cursor, divisor);
|
||||
residues.Insert(cursor, residue);
|
||||
divisible = residue == UInt128.Zero;
|
||||
_cursor = cursor + 1;
|
||||
|
||||
// update last appended hint
|
||||
if (!_hasLastAppended || divisor >= _lastAppended)
|
||||
{
|
||||
_hasLastAppended = true;
|
||||
_lastAppended = divisor;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Merge or append current candidate divisor (ascending order required across calls).
|
||||
// Returns true and sets 'divisible' based on residue at 'number'.
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool MergeOrAppendGpu(PrimeOrderCalculatorAccelerator gpu, UInt128 number, UInt128 divisor, out bool divisible)
|
||||
{
|
||||
// Assume number is non-decreasing; advance residues lazily when number changes.
|
||||
List<UInt128> divisors = _divisors;
|
||||
if (number != _currentNumber)
|
||||
{
|
||||
EnsureAt(number);
|
||||
// After EnsureAt we can keep _cursor (still valid monotonic pointer)
|
||||
if (_cursor > divisors.Count) _cursor = divisors.Count;
|
||||
}
|
||||
|
||||
UInt128 residue;
|
||||
// Fast path: append to tail when candidates are strictly increasing.
|
||||
List<UInt128> residues = _residues;
|
||||
if (divisors.Count == 0 || (_hasLastAppended && divisor >= _lastAppended && divisors[^1] <= divisor))
|
||||
{
|
||||
residue = ComputeInitialResidueGpu(gpu, number, divisor);
|
||||
divisors.Add(divisor);
|
||||
residues.Add(residue);
|
||||
_hasLastAppended = true;
|
||||
_lastAppended = divisor;
|
||||
_cursor = divisors.Count; // position after last
|
||||
divisible = residue == UInt128.Zero;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Merge pointer: advance until current >= divisor
|
||||
var cursor = _cursor;
|
||||
while (cursor < divisors.Count && divisors[cursor] < divisor)
|
||||
{
|
||||
cursor++;
|
||||
}
|
||||
|
||||
if (cursor < divisors.Count && divisors[cursor] == divisor)
|
||||
{
|
||||
divisible = residues[cursor] == UInt128.Zero;
|
||||
_cursor = cursor + 1; // position after matched
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// Insert at cursor to keep order
|
||||
residue = ComputeInitialResidueGpu(gpu, number, divisor);
|
||||
divisors.Insert(cursor, divisor);
|
||||
residues.Insert(cursor, residue);
|
||||
divisible = residue == UInt128.Zero;
|
||||
_cursor = cursor + 1;
|
||||
|
||||
// update last appended hint
|
||||
if (!_hasLastAppended || divisor >= _lastAppended)
|
||||
{
|
||||
_hasLastAppended = true;
|
||||
_lastAppended = divisor;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Merge or append current candidate divisor (ascending order required across calls).
|
||||
// Returns true and sets 'divisible' based on residue at 'number'.
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool MergeOrAppendHybrid(PrimeOrderCalculatorAccelerator gpu, UInt128 number, UInt128 divisor, out bool divisible)
|
||||
{
|
||||
// Assume number is non-decreasing; advance residues lazily when number changes.
|
||||
List<UInt128> divisors = _divisors;
|
||||
if (number != _currentNumber)
|
||||
{
|
||||
EnsureAt(number);
|
||||
// After EnsureAt we can keep _cursor (still valid monotonic pointer)
|
||||
if (_cursor > divisors.Count) _cursor = divisors.Count;
|
||||
}
|
||||
|
||||
UInt128 residue;
|
||||
// Fast path: append to tail when candidates are strictly increasing.
|
||||
List<UInt128> residues = _residues;
|
||||
if (divisors.Count == 0 || (_hasLastAppended && divisor >= _lastAppended && divisors[^1] <= divisor))
|
||||
{
|
||||
residue = ComputeInitialResidueHybrid(gpu, number, divisor);
|
||||
divisors.Add(divisor);
|
||||
residues.Add(residue);
|
||||
_hasLastAppended = true;
|
||||
_lastAppended = divisor;
|
||||
_cursor = divisors.Count; // position after last
|
||||
divisible = residue == UInt128.Zero;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Merge pointer: advance until current >= divisor
|
||||
var cursor = _cursor;
|
||||
while (cursor < divisors.Count && divisors[cursor] < divisor)
|
||||
{
|
||||
cursor++;
|
||||
}
|
||||
|
||||
if (cursor < divisors.Count && divisors[cursor] == divisor)
|
||||
{
|
||||
divisible = residues[cursor] == UInt128.Zero;
|
||||
_cursor = cursor + 1; // position after matched
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// Insert at cursor to keep order
|
||||
residue = ComputeInitialResidueHybrid(gpu, number, divisor);
|
||||
divisors.Insert(cursor, divisor);
|
||||
residues.Insert(cursor, residue);
|
||||
divisible = residue == UInt128.Zero;
|
||||
@@ -238,7 +425,7 @@ public sealed class ModResidueTracker
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private UInt128 ComputeInitialResidue(PrimeOrderCalculatorAccelerator gpu, UInt128 number, UInt128 divisor)
|
||||
private UInt128 ComputeInitialResidueCpu(UInt128 number, UInt128 divisor)
|
||||
{
|
||||
if (_model == ResidueModel.Identity)
|
||||
{
|
||||
@@ -266,12 +453,82 @@ public sealed class ModResidueTracker
|
||||
if (pow != UInt128.One)
|
||||
return (pow - UInt128.One) % divisor;
|
||||
|
||||
ulong ord = divisor.CalculateOrder(gpu);
|
||||
ulong ord = divisor.CalculateOrderCpu();
|
||||
if (ord == 0UL || number % (UInt128)ord != UInt128.Zero)
|
||||
return UInt128.One; // does NOT divide
|
||||
|
||||
return UInt128.Zero; // q really divides M_p
|
||||
}
|
||||
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private UInt128 ComputeInitialResidueGpu(PrimeOrderCalculatorAccelerator gpu, UInt128 number, UInt128 divisor)
|
||||
{
|
||||
if (_model == ResidueModel.Identity)
|
||||
{
|
||||
// Fast path for 64-bit inputs
|
||||
if (number <= ulong.MaxValue && divisor <= ulong.MaxValue)
|
||||
{
|
||||
// TODO: Swap this modulo fallback for the shared divisor-cycle remainder helper so 64-bit
|
||||
// residues reuse cached cycles instead of recomputing `%` on every lookup.
|
||||
return (ulong)number % (ulong)divisor;
|
||||
}
|
||||
|
||||
// TODO: Use the Montgomery folding helper measured faster in the Pow2Montgomery benchmarks so this
|
||||
// wide residue path avoids general `%` on UInt128 inputs.
|
||||
return number % divisor;
|
||||
}
|
||||
|
||||
// residue of Mersenne number M_number = 2^number - 1 modulo divisor
|
||||
// TODO: Consult MersenneDivisorCycles.Shared (or similar caches) here so we reuse precomputed cycle lengths
|
||||
// instead of recomputing powmods for every divisor; the divisor-cycle benchmarks showed large wins once the
|
||||
// cached orders were used across scans, and a miss should trigger the configured device to compute the cycle
|
||||
// immediately without storing it back or requesting additional cache blocks.
|
||||
UInt128 pow = number.Pow2MontgomeryModWindowed(divisor);
|
||||
|
||||
// q divides M_p ⇔ 2^p ≡ 1 (mod q) AND ord_q(2) | p
|
||||
if (pow != UInt128.One)
|
||||
return (pow - UInt128.One) % divisor;
|
||||
|
||||
ulong ord = divisor.CalculateOrderGpu(gpu);
|
||||
if (ord == 0UL || number % (UInt128)ord != UInt128.Zero)
|
||||
return UInt128.One; // does NOT divide
|
||||
|
||||
return UInt128.Zero; // q really divides M_p
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private UInt128 ComputeInitialResidueHybrid(PrimeOrderCalculatorAccelerator gpu, UInt128 number, UInt128 divisor)
|
||||
{
|
||||
if (_model == ResidueModel.Identity)
|
||||
{
|
||||
// Fast path for 64-bit inputs
|
||||
if (number <= ulong.MaxValue && divisor <= ulong.MaxValue)
|
||||
{
|
||||
// TODO: Swap this modulo fallback for the shared divisor-cycle remainder helper so 64-bit
|
||||
// residues reuse cached cycles instead of recomputing `%` on every lookup.
|
||||
return (ulong)number % (ulong)divisor;
|
||||
}
|
||||
|
||||
// TODO: Use the Montgomery folding helper measured faster in the Pow2Montgomery benchmarks so this
|
||||
// wide residue path avoids general `%` on UInt128 inputs.
|
||||
return number % divisor;
|
||||
}
|
||||
|
||||
// residue of Mersenne number M_number = 2^number - 1 modulo divisor
|
||||
// TODO: Consult MersenneDivisorCycles.Shared (or similar caches) here so we reuse precomputed cycle lengths
|
||||
// instead of recomputing powmods for every divisor; the divisor-cycle benchmarks showed large wins once the
|
||||
// cached orders were used across scans, and a miss should trigger the configured device to compute the cycle
|
||||
// immediately without storing it back or requesting additional cache blocks.
|
||||
UInt128 pow = number.Pow2MontgomeryModWindowed(divisor);
|
||||
|
||||
// q divides M_p ⇔ 2^p ≡ 1 (mod q) AND ord_q(2) | p
|
||||
if (pow != UInt128.One)
|
||||
return (pow - UInt128.One) % divisor;
|
||||
|
||||
ulong ord = divisor.CalculateOrderHybrid(gpu);
|
||||
if (ord == 0UL || number % (UInt128)ord != UInt128.Zero)
|
||||
return UInt128.One; // does NOT divide
|
||||
|
||||
return UInt128.Zero; // q really divides M_p
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user