How to determine which logical cores share the same physical core?
Asked Answered
S

1

21

I am working on a tool to allow students to self-evaluate their programming assignment's performance. In particular, the program they're writing is multi-threaded and I have no direct way of influencing the number of threads created. I would like to compare the performance of their programs given a different number of cores (and ideally, their programs should speed up roughly proportionally to how many cores it is allowed to use).

We can pass a bitmask to Process.SetAffinity to control which cores the program uses.

This is problematic on i5 and i7 machines that use hyper-threading and split each physical core in to two logical ones. I want the program to run on two/four distinct physical cores. On my i7 machine, a process with affinity set to 3 (cores 0&1) will run roughly as fast as the program on a single core (indicating these logical cores share the same physical core), but with affinity set to 5 (cores 0&3) it will run much faster (indicating these cores are using different physical cores). However, I haven't found a reliable way (other than trial-and-error) to determine this.

How can I (without experimentation) determine which logical cores share the same physical core?

(/proc/cpuinfo has the information I need but that is not available on Windows machines.)

Saddlebag answered 15/10, 2014 at 19:55 Comment(12)
The information probably available via WMI (Windows Management Instrumentation) - or at least the count of physical/virtual cores, which it might be valid to extrapolate into an interleaved bitmask.Kentigerma
It looks like I forgot to mention I am specifically looking for a C# solution. I did add the C# tag but I didn't mention it anywhere. I've now added C# in the title, which should make it a bit clearer what I am looking for.Saddlebag
@TomvanderZanden You shouldn't add C# to the title. Tag should be enough. If it's not, make it clear in your question.Prudish
as @Kentigerma said, you can use WMI to discover information about the processor and the number of physical and virtual cores. The affinity mask is probably ordered in a way that each bit is a virtual processor in order. So, if you have 2 physical cores and and four virtual cores(two virtual cores per physical core), the bits 0-1 are the first physical core and their virtual cores, bits 2-3 are the second core.Sent
As far as I can tell, WMI does not reveal this information. It only allows one to determine how many Logical and Physical cores a processor has, but it seems to have no option to determine which cores are grouped together.Saddlebag
The convention for i3, i5 and i7 is to have the odd ids (1,3,5,7) represent the physical and the even (2,4,6,8) the virtual cores. I am not sure if there is a way to get this "convention" information via s system call of some sort though. One extra note, you can disable Hyper Threading in the BIOS which will leave you with physical cores only.Yevetteyew
@xpa1492: You're using one-based numbering, but it's zero-based almost everywhere. In zero-based numbering, are the master cores even (0,2,4,6) and the slave cores odd (1,3,5,7) ?Spot
Also, do note that it's quite possible that the code will run as fast on HT cores mapped to the same physical core as on two separate physical core. It's still a good idea to measure the difference between two physical versus two logical on one physical, but HT is actually a pretty awesome technology. And of course, if you're doing a lot of synchronous I/O, there'll be little difference between the two cases, which is also an interesting bit of information. And yes, the grouped cores will be in one range of IDs, so the amount of physical vs logical cores should be enough on single-CPU systems.Nesta
@BenVoigt Are you sure there even is a distinction between "master" and "slave"? In my observations, the two in each pair seemed more or less equivalent, rather than one being preferred...Nesta
@Luaan: There definitely are some performance advantages to sharing a physical core, namely sharing cache structures, if the threads are closely coupled.Spot
I wanted to write up an answer to this post, but I didn't have enough time to pull together enough sources and information. However, if you're not afraid of COM calls in C#, see this SO post:#6972937 The information in this post relates to this issue and should get you on the road to a solution. Please post an answer, yourself, if you solve this on your own.Acosta
I think, GetCurrentProcessorNumberEx will be helpful too msdn.microsoft.com/en-us/library/windows/desktop/…Sample
S
7

Based on comments to your question (thanks to all, especially to @RLH) I made this class for you:

/// <summary>
/// Provides CPU information
/// </summary>
public static class Processor
{
    private static IHardwareCore[] cores;
    private static int[] logicalCores;

    /// <summary>
    /// Hardware core
    /// </summary>
    public interface IHardwareCore 
    {
        /// <summary>
        /// Logical core IDs
        /// </summary>
        int[] LogicalCores { get; }
    }

    /// <summary>
    /// Hardware cores
    /// </summary>
    public static IHardwareCore[] HardwareCores
    {
        get
        {
            return cores ?? (cores = GetLogicalProcessorInformation()
                .Where(x => x.Relationship == LOGICAL_PROCESSOR_RELATIONSHIP.RelationProcessorCore)
                .Select(x => new HardwareCore((UInt64)x.ProcessorMask))
                .ToArray<IHardwareCore>());
        }
    }

    /// <summary>
    /// All logical core IDs
    /// </summary>
    public static int[] LogicalCores
    {
        get
        {
            return logicalCores ?? (logicalCores = HardwareCores
                .SelectMany(x => x.LogicalCores)
                .ToArray());
        }
    }

    /// <summary>
    /// Current logical core ID
    /// </summary>
    public static int CurrentLogicalCore
    {
        get { return GetCurrentProcessorNumber(); }
    }

    private class HardwareCore : IHardwareCore
    {
        public HardwareCore(UInt64 logicalCoresMask)
        {
            var logicalCores = new List<int>();

            for (var i = 0; i < 64; ++i)
            {
                if (((logicalCoresMask >> i) & 0x1) == 0) continue;
                logicalCores.Add(i);
            }

            LogicalCores = logicalCores.ToArray();
        }

        public int[] LogicalCores { get; private set; }
    }

    #region Exports

    [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[] GetLogicalProcessorInformation()
    {
        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;
    }

    [DllImport(@"kernel32.dll", SetLastError = true)]
    private static extern int GetCurrentProcessorNumber();

    #endregion
}

Usage example:

for (var i = 0; i < Processor.HardwareCores.Length; ++i)
{
    Console.WriteLine("Hardware Core {0} has logical cores {1}", i,
        string.Join(", ", Processor.HardwareCores[i].LogicalCores));
}
Console.WriteLine("All logical cores: " + string.Join(", ", Processor.LogicalCores));
Console.WriteLine("Current Logical Core is " + Processor.CurrentLogicalCore);

Example outputs for Intel Core i5:

Hardware Core 0 has logical cores 0, 1
Hardware Core 1 has logical cores 2, 3
All logical cores: 0, 1, 2, 3
Current Logical Core is 2
Sample answered 18/10, 2014 at 14:42 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.