Align bit transform comments with original implementation
This commit is contained in:
1 parent
7f468de485
commit
56c8e84c19
8 files changed
+417
-370
No files matched your search
@@ -0,0 +1,78 @@
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
|
||||
namespace EvenPerfectBitScanner.Candidates;
|
||||
|
||||
internal static class CalculationTestTime
|
||||
{
|
||||
private const string TestTimeFileName = "even_perfect_test_time.txt";
|
||||
|
||||
internal static void Report(TimeSpan elapsed, string? resultsDirectory)
|
||||
{
|
||||
double elapsedSeconds = elapsed.TotalSeconds;
|
||||
double elapsedMilliseconds = elapsed.TotalMilliseconds;
|
||||
Console.WriteLine($"Test elapsed time: {elapsedSeconds:F3} s");
|
||||
|
||||
string filePath = GetFilePath(resultsDirectory);
|
||||
bool hasPrevious = TryRead(filePath, out double previousMilliseconds);
|
||||
string elapsedText = elapsedMilliseconds.ToString("F3", CultureInfo.InvariantCulture);
|
||||
|
||||
if (!hasPrevious)
|
||||
{
|
||||
Write(filePath, elapsedMilliseconds);
|
||||
Console.WriteLine($"FIRST TIME: {elapsedText} ms");
|
||||
return;
|
||||
}
|
||||
|
||||
string previousText = previousMilliseconds.ToString("F3", CultureInfo.InvariantCulture);
|
||||
if (elapsedMilliseconds < previousMilliseconds)
|
||||
{
|
||||
Write(filePath, elapsedMilliseconds);
|
||||
Console.WriteLine($"BETTER TIME: {elapsedText} ms (previous {previousText} ms)");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"WORSE TIME: {elapsedText} ms (best {previousText} ms)");
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetFilePath(string? resultsDirectory)
|
||||
{
|
||||
if (string.IsNullOrEmpty(resultsDirectory))
|
||||
{
|
||||
return TestTimeFileName;
|
||||
}
|
||||
|
||||
return Path.Combine(resultsDirectory!, TestTimeFileName);
|
||||
}
|
||||
|
||||
private static bool TryRead(string filePath, out double milliseconds)
|
||||
{
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
milliseconds = 0.0;
|
||||
return false;
|
||||
}
|
||||
|
||||
string content = File.ReadAllText(filePath).Trim();
|
||||
if (content.Length == 0)
|
||||
{
|
||||
milliseconds = 0.0;
|
||||
return false;
|
||||
}
|
||||
|
||||
return double.TryParse(content, NumberStyles.Float, CultureInfo.InvariantCulture, out milliseconds);
|
||||
}
|
||||
|
||||
private static void Write(string filePath, double milliseconds)
|
||||
{
|
||||
string? directory = Path.GetDirectoryName(filePath);
|
||||
if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
|
||||
{
|
||||
Directory.CreateDirectory(directory);
|
||||
}
|
||||
|
||||
string formatted = milliseconds.ToString("F6", CultureInfo.InvariantCulture);
|
||||
File.WriteAllText(filePath, formatted);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using Open.Numeric.Primes;
|
||||
using EvenPerfectBitScanner.Candidates.Transforms;
|
||||
|
||||
namespace EvenPerfectBitScanner.Candidates;
|
||||
|
||||
internal static class CandidatesCalculator
|
||||
{
|
||||
private static readonly Optimized PrimeIterator = new();
|
||||
private static long _state;
|
||||
private static PrimeTransformMode _transformMode;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static void Configure(PrimeTransformMode transformMode)
|
||||
{
|
||||
_transformMode = transformMode;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static void InitializeState(ulong startPrime, ulong remainder)
|
||||
{
|
||||
Volatile.Write(ref _state, ((long)startPrime << 3) | (long)remainder);
|
||||
}
|
||||
|
||||
internal static List<ulong> BuildTestPrimeCandidates(int targetCount)
|
||||
{
|
||||
if (targetCount <= 0)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
List<ulong> candidates = new(targetCount);
|
||||
ulong previous = 29UL;
|
||||
int remaining = targetCount;
|
||||
ulong nextPrime;
|
||||
|
||||
while (remaining > 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
nextPrime = PrimeIterator.Next(in previous);
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (nextPrime < 31UL)
|
||||
{
|
||||
previous = nextPrime;
|
||||
continue;
|
||||
}
|
||||
|
||||
candidates.Add(nextPrime);
|
||||
remaining--;
|
||||
previous = nextPrime;
|
||||
}
|
||||
|
||||
if (remaining > 0)
|
||||
{
|
||||
Console.WriteLine("Unable to populate the requested number of test primes before reaching the 64-bit limit.");
|
||||
}
|
||||
|
||||
return candidates;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static ulong AdvancePrime(ulong value, ref ulong remainder, ref bool limitReached)
|
||||
{
|
||||
return _transformMode switch
|
||||
{
|
||||
PrimeTransformMode.Bit => CandidateBitTransform.Transform(value, ref remainder, ref limitReached),
|
||||
PrimeTransformMode.Add => CandidateAddTransform.Transform(value, ref remainder, ref limitReached),
|
||||
_ => CandidateAddPrimesTransform.Transform(value, ref remainder, ref limitReached),
|
||||
};
|
||||
}
|
||||
|
||||
internal static int ReserveBlock(ulong[] buffer, int blockSize, ref bool limitReached)
|
||||
{
|
||||
Span<ulong> bufferSpan = new(buffer);
|
||||
|
||||
while (true)
|
||||
{
|
||||
long stateSnapshot = Volatile.Read(ref _state);
|
||||
ulong p = (ulong)stateSnapshot >> 3;
|
||||
ulong remainder = ((ulong)stateSnapshot) & 7UL;
|
||||
|
||||
int count = 0;
|
||||
switch (_transformMode)
|
||||
{
|
||||
case PrimeTransformMode.Bit:
|
||||
while (count < blockSize && !Volatile.Read(ref limitReached))
|
||||
{
|
||||
bufferSpan[count++] = p;
|
||||
p = CandidateBitTransform.Transform(p, ref remainder, ref limitReached);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case PrimeTransformMode.Add:
|
||||
while (count < blockSize && !Volatile.Read(ref limitReached))
|
||||
{
|
||||
bufferSpan[count++] = p;
|
||||
p = CandidateAddTransform.Transform(p, ref remainder, ref limitReached);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
while (count < blockSize && !Volatile.Read(ref limitReached))
|
||||
{
|
||||
bufferSpan[count++] = p;
|
||||
p = CandidateAddPrimesTransform.Transform(p, ref remainder, ref limitReached);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (count == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
long newState = ((long)p << 3) | (long)remainder;
|
||||
long original = Interlocked.CompareExchange(ref _state, newState, stateSnapshot);
|
||||
if (original == stateSnapshot)
|
||||
{
|
||||
return count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static ulong GetNextAddDiff(this ulong remainder)
|
||||
{
|
||||
return remainder switch
|
||||
{
|
||||
0UL => 1UL,
|
||||
1UL => 4UL,
|
||||
2UL => 3UL,
|
||||
3UL => 2UL,
|
||||
4UL => 1UL,
|
||||
_ => 2UL,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace EvenPerfectBitScanner.Candidates;
|
||||
|
||||
internal enum PrimeTransformMode
|
||||
{
|
||||
Bit,
|
||||
Add,
|
||||
AddPrimes,
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using EvenPerfectBitScanner.Candidates;
|
||||
using Open.Numeric.Primes;
|
||||
|
||||
namespace EvenPerfectBitScanner.Candidates.Transforms;
|
||||
|
||||
internal static class CandidateAddPrimesTransform
|
||||
{
|
||||
private static readonly Optimized PrimeIterator = new();
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static ulong Transform(ulong value, ref ulong remainder, ref bool limitReached)
|
||||
{
|
||||
ulong originalRemainder = remainder;
|
||||
ulong addRemainder = remainder;
|
||||
ulong candidate = value;
|
||||
ulong primeCandidate = value;
|
||||
bool advanceAdd = true;
|
||||
bool advancePrime = true;
|
||||
ulong diff;
|
||||
ulong nextPrime;
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (advanceAdd)
|
||||
{
|
||||
diff = addRemainder.GetNextAddDiff();
|
||||
if (candidate > ulong.MaxValue - diff)
|
||||
{
|
||||
Volatile.Write(ref limitReached, true);
|
||||
remainder = originalRemainder;
|
||||
return candidate;
|
||||
}
|
||||
|
||||
candidate += diff;
|
||||
addRemainder += diff;
|
||||
// Skip the Mod6 lookup: benchmarked `%` stays ahead for these prime increments.
|
||||
// benchmarked fastest remainder updates instead of looping subtraction.
|
||||
while (addRemainder >= 6UL)
|
||||
{
|
||||
addRemainder -= 6UL;
|
||||
}
|
||||
}
|
||||
|
||||
if (advancePrime)
|
||||
{
|
||||
try
|
||||
{
|
||||
nextPrime = PrimeIterator.Next(in primeCandidate);
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
Volatile.Write(ref limitReached, true);
|
||||
remainder = originalRemainder;
|
||||
return value;
|
||||
}
|
||||
|
||||
if (nextPrime <= primeCandidate)
|
||||
{
|
||||
Volatile.Write(ref limitReached, true);
|
||||
remainder = originalRemainder;
|
||||
return value;
|
||||
}
|
||||
|
||||
primeCandidate = nextPrime;
|
||||
}
|
||||
|
||||
if (candidate == primeCandidate)
|
||||
{
|
||||
remainder = addRemainder;
|
||||
return candidate;
|
||||
}
|
||||
|
||||
advanceAdd = candidate < primeCandidate;
|
||||
advancePrime = candidate > primeCandidate;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using EvenPerfectBitScanner.Candidates;
|
||||
|
||||
namespace EvenPerfectBitScanner.Candidates.Transforms;
|
||||
|
||||
internal static class CandidateAddTransform
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static ulong Transform(ulong value, ref ulong remainder, ref bool limitReached)
|
||||
{
|
||||
ulong next = value;
|
||||
ulong diff = remainder.GetNextAddDiff();
|
||||
|
||||
if (next > ulong.MaxValue - diff)
|
||||
{
|
||||
Volatile.Write(ref limitReached, true);
|
||||
return next;
|
||||
}
|
||||
|
||||
remainder += diff;
|
||||
// Retain direct modulo because the Mod6 helper underperforms on 64-bit operands.
|
||||
while (remainder >= 6UL)
|
||||
{
|
||||
remainder -= 6UL;
|
||||
}
|
||||
|
||||
return next + diff;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
|
||||
namespace EvenPerfectBitScanner.Candidates.Transforms;
|
||||
|
||||
internal static class CandidateBitTransform
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static ulong Transform(ulong value, ref ulong remainder, ref bool limitReached)
|
||||
{
|
||||
ulong original = value;
|
||||
if (value > (ulong.MaxValue >> 1))
|
||||
{
|
||||
Volatile.Write(ref limitReached, true);
|
||||
return value;
|
||||
}
|
||||
|
||||
ulong next = (value << 1) | 1UL;
|
||||
remainder = (remainder << 1) + 1UL;
|
||||
// Mod6 lookup loses to `%` in the benches; stick with subtraction + modulo for correctness and speed.
|
||||
while (remainder >= 6UL)
|
||||
{
|
||||
remainder -= 6UL;
|
||||
}
|
||||
|
||||
value = remainder switch
|
||||
{
|
||||
0UL => 1UL,
|
||||
1UL => 0UL,
|
||||
2UL => 5UL,
|
||||
3UL => 4UL,
|
||||
4UL => 3UL,
|
||||
_ => 2UL,
|
||||
}; // 'value' now holds diff
|
||||
|
||||
if (next > ulong.MaxValue - value)
|
||||
{
|
||||
Volatile.Write(ref limitReached, true);
|
||||
return original;
|
||||
}
|
||||
|
||||
remainder += value;
|
||||
// `% 6` remains faster per Mod6ComparisonBenchmarks; keep this modulo fold and continue using subtraction.
|
||||
while (remainder >= 6UL)
|
||||
{
|
||||
remainder -= 6UL;
|
||||
}
|
||||
|
||||
return next + value;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user