From 38a96535cafd75af5877b41686b513bee75bb934 Mon Sep 17 00:00:00 2001 From: konard Date: Sun, 14 Sep 2025 08:23:21 +0300 Subject: [PATCH 1/3] Initial commit with task details for issue #34 Adding CLAUDE.md with task information for AI processing. This file will be removed when the task is complete. Issue: https://github.com/linksplatform/Unsafe/issues/34 --- CLAUDE.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..05f1f2f --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,5 @@ +Issue to solve: https://github.com/linksplatform/Unsafe/issues/34 +Your prepared branch: issue-34-952798a7 +Your prepared working directory: /tmp/gh-issue-solver-1757827397826 + +Proceed. \ No newline at end of file From dd98460ae0503624a9cb396678eca9187442617d Mon Sep 17 00:00:00 2001 From: konard Date: Sun, 14 Sep 2025 08:34:07 +0300 Subject: [PATCH 2/3] Implement automatic memory channel detection for optimized parallelization MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This enhancement addresses issue #34 by implementing dynamic detection of memory channels instead of hardcoding the thread count to 2 for memory zeroing operations. Key changes: - Added MemoryChannelCount property to detect system memory channels - Uses WMI Win32_PhysicalMemory on Windows to query InterleaveDataDepth - Falls back to estimating based on memory device count if WMI data unavailable - Maintains backward compatibility with 2-channel default for non-Windows platforms - Respects processor count limitations to avoid resource waste - Added comprehensive unit tests for the new functionality - Added benchmark for memory channel detection performance Performance benefits: - Optimizes thread count based on actual memory architecture - Better utilizes memory bandwidth on systems with more memory channels - Maintains efficient operation on single/dual-channel systems 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .../MemoryBlockBenchmarks.cs | 3 + .../MemoryChannelTests.cs | 79 ++++++++++++++++++ csharp/Platform.Unsafe/MemoryBlock.cs | 83 +++++++++++++++++-- csharp/Platform.Unsafe/Platform.Unsafe.csproj | 1 + 4 files changed, 161 insertions(+), 5 deletions(-) create mode 100644 csharp/Platform.Unsafe.Tests/MemoryChannelTests.cs diff --git a/csharp/Platform.Unsafe.Benchmarks/MemoryBlockBenchmarks.cs b/csharp/Platform.Unsafe.Benchmarks/MemoryBlockBenchmarks.cs index f86bcd2..afdc4e6 100644 --- a/csharp/Platform.Unsafe.Benchmarks/MemoryBlockBenchmarks.cs +++ b/csharp/Platform.Unsafe.Benchmarks/MemoryBlockBenchmarks.cs @@ -44,5 +44,8 @@ public void MemoryBlockZero() MemoryBlock.Zero(pointer, _array.Length); } } + + [Benchmark] + public void GetMemoryChannelCount() => _ = MemoryBlock.MemoryChannelCount; } } diff --git a/csharp/Platform.Unsafe.Tests/MemoryChannelTests.cs b/csharp/Platform.Unsafe.Tests/MemoryChannelTests.cs new file mode 100644 index 0000000..58bc4bc --- /dev/null +++ b/csharp/Platform.Unsafe.Tests/MemoryChannelTests.cs @@ -0,0 +1,79 @@ +using System; +using System.Runtime.InteropServices; +using Xunit; + +namespace Platform.Unsafe.Tests +{ + public static class MemoryChannelTests + { + [Fact] + public static void MemoryChannelCountIsValid() + { + // The detected memory channel count should be at least 1 and reasonable + var channelCount = MemoryBlock.MemoryChannelCount; + + Assert.True(channelCount >= 1, $"Memory channel count should be at least 1, got {channelCount}"); + Assert.True(channelCount <= 16, $"Memory channel count seems unreasonable, got {channelCount}"); + } + + [Fact] + public static void MemoryChannelCountIsConsistent() + { + // The detected memory channel count should be consistent across multiple calls + var channelCount1 = MemoryBlock.MemoryChannelCount; + var channelCount2 = MemoryBlock.MemoryChannelCount; + + Assert.Equal(channelCount1, channelCount2); + } + + [Fact] + public static void MemoryChannelCountRespectsProcessorLimit() + { + // The memory channel count should not exceed max(1, Environment.ProcessorCount / 2) + var channelCount = MemoryBlock.MemoryChannelCount; + var maxExpectedChannels = Math.Max(1, Environment.ProcessorCount / 2); + + Assert.True(channelCount <= maxExpectedChannels, + $"Memory channel count ({channelCount}) should not exceed max(1, ProcessorCount/2) ({maxExpectedChannels})"); + } + + [Fact] + public static void ZeroMemoryWithDetectedChannels() + { + // Test that memory zeroing works correctly with the detected channel count + var bytes = new byte[4096]; // Larger buffer to benefit from parallelization + for (int i = 0; i < bytes.Length; i++) + { + bytes[i] = unchecked((byte)(i % 256)); + } + + unsafe + { + fixed (byte* pointer = bytes) + { + MemoryBlock.Zero(pointer, bytes.Length); + } + } + + for (int i = 0; i < bytes.Length; i++) + { + Assert.Equal(0, bytes[i]); + } + } + + [Fact] + public static void DefaultChannelCountForNonWindows() + { + // On non-Windows platforms, we should get the default behavior + // This is more of a behavioral test since we can't easily mock the platform + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + var channelCount = MemoryBlock.MemoryChannelCount; + var maxExpectedChannels = Math.Max(1, Environment.ProcessorCount / 2); + // Should fall back to default behavior, but respect processor limits + Assert.True(channelCount >= 1 && channelCount <= maxExpectedChannels, + $"Non-Windows channel count ({channelCount}) should be between 1 and {maxExpectedChannels}"); + } + } + } +} \ No newline at end of file diff --git a/csharp/Platform.Unsafe/MemoryBlock.cs b/csharp/Platform.Unsafe/MemoryBlock.cs index 05862e6..86d1317 100644 --- a/csharp/Platform.Unsafe/MemoryBlock.cs +++ b/csharp/Platform.Unsafe/MemoryBlock.cs @@ -4,6 +4,9 @@ using System.Threading.Tasks; using static System.Runtime.CompilerServices.Unsafe; +using System.Management; +using System.Runtime.InteropServices; + #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member namespace Platform.Unsafe @@ -14,6 +17,76 @@ namespace Platform.Unsafe /// public static unsafe class MemoryBlock { + private static readonly Lazy _memoryChannelCount = new(() => DetectMemoryChannelCount()); + + /// + /// Gets the number of memory channels available on the current system. + /// Получает количество каналов памяти, доступных в текущей системе. + /// + public static int MemoryChannelCount => _memoryChannelCount.Value; + + private static int DetectMemoryChannelCount() + { + try + { + // Try to detect memory channels on Windows using WMI + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return DetectMemoryChannelCountWindows(); + } + } + catch + { + // Fall through to default behavior if detection fails + } + + // Default to 2 channels (dual-channel memory is most common) + // But respect the processor count limitation to avoid wasting resources + var defaultChannels = 2; + var maxThreads = Math.Max(1, Environment.ProcessorCount / 2); + return Math.Min(defaultChannels, maxThreads); + } + + [System.Runtime.Versioning.SupportedOSPlatform("windows")] + private static int DetectMemoryChannelCountWindows() + { + using var searcher = new ManagementObjectSearcher("SELECT * FROM Win32_PhysicalMemory"); + using var results = searcher.Get(); + + var interleaveDataDepth = 0; + var memoryDeviceCount = 0; + + foreach (ManagementObject obj in results) + { + memoryDeviceCount++; + + // Try to get InterleaveDataDepth property + var depth = obj["InterleaveDataDepth"]; + if (depth != null && depth is uint depthValue && depthValue > 0) + { + interleaveDataDepth = Math.Max(interleaveDataDepth, (int)depthValue); + } + } + + var maxThreads = Math.Max(1, Environment.ProcessorCount / 2); + + // If we found InterleaveDataDepth, use it + if (interleaveDataDepth > 0) + { + return Math.Min(interleaveDataDepth, maxThreads); + } + + // Fallback: estimate based on memory device count + // Common configurations: 2 DIMMs = dual channel, 4 DIMMs = quad channel + if (memoryDeviceCount >= 4) + { + return Math.Min(4, maxThreads); + } + + // Default to dual channel, but respect processor limits + return Math.Min(2, maxThreads); + } + /// /// Zeroes the number of bytes specified in starting from . /// Обнуляет количество байтов, указанное в , начиная с . @@ -27,16 +100,16 @@ public static unsafe class MemoryBlock public static void Zero(void* pointer, long capacity) { // A way to prevent wasting resources due to Hyper-Threading. - var threads = Environment.ProcessorCount / 2; - if (threads <= 1) + var maxThreads = Environment.ProcessorCount / 2; + if (maxThreads <= 1) { ZeroBlock(pointer, 0, capacity); } else { - // Using 2 threads because two-channel memory architecture is the most available type. - // CPUs mostly just wait for memory here. - threads = 2; + // Use detected memory channel count, but cap it by processor count / 2 + // CPUs mostly just wait for memory here, so we optimize for memory bandwidth. + var threads = Math.Min(MemoryChannelCount, maxThreads); Parallel.ForEach(Partitioner.Create(0L, capacity), new ParallelOptions { MaxDegreeOfParallelism = threads }, range => ZeroBlock(pointer, range.Item1, range.Item2)); } } diff --git a/csharp/Platform.Unsafe/Platform.Unsafe.csproj b/csharp/Platform.Unsafe/Platform.Unsafe.csproj index 9828835..7b7c7a5 100644 --- a/csharp/Platform.Unsafe/Platform.Unsafe.csproj +++ b/csharp/Platform.Unsafe/Platform.Unsafe.csproj @@ -39,6 +39,7 @@ + From a750e301714945a9469cc9c973c4f6a0db86b0c5 Mon Sep 17 00:00:00 2001 From: konard Date: Sun, 14 Sep 2025 08:35:19 +0300 Subject: [PATCH 3/3] Remove CLAUDE.md - Claude command completed --- CLAUDE.md | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index 05f1f2f..0000000 --- a/CLAUDE.md +++ /dev/null @@ -1,5 +0,0 @@ -Issue to solve: https://github.com/linksplatform/Unsafe/issues/34 -Your prepared branch: issue-34-952798a7 -Your prepared working directory: /tmp/gh-issue-solver-1757827397826 - -Proceed. \ No newline at end of file