diff --git a/PerfectNumbers.Core/Gpu/DivisorGeneratorGpuCache.cs b/PerfectNumbers.Core/Gpu/DivisorGeneratorGpuCache.cs new file mode 100644 index 0000000..9236270 --- /dev/null +++ b/PerfectNumbers.Core/Gpu/DivisorGeneratorGpuCache.cs @@ -0,0 +1,142 @@ +using System; +using System.Collections.Concurrent; +using System.Runtime.CompilerServices; +using ILGPU; +using ILGPU.Runtime; + +namespace PerfectNumbers.Core.Gpu; + +internal static class DivisorGeneratorGpuCache +{ + private sealed class AcceleratorReferenceComparer : IEqualityComparer + { + public static AcceleratorReferenceComparer Instance { get; } = new(); + + public bool Equals(Accelerator? x, Accelerator? y) + { + return ReferenceEquals(x, y); + } + + public int GetHashCode(Accelerator obj) + { + return RuntimeHelpers.GetHashCode(obj); + } + } + + private sealed class DivisorGeneratorGpuTables : IDisposable + { + internal DivisorGeneratorGpuTables(Accelerator accelerator) + { + SmallPrimes = AllocateAndCopyUInt(accelerator, DivisorGenerator.SmallPrimes); + SmallPrimesPow2 = AllocateAndCopyULong(accelerator, DivisorGenerator.SmallPrimesPow2); + SmallPrimesLastOne = AllocateAndCopyUInt(accelerator, DivisorGenerator.SmallPrimesLastOne); + SmallPrimesPow2LastOne = AllocateAndCopyULong(accelerator, DivisorGenerator.SmallPrimesPow2LastOne); + SmallPrimesLastSeven = AllocateAndCopyUInt(accelerator, DivisorGenerator.SmallPrimesLastSeven); + SmallPrimesPow2LastSeven = AllocateAndCopyULong(accelerator, DivisorGenerator.SmallPrimesPow2LastSeven); + SmallPrimesLastThree = AllocateAndCopyUInt(accelerator, DivisorGenerator.SmallPrimesLastThree); + SmallPrimesPow2LastThree = AllocateAndCopyULong(accelerator, DivisorGenerator.SmallPrimesPow2LastThree); + SmallPrimesLastNine = AllocateAndCopyUInt(accelerator, DivisorGenerator.SmallPrimesLastNine); + SmallPrimesPow2LastNine = AllocateAndCopyULong(accelerator, DivisorGenerator.SmallPrimesPow2LastNine); + SmallPrimesLastOneWithoutLastThree = AllocateAndCopyUInt(accelerator, DivisorGenerator.SmallPrimesLastOneWithoutLastThree); + SmallPrimesPow2LastOneWithoutLastThree = AllocateAndCopyULong(accelerator, DivisorGenerator.SmallPrimesPow2LastOneWithoutLastThree); + SmallPrimesLastSevenWithoutLastThree = AllocateAndCopyUInt(accelerator, DivisorGenerator.SmallPrimesLastSevenWithoutLastThree); + SmallPrimesPow2LastSevenWithoutLastThree = AllocateAndCopyULong(accelerator, DivisorGenerator.SmallPrimesPow2LastSevenWithoutLastThree); + SmallPrimesLastNineWithoutLastThree = AllocateAndCopyUInt(accelerator, DivisorGenerator.SmallPrimesLastNineWithoutLastThree); + SmallPrimesPow2LastNineWithoutLastThree = AllocateAndCopyULong(accelerator, DivisorGenerator.SmallPrimesPow2LastNineWithoutLastThree); + } + + internal MemoryBuffer1D SmallPrimes { get; } + internal MemoryBuffer1D SmallPrimesPow2 { get; } + internal MemoryBuffer1D SmallPrimesLastOne { get; } + internal MemoryBuffer1D SmallPrimesPow2LastOne { get; } + internal MemoryBuffer1D SmallPrimesLastSeven { get; } + internal MemoryBuffer1D SmallPrimesPow2LastSeven { get; } + internal MemoryBuffer1D SmallPrimesLastThree { get; } + internal MemoryBuffer1D SmallPrimesPow2LastThree { get; } + internal MemoryBuffer1D SmallPrimesLastNine { get; } + internal MemoryBuffer1D SmallPrimesPow2LastNine { get; } + internal MemoryBuffer1D SmallPrimesLastOneWithoutLastThree { get; } + internal MemoryBuffer1D SmallPrimesPow2LastOneWithoutLastThree { get; } + internal MemoryBuffer1D SmallPrimesLastSevenWithoutLastThree { get; } + internal MemoryBuffer1D SmallPrimesPow2LastSevenWithoutLastThree { get; } + internal MemoryBuffer1D SmallPrimesLastNineWithoutLastThree { get; } + internal MemoryBuffer1D SmallPrimesPow2LastNineWithoutLastThree { get; } + + public void Dispose() + { + SmallPrimes.Dispose(); + SmallPrimesPow2.Dispose(); + SmallPrimesLastOne.Dispose(); + SmallPrimesPow2LastOne.Dispose(); + SmallPrimesLastSeven.Dispose(); + SmallPrimesPow2LastSeven.Dispose(); + SmallPrimesLastThree.Dispose(); + SmallPrimesPow2LastThree.Dispose(); + SmallPrimesLastNine.Dispose(); + SmallPrimesPow2LastNine.Dispose(); + SmallPrimesLastOneWithoutLastThree.Dispose(); + SmallPrimesPow2LastOneWithoutLastThree.Dispose(); + SmallPrimesLastSevenWithoutLastThree.Dispose(); + SmallPrimesPow2LastSevenWithoutLastThree.Dispose(); + SmallPrimesLastNineWithoutLastThree.Dispose(); + SmallPrimesPow2LastNineWithoutLastThree.Dispose(); + } + + private static MemoryBuffer1D AllocateAndCopyUInt(Accelerator accelerator, uint[] source) + { + var buffer = accelerator.Allocate1D(source.Length); + if (source.Length > 0) + { + buffer.View.CopyFromCPU(source); + } + + return buffer; + } + + private static MemoryBuffer1D AllocateAndCopyULong(Accelerator accelerator, ulong[] source) + { + var buffer = accelerator.Allocate1D(source.Length); + if (source.Length > 0) + { + buffer.View.CopyFromCPU(source); + } + + return buffer; + } + } + + private static readonly ConcurrentDictionary Cache = new(AcceleratorReferenceComparer.Instance); + + public static void WarmUpByDivisorTables(Accelerator accelerator) + { + if (accelerator is null || accelerator.AcceleratorType == AcceleratorType.CPU) + { + return; + } + + Cache.GetOrAdd(accelerator, acc => new DivisorGeneratorGpuTables(acc)); + } + + public static void Clear(Accelerator accelerator) + { + if (accelerator is null) + { + return; + } + + if (Cache.TryRemove(accelerator, out var tables)) + { + tables.Dispose(); + } + } + + public static void DisposeAll() + { + foreach (var entry in Cache) + { + entry.Value.Dispose(); + } + + Cache.Clear(); + } +} diff --git a/PerfectNumbers.Core/Gpu/GpuContextPool.cs b/PerfectNumbers.Core/Gpu/GpuContextPool.cs index 730f365..53906c7 100644 --- a/PerfectNumbers.Core/Gpu/GpuContextPool.cs +++ b/PerfectNumbers.Core/Gpu/GpuContextPool.cs @@ -24,6 +24,10 @@ public static class GpuContextPool Context = Context.CreateDefault(); Accelerator = Context.GetPreferredDevice(preferCpu).CreateAccelerator(Context); IsCpu = Accelerator.AcceleratorType == AcceleratorType.CPU; + if (!IsCpu) + { + DivisorGeneratorGpuCache.WarmUpByDivisorTables(Accelerator); + } // NOTE: Avoid loading/compiling any kernel here to prevent implicit // CL stream/queue creation during accelerator construction. // Some OpenCL drivers are fragile when a queue is created immediately @@ -38,6 +42,7 @@ public static class GpuContextPool // Release PrimeTester GPU state for this accelerator (kernel/device primes) // Clear any per-accelerator cached resources to avoid releasing // after the accelerator is destroyed. + DivisorGeneratorGpuCache.Clear(Accelerator); PrimeTester.ClearGpuCaches(Accelerator); Accelerator.Dispose(); @@ -117,6 +122,7 @@ public static class GpuContextPool gpu.Dispose(); } + DivisorGeneratorGpuCache.DisposeAll(); PrimeTester.DisposeGpuContexts(); WarmedGpuContextCount = 0; }