Merge pull request #76 from mlemanczyk/codex/review-cache-cycle-generation-and-loading

Fix divisor cycle generation range partitioning
This commit is contained in:
Marek Lemańczyk authored and GitHub committed 2025-09-24 17:22:32 +02:00
commit 3f010ce9ad
2 files changed
+98 -25

No files matched your search

@@ -49,4 +49,57 @@ public class MersenneDivisorCyclesGenerateGpuTests
} }
} }
} }
[Fact]
[Trait("Category", "Fast")]
public void Generate_produces_unique_cycles_for_expected_divisors()
{
string path = Path.GetTempFileName();
try
{
MersenneDivisorCycles.Generate(path, maxDivisor: 50UL, threads: 4);
var pairs = new List<(ulong divisor, ulong cycle)>();
using var stream = new FileStream(path, FileMode.Open, FileAccess.Read);
using var reader = new BinaryReader(stream);
while (stream.Position < stream.Length)
{
ulong divisor = reader.ReadUInt64();
ulong cycle = reader.ReadUInt64();
pairs.Add((divisor, cycle));
}
var expected = new List<ulong>();
for (ulong divisor = 3; divisor <= 50; divisor++)
{
if ((divisor & 1UL) == 0UL || divisor % 3UL == 0UL || divisor % 5UL == 0UL || divisor % 7UL == 0UL || divisor % 11UL == 0UL)
{
continue;
}
expected.Add(divisor);
}
pairs.Should().HaveCount(expected.Count);
HashSet<ulong> uniqueDivisors = new();
foreach (var pair in pairs)
{
uniqueDivisors.Add(pair.divisor).Should().BeTrue();
pair.cycle.Should().Be(MersenneDivisorCycles.CalculateCycleLength(pair.divisor));
}
uniqueDivisors.Should().BeEquivalentTo(expected);
var (_, completeCount) = MersenneDivisorCycles.FindLast(path);
completeCount.Should().Be(expected.Count);
}
finally
{
if (File.Exists(path))
{
File.Delete(path);
}
}
}
} }
+45 -25
View File
@@ -153,8 +153,8 @@ public class MersenneDivisorCycles
public static void Generate(string path, ulong maxDivisor, int threads = 16) public static void Generate(string path, ulong maxDivisor, int threads = 16)
{ {
// Binary-only generation of (divisor, cycle) pairs. CSV scaffolding removed. // Binary-only generation of (divisor, cycle) pairs. CSV scaffolding removed.
ulong start, d = start = 2; ulong start = 2;
ulong blockSize = maxDivisor / (ulong)threads + 1; ulong blockSize = maxDivisor / (ulong)threads + 1UL;
Task[] tasks = new Task[threads]; Task[] tasks = new Task[threads];
using Stream outputStream = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.Read, BufferSize10M, useAsync: false); using Stream outputStream = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.Read, BufferSize10M, useAsync: false);
// Start fresh to avoid mixing formats // Start fresh to avoid mixing formats
@@ -163,48 +163,68 @@ public class MersenneDivisorCycles
using var writer = new BinaryWriter(outputStream, Encoding.UTF8, leaveOpen: false); using var writer = new BinaryWriter(outputStream, Encoding.UTF8, leaveOpen: false);
for (var taskIndex = 0; taskIndex < threads; taskIndex++) for (var taskIndex = 0; taskIndex < threads; taskIndex++)
{ {
Task t = Task.Factory.StartNew((taskIndex) => ulong threadStart = start + (ulong)taskIndex * blockSize;
if (threadStart > maxDivisor)
{ {
ulong cycle, d = start + (ulong)(int)taskIndex! * blockSize; tasks[taskIndex] = Task.CompletedTask;
(ulong d, ulong cycle)[] localCycles = ArrayPool<(ulong, ulong)>.Shared.Rent((int)(maxDivisor - d + 1UL));
int localCycleIndex = 0;
for (; d <= maxDivisor; d++)
{
if ((d & 1UL) == 0UL)
{
// skip even divisors
continue; continue;
} }
cycle = CalculateCycleLength(d); ulong threadEnd = threadStart + blockSize - 1UL;
if (threadEnd > maxDivisor)
localCycles[localCycleIndex++] = (d, cycle); {
threadEnd = maxDivisor;
} }
if (localCycleIndex > 0) ulong localStart = threadStart;
ulong localEnd = threadEnd;
tasks[taskIndex] = Task.Run(() =>
{ {
ulong rangeLength = localEnd - localStart + 1UL;
(ulong divisor, ulong cycle)[] localCycles = ArrayPool<(ulong, ulong)>.Shared.Rent(checked((int)rangeLength));
int localCycleIndex = 0;
try
{
for (ulong divisor = localStart; divisor <= localEnd; divisor++)
{
if ((divisor & 1UL) == 0UL)
{
continue;
}
if ((divisor % 3UL) == 0UL || (divisor % 5UL) == 0UL || (divisor % 7UL) == 0UL || (divisor % 11UL) == 0UL)
{
continue;
}
ulong cycle = CalculateCycleLength(divisor);
localCycles[localCycleIndex++] = (divisor, cycle);
}
if (localCycleIndex == 0)
{
return;
}
lock (writer) lock (writer)
{ {
writer.BaseStream.Position = writer.BaseStream.Length; writer.BaseStream.Position = writer.BaseStream.Length;
for (int i = 0; i < localCycleIndex; i++) for (int i = 0; i < localCycleIndex; i++)
{ {
(d, cycle) = localCycles[i]; (ulong divisor, ulong cycle) = localCycles[i];
if ((d % 3UL) == 0UL || (d % 5UL) == 0UL || (d % 7UL) == 0UL || (d % 11UL) == 0UL) writer.Write(divisor);
continue;
writer.Write(d);
writer.Write(cycle); writer.Write(cycle);
} }
writer.Flush(); writer.Flush();
} }
} }
finally
{
ArrayPool<(ulong, ulong)>.Shared.Return(localCycles, clearArray: false); ArrayPool<(ulong, ulong)>.Shared.Return(localCycles, clearArray: false);
}
}, taskIndex); });
tasks[taskIndex] = t;
} }
Task.WaitAll(tasks); Task.WaitAll(tasks);