0

I would like to write a tool in C# which can detect the presence of multiple CPU groups in Windows. I have seen this referred to as kGroups, and also as NUMA nodes.

This need has grown out of multiple performance related issues where we discovered the customer was running HP servers which often have their NUMA Group Size Optimization in the BIOS set to "Clustered" instead of "Flat" which can result in multiple CPU groups in Windows. Any one process, unless otherwise designed to operate across kGroups, will only be able to use logical processors within the kGroup the process affinity is set to.

I have found a number of resources that can detect information about number of physical/logical processors, but I'm unable to find information on if/how those CPU's might be logically grouped. I'm open to getting this info through p/invoke or WMI among other methods.

Edit: Found the following post with a full example of the GetLogicalProcessorInformationEx call via p/invoke. Will update when I can confirm how to test numa node configuration.

https://stackoverflow.com/a/6972620/3736007

references: http://h17007.www1.hpe.com/docs/iss/proliant_uefi/UEFI_Gen9_060216/s_set_NUMA_group.html

Solved (I think). I was able to use David Heffernan's c# implementation, wrapped it up in a class and added some properties to return some of the information I'm after. Unsafe code is still a bit of black magic to me so I know it could be done better, but it's working so far.

public static class LogicalProcessorInformation
{
    public static int CpuPackages
    {
        get
        {
            if (_buffer == null)
                _buffer = MyGetLogicalProcessorInformation();
            return _buffer.Count(b => b.Relationship == LOGICAL_PROCESSOR_RELATIONSHIP.RelationProcessorPackage);
        }
    }

    public static int CpuCores
    {
        get
        {
            if (_buffer == null)
                _buffer = MyGetLogicalProcessorInformation();
            return _buffer.Count(b => b.Relationship == LOGICAL_PROCESSOR_RELATIONSHIP.RelationProcessorCore);
        }
    }

    public static int LogicalProcessors
    {
        get
        {
            if (_buffer == null)
                _buffer = MyGetLogicalProcessorInformation();
            var count = 0;
            foreach (var obj in _buffer.Where(b => b.Relationship == LOGICAL_PROCESSOR_RELATIONSHIP.RelationProcessorCore))
            {
                count += CountSetBits(obj.ProcessorMask);
            }
            return count;
        }
    }

    public static int CpuGroups
    {
        get
        {
            if (_buffer == null)
                _buffer = MyGetLogicalProcessorInformation();
            return _buffer.Count(b => b.Relationship == LOGICAL_PROCESSOR_RELATIONSHIP.RelationGroup);
        }
    }

    public static int NumaNodes
    {
        get
        {
            if (_buffer == null)
                _buffer = MyGetLogicalProcessorInformation();
            return _buffer.Count(b => b.Relationship == LOGICAL_PROCESSOR_RELATIONSHIP.RelationNumaNode);
        }
    }

    private static int CountSetBits(UIntPtr bitMask)
    {
        // todo: get rid of tostring and figure out the right way.
        var bitMaskuint = uint.Parse(bitMask.ToString());
        uint count = 0;
        while (bitMaskuint != 0)
        {
            count += bitMaskuint & 1;
            bitMaskuint >>= 1;
        }

        return (int)count;
    }

    private static SYSTEM_LOGICAL_PROCESSOR_INFORMATION[] _buffer;

    [StructLayout(LayoutKind.Sequential)]
    private struct PROCESSORCORE
    {
        public byte Flags;
    };

    [StructLayout(LayoutKind.Sequential)]
    private struct NUMANODE
    {
        public uint NodeNumber;
    }

    private enum PROCESSOR_CACHE_TYPE
    {
        CacheUnified,
        CacheInstruction,
        CacheData,
        CacheTrace
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct CACHE_DESCRIPTOR
    {
        public byte Level;
        public byte Associativity;
        public ushort LineSize;
        public uint Size;
        public PROCESSOR_CACHE_TYPE Type;
    }

    [StructLayout(LayoutKind.Explicit)]
    private struct SYSTEM_LOGICAL_PROCESSOR_INFORMATION_UNION
    {
        [FieldOffset(0)]
        public PROCESSORCORE ProcessorCore;
        [FieldOffset(0)]
        public NUMANODE NumaNode;
        [FieldOffset(0)]
        public CACHE_DESCRIPTOR Cache;
        [FieldOffset(0)]
        private UInt64 Reserved1;
        [FieldOffset(8)]
        private UInt64 Reserved2;
    }

    private enum LOGICAL_PROCESSOR_RELATIONSHIP
    {
        RelationProcessorCore,
        RelationNumaNode,
        RelationCache,
        RelationProcessorPackage,
        RelationGroup,
        RelationAll = 0xffff
    }

    private struct SYSTEM_LOGICAL_PROCESSOR_INFORMATION
    {
        public UIntPtr ProcessorMask;
        public LOGICAL_PROCESSOR_RELATIONSHIP Relationship;
        public SYSTEM_LOGICAL_PROCESSOR_INFORMATION_UNION ProcessorInformation;
    }

    [DllImport(@"kernel32.dll", SetLastError = true)]
    private static extern bool GetLogicalProcessorInformation(
        IntPtr Buffer,
        ref uint ReturnLength
    );

    private const int ERROR_INSUFFICIENT_BUFFER = 122;

    private static SYSTEM_LOGICAL_PROCESSOR_INFORMATION[] MyGetLogicalProcessorInformation()
    {
        uint ReturnLength = 0;
        GetLogicalProcessorInformation(IntPtr.Zero, ref ReturnLength);
        if (Marshal.GetLastWin32Error() == ERROR_INSUFFICIENT_BUFFER)
        {
            IntPtr Ptr = Marshal.AllocHGlobal((int)ReturnLength);
            try
            {
                if (GetLogicalProcessorInformation(Ptr, ref ReturnLength))
                {
                    int size = Marshal.SizeOf(typeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION));
                    int len = (int)ReturnLength / size;
                    SYSTEM_LOGICAL_PROCESSOR_INFORMATION[] Buffer = new SYSTEM_LOGICAL_PROCESSOR_INFORMATION[len];
                    IntPtr Item = Ptr;
                    for (int i = 0; i < len; i++)
                    {
                        Buffer[i] = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION)Marshal.PtrToStructure(Item, typeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION));
                        Item += size;
                    }
                    return Buffer;
                }
            }
            finally
            {
                Marshal.FreeHGlobal(Ptr);
            }
        }
        return null;
    }
}
Community
  • 1
  • 1

0 Answers0