以前の記事でC#からCPUモデルを取得する方法を紹介しましたが、ARM64環境に対する手当が不十分でした。今回はARM64環境も含めてC#でCPUモデルを取得する方法を紹介します。
アーキテクチャとOSごとのCPUモデル取得方法
やり方を検討するにあたり、アーキテクチャごとに各OSでどうやったらCPUモデルを取得できるかをまとめます。
| アーキテクチャ |
OS |
取得方法 |
| x86_64 |
共通 |
CPUID命令を使う |
| x86_64 |
Linux |
/proc/cpuinfoを読む、lscpuを使う |
| x86_64 |
Windows |
WMI (Win32_Processor)を使う、レジストリを読む |
| x86_64 |
macOS |
sysctlを使う |
| ARM64 |
共通 |
なし |
| ARM64 |
Linux |
lscpuを使う、/proc/cpuinfoを読んでマッピング参照 |
| ARM64 |
Windows |
WMI (Win32_Processor)を使う、レジストリを読む |
| ARM64 |
macOS |
sysctlを使う |
x86_64環境ではCPUID命令を使ってCPUモデルを直接取得できるのに対し、ARM64環境ではCPUID命令が存在しないため、OS依存の方法で取得します。具体的にはOSが提供するAPIやコマンドを利用してCPUモデルを取得しましょう。
特にLinux環境では、x86_64で/proc/cpuinfoにCPUモデル情報が含まれていたのがARM64では含まれていないため、lscpuコマンドを使うか/proc/cpuinfoを読んでマッピングテーブルを参照しましょう。
C#での実装例
今回は、x86_64とARM64環境の両方をサポートしています。
Linux ARM64環境でCPUモデルを取得するために/proc/cpuinfoを読んでARMのImplementer(CPUベンダー)とパーツ番号からモデル名をマッピングする方法を採用しました。
マッピング表は、util-linux/util-linux | GitHubとfastfetch-cli/fastfetch | GitHubを参考にしています。が、この実装ってCPUベンダーが新しいCPUを出すたびに更新しないといけないので、なかなか大変です。とはいえ、この記事でも述べられているように、lscpuも内部実装はマッピングなわけで、ARM64環境でCPUモデルを取得するにはこの方法が2025年時点では最も確実です。1
Windows、macOSはアーキテクチャに関係なく同じ方法で取得できるため、アーキテクチャ判定はx86_64でCPUID命令を使えたら使うだけの分岐です。
Windowsはレジストリを直接読んでいます。WMIを使う方法もありますが、WMIは起動が遅いので、レジストリを直接読む方法の方が高速です。
macOSはsysctlを使っています。ただし、前回はlibcとしていましたが、libSystem.dylibの方が正しくこちらで動作を確認しました。
using System.Runtime.InteropServices;
namespace SystemInfo.Core;
public class CpuModel
{
const string UnknownPhrase = "Unknown";
public static CpuModel Current { get; } = new CpuModel();
public string ModelName { get; } = "";
public string UnknownReason { get; } = "";
private CpuModel()
{
if (System.Runtime.Intrinsics.X86.X86Base.IsSupported)
{
(ModelName, UnknownReason) = GetX86CpuModelName();
}
else
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
(ModelName, UnknownReason) = GetWindowsModelName();
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
(ModelName, UnknownReason) = GetLinuxModelName();
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
(ModelName, UnknownReason) = GetOSXModelname();
}
else
{
(ModelName, UnknownReason) = (UnknownPhrase, $"Platform not supported for... OS: {RuntimeInformation.OSDescription}, Architecture: {RuntimeInformation.OSArchitecture}");
}
}
}
private static (string modelName, string unknownReason) GetX86CpuModelName()
{
Span<int> regs = stackalloc int[12];
var extendedId = System.Runtime.Intrinsics.X86.X86Base.CpuId(unchecked((int)0x80000000), 0).Eax;
if ((uint)extendedId >= 0x80000004)
{
int p = 0;
for (uint i = 0x80000002; i <= 0x80000004; ++i)
{
var (Eax, Ebx, Ecx, Edx) = System.Runtime.Intrinsics.X86.X86Base.CpuId((int)i, 0);
regs[p + 0] = Eax;
regs[p + 1] = Ebx;
regs[p + 2] = Ecx;
regs[p + 3] = Edx;
p += 4;
}
return (ConvertToString(regs), "");
}
return (UnknownPhrase, $"CPU Architecture not supported... extendedId: {extendedId}");
static string ConvertToString(ReadOnlySpan<int> regs)
{
Span<byte> bytes = stackalloc byte[regs.Length * 4];
for (int i = 0; i < regs.Length; i++)
{
BitConverter.TryWriteBytes(bytes.Slice(i * 4, 4), regs[i]);
}
return System.Text.Encoding.ASCII.GetString(bytes).Trim();
}
}
private static (string modelName, string unknownReason) GetWindowsModelName()
{
const string registryKey = @"HARDWARE\DESCRIPTION\System\CentralProcessor\0";
const string valueName = "ProcessorNameString";
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
throw new PlatformNotSupportedException("Not Windows OS.");
using var key = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(registryKey);
if (key != null)
{
var value = key.GetValue(valueName);
if (value is string model)
{
return (model, "");
}
}
return (UnknownPhrase, "Windows Registry Key not found.");
}
private static (string modelName, string unknownReason) GetLinuxModelName()
{
try
{
var cpuInfo = File.ReadAllText("/proc/cpuinfo");
var lines = cpuInfo.Split('\n');
string vendorId = "";
string cpuPart = "";
string modelName = "";
foreach (var line in lines)
{
if (line.StartsWith("model name"))
{
var parts = line.Split(':');
if (parts.Length > 1)
{
modelName = parts[1].Trim();
return (modelName, "");
}
}
else if (line.StartsWith("CPU implementer"))
{
var parts = line.Split(":");
if (parts.Length > 1)
{
vendorId = parts[1].Trim().ToLower();
}
}
else if (line.StartsWith("CPU part"))
{
var parts = line.Split(":");
if (parts.Length > 1)
{
cpuPart = parts[1].Trim().ToLower();
}
}
}
if (modelName == "" && vendorId != "")
{
if (cpuPart == "")
return (UnknownPhrase, "CPU part not found for ARM CPU.");
var armModelName = GetArmCpuName(vendorId, cpuPart);
return armModelName != "Undefined"
? (armModelName, "")
: (armModelName, $"CPU part '{cpuPart}' (vendor '{vendorId}' ({GetArmImplementerName(vendorId)})) is not mapped.");
}
return (UnknownPhrase, "'model name' section not found.");
}
catch (Exception ex)
{
return (UnknownPhrase, $"Exception occurred: {ex.Message}");
}
}
private static (string modelName, string unknownReason) GetOSXModelname()
{
try
{
nint size = 0;
int result = sysctlbyname("machdep.cpu.brand_string", IntPtr.Zero, ref size, IntPtr.Zero, 0);
if (result != 0)
{
return (UnknownPhrase, $"sysctlbyname failed to get size. Return code: {result}, errno: {Marshal.GetLastPInvokeError()}");
}
if (size == 0)
{
return (UnknownPhrase, "sysctlbyname returned size 0");
}
IntPtr buffer = Marshal.AllocHGlobal((int)size);
try
{
result = sysctlbyname("machdep.cpu.brand_string", buffer, ref size, IntPtr.Zero, 0);
if (result != 0)
{
return (UnknownPhrase, $"sysctlbyname failed to get value. Return code: {result}, errno: {Marshal.GetLastPInvokeError()}");
}
var cpuBrand = Marshal.PtrToStringAnsi(buffer);
if (string.IsNullOrEmpty(cpuBrand))
{
return (UnknownPhrase, "machdep.cpu.brand_string returned empty string.");
}
return (cpuBrand.Trim(), "");
}
finally
{
Marshal.FreeHGlobal(buffer);
}
}
catch (Exception ex)
{
return (UnknownPhrase, $"Exception occurred: {ex.Message}");
}
}
[DllImport("libSystem.dylib")]
private static extern int sysctlbyname(string name, IntPtr oldp, ref nint oldlenp, IntPtr newp, nint newlen);
private enum ArmImplementers
{
Ampere,
ARM,
APM,
Apple,
Broadcom,
Cavium,
DEC,
Faraday,
Fujitsu,
HiSilicon,
Infineon,
Intel,
Marvell,
Microsoft,
Motorola,
NVIDIA,
Phytium,
Qualcomm,
Samsung,
Unknown,
}
private static ArmImplementers GetArmImplementerName(string vendorId)
{
return vendorId switch
{
"0x41" => ArmImplementers.ARM,
"0x42" => ArmImplementers.Broadcom,
"0x43" => ArmImplementers.Cavium,
"0x44" => ArmImplementers.DEC,
"0x46" => ArmImplementers.Fujitsu,
"0x48" => ArmImplementers.HiSilicon,
"0x49" => ArmImplementers.Infineon,
"0x4d" => ArmImplementers.Motorola,
"0x6d" => ArmImplementers.Microsoft,
"0x4e" => ArmImplementers.NVIDIA,
"0x50" => ArmImplementers.APM,
"0x51" => ArmImplementers.Qualcomm,
"0x53" => ArmImplementers.Samsung,
"0x56" => ArmImplementers.Marvell,
"0x61" => ArmImplementers.Apple,
"0x66" => ArmImplementers.Faraday,
"0x69" => ArmImplementers.Intel,
"0x70" => ArmImplementers.Phytium,
"0xc0" => ArmImplementers.Ampere,
_ => ArmImplementers.Unknown,
};
}
private static string GetArmCpuName(string vendorId, string cpuPart)
{
var armImpl = GetArmImplementerName(vendorId);
return armImpl switch
{
ArmImplementers.ARM => cpuPart switch
{
"0x810" => "ARM810",
"0x920" => "ARM920",
"0x922" => "ARM922",
"0x926" => "ARM926",
"0x940" => "ARM940",
"0x946" => "ARM946",
"0x966" => "ARM966",
"0xa20" => "ARM1020",
"0xa22" => "ARM1022",
"0xa26" => "ARM1026",
"0xb02" => "ARM11 MPCore",
"0xb36" => "ARM1136",
"0xb56" => "ARM1156",
"0xb76" => "ARM1176",
"0xc05" => "Cortex-A5",
"0xc07" => "Cortex-A7",
"0xc08" => "Cortex-A8",
"0xc09" => "Cortex-A9",
"0xc0d" => "Cortex-A17",
"0xc0f" => "Cortex-A15",
"0xc0e" => "Cortex-A17",
"0xc14" => "Cortex-R4",
"0xc15" => "Cortex-R5",
"0xc17" => "Cortex-R7",
"0xc18" => "Cortex-R8",
"0xc20" => "Cortex-M0",
"0xc21" => "Cortex-M1",
"0xc23" => "Cortex-M3",
"0xc24" => "Cortex-M4",
"0xc27" => "Cortex-M7",
"0xc60" => "Cortex-M0+",
"0xd00" => "Foundation",
"0xd01" => "Cortex-A32",
"0xd02" => "Cortex-A34",
"0xd03" => "Cortex-A53",
"0xd04" => "Cortex-A35",
"0xd05" => "Cortex-A55",
"0xd06" => "Cortex-A65",
"0xd07" => "Cortex-A57",
"0xd08" => "Cortex-A72",
"0xd09" => "Cortex-A73",
"0xd0a" => "Cortex-A75",
"0xd0b" => "Cortex-A76",
"0xd0c" => "Neoverse-N1",
"0xd0d" => "Cortex-A77",
"0xd0e" => "Cortex-A76AE",
"0xd0f" => "AEMv8",
"0xd13" => "Cortex-R52",
"0xd20" => "Cortex-M23",
"0xd21" => "Cortex-M33",
"0xd40" => "Neoverse-V1",
"0xd41" => "Cortex-A78",
"0xd42" => "Cortex-A78AE",
"0xd43" => "Cortex-A65AE",
"0xd44" => "Cortex-X1",
"0xd46" => "Cortex-A510",
"0xd47" => "Cortex-A710",
"0xd48" => "Cortex-X2",
"0xd49" => "Neoverse-N2",
"0xd4a" => "Neoverse-E1",
"0xd4b" => "Cortex-A78C",
"0xd4c" => "Cortex-X1C",
"0xd4d" => "Cortex-A715",
"0xd4e" => "Cortex-X3",
"0xd4f" => "Neoverse-V2",
"0xd80" => "Cortex-A520",
"0xd81" => "Cortex-A720",
"0xd82" => "Cortex-X4",
"0xd84" => "Neoverse-V3",
"0xd85" => "Cortex-X925",
"0xd87" => "Cortex-A725",
"0xd88" => "Cortex-A520AE",
"0xd89" => "Cortex-A720AE",
"0xd8a" => "C1-Nano",
"0xd8b" => "C1-Pro",
"0xd8c" => "C1-Ultra",
"0xd8e" => "Neoverse-N3",
"0xd8f" => "Cortex-A320",
"0xd90" => "C1-Premium",
_ => "Undefined",
},
ArmImplementers.Ampere => cpuPart switch
{
"0xac3" => "Ampere-1",
"0xac4" => "Ampere-1a",
_ => "Undefined",
},
ArmImplementers.APM => cpuPart switch
{
"0x000" => "X-Gene",
_ => "Undefined",
},
ArmImplementers.Apple => cpuPart switch
{
"0x000" => "Swift",
"0x001" => "Cyclone",
"0x002" => "Typhoon",
"0x003" => "Typhoon/Capri",
"0x004" => "Twister",
"0x005" => "Twister/Elba/Malta",
"0x006" => "Hurricane",
"0x007" => "Hurricane/Myst",
"0x008" => "Monsoon",
"0x009" => "Mistral",
"0x00b" => "Vortex",
"0x00c" => "Tempest",
"0x00f" => "Tempest-M9",
"0x010" => "Vortex/Aruba",
"0x011" => "Tempest/Aruba",
"0x012" => "Lightning",
"0x013" => "Thunder",
"0x020" => "Icestorm-A14",
"0x021" => "Firestorm-A14",
"0x022" => "Icestorm-M1",
"0x023" => "Firestorm-M1",
"0x024" => "Icestorm-M1-Pro",
"0x025" => "Firestorm-M1-Pro",
"0x026" => "Thunder-M10",
"0x028" => "Icestorm-M1-Max",
"0x029" => "Firestorm-M1-Max",
"0x030" => "Blizzard-A15",
"0x031" => "Avalanche-A15",
"0x032" => "Blizzard-M2",
"0x033" => "Avalanche-M2",
"0x034" => "Blizzard-M2-Pro",
"0x035" => "Avalanche-M2-Pro",
"0x036" => "Sawtooth-A16",
"0x037" => "Everest-A16",
"0x038" => "Blizzard-M2-Max",
"0x039" => "Avalanche-M2-Max",
"0x046" => "Sawtooth-M11",
"0x048" => "Sawtooth-M3-Max",
"0x049" => "Everest-M3-Max",
_ => "Undefined",
},
ArmImplementers.Broadcom => cpuPart switch
{
"0x0f" => "Brahma-B15",
"0x100" => "Brahma-B53",
"0x516" => "ThunderX2",
_ => "Undefined",
},
ArmImplementers.Cavium => cpuPart switch
{
"0x0a0" => "ThunderX",
"0x0a1" => "ThunderX-88XX",
"0x0a2" => "ThunderX-81XX",
"0x0a3" => "ThunderX-83XX",
"0x0af" => "ThunderX2-99xx",
"0x0b0" => "OcteonTX2",
"0x0b1" => "OcteonTX2-98XX",
"0x0b2" => "OcteonTX2-96XX",
"0x0b3" => "OcteonTX2-95XX",
"0x0b4" => "OcteonTX2-95XXN",
"0x0b5" => "OcteonTX2-95XXMM",
"0x0b6" => "OcteonTX2-95XXO",
"0x0b8" => "ThunderX3-T110",
_ => "Undefined",
},
ArmImplementers.DEC => cpuPart switch
{
"0x001" => "SA110",
"0x002" => "SA1100",
_ => "Undefined",
},
ArmImplementers.Faraday => cpuPart switch
{
"0x526" => "FA526",
"0x626" => "FA626",
_ => "Undefined",
},
ArmImplementers.Fujitsu => cpuPart switch
{
"0x001" => "A64FX",
"0x003" => "MONAKA",
_ => "Undefined",
},
ArmImplementers.HiSilicon => cpuPart switch
{
"0xd01" => "TaiShan-v110",
"0xd02" => "TaiShan-v120",
"0xd40" => "Cortex-A76",
"0xd41" => "Cortex-A77",
_ => "Undefined",
},
ArmImplementers.Intel => cpuPart switch
{
"0x200" => "i80200",
"0x210" => "PXA250A",
"0x212" => "PXA210A",
"0x242" => "i80321-400",
"0x243" => "i80321-600",
"0x290" => "PXA250B/PXA26x",
"0x292" => "PXA210B",
"0x2c2" => "i80321-400-B0",
"0x2c3" => "i80321-600-B0",
"0x2d0" => "PXA250C/PXA255/PXA26x",
"0x2d2" => "PXA210C",
"0x411" => "PXA27x",
"0x41c" => "IPX425-533",
"0x41d" => "IPX425-400",
"0x41f" => "IPX425-266",
"0x682" => "PXA32x",
"0x683" => "PXA930/PXA935",
"0x688" => "PXA30x",
"0x689" => "PXA31x",
"0xb11" => "SA1110",
"0xc12" => "IPX1200",
_ => "Undefined",
},
ArmImplementers.Marvell => cpuPart switch
{
"0x131" => "Feroceon-88FR131",
"0x581" => "PJ4/PJ4b",
"0x584" => "PJ4B-MP",
_ => "Undefined",
},
ArmImplementers.Microsoft => cpuPart switch
{
"0xd49" => "Azure-Cobalt-100",
_ => "Undefined",
},
ArmImplementers.NVIDIA => cpuPart switch
{
"0x000" => "Denver",
"0x003" => "Denver 2",
"0x004" => "Carmel",
"0x010" => "Olympus",
_ => "Undefined",
},
ArmImplementers.Phytium => cpuPart switch
{
"0x303" => "FTC310",
"0x660" => "FTC660",
"0x661" => "FTC661",
"0x662" => "FTC662",
"0x663" => "FTC663",
"0x664" => "FTC664",
"0x862" => "FTC862",
_ => "Undefined",
},
ArmImplementers.Qualcomm => cpuPart switch
{
"0x001" => "Oryon 1",
"0x002" => "Oryon 2",
"0x00f" => "Scorpion",
"0x02d" => "Scorpion",
"0x04d" => "Krait",
"0x06f" => "Krait",
"0x201" => "Kryo",
"0x205" => "Kryo",
"0x211" => "Kryo",
"0x800" => "Falkor-V1/Kryo",
"0x801" => "Kryo-V2",
"0x802" => "Kryo-3XX-Gold",
"0x803" => "Kryo-3XX-Silver",
"0x804" => "Kryo-4XX-Gold",
"0x805" => "Kryo-4XX-Silver",
"0xc00" => "Falkor",
"0xc01" => "Saphira",
_ => "Undefined",
},
ArmImplementers.Samsung => cpuPart switch
{
"0x001" => "Exynos-M1",
"0x002" => "Exynos-M3",
"0x003" => "Exynos-M4",
"0x004" => "Exynos-M5",
_ => "Undefined",
},
ArmImplementers.Infineon => "Undefined",
ArmImplementers.Motorola => "Undefined",
ArmImplementers.Unknown => "Undefined",
_ => "Undefined",
};
}
}
動作確認
こういったOSとアーキテクチャを組み合わせた動作確認は手元に環境がなくて諦めがちです。安心してください。幸いにしてGitHub Actionsはx86_64およびARM64のLinux/Windows/macOS環境を提供しているので、これを使って動作確認ができます。誰でも再現可能ですし、簡単に確認できて最高ですね。
run:
permissions:
contents: read
strategy:
fail-fast: false
matrix:
runs-on:
- ubuntu-24.04
- ubuntu-24.04-arm
- windows-2025
- windows-11-arm
- macos-15-intel
- macos-26
runs-on: ${{ matrix.runs-on }}
timeout-minutes: 10
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
with:
persist-credentials: false
- uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9
with:
dotnet-version: "10.0.x"
- name: Get CPU Model
run: dotnet Program.cs
適当なProgram.csを用意して実行します。.NET 10ならProgram.csファイル1つあればdotnet Program.csで実行可能です。
using System.Runtime.InteropServices;
StartupInfo.Print();
public static class StartupInfo
{
private static readonly string cpuModel = !string.IsNullOrWhiteSpace(CpuModel.Current.ModelName) ? CpuModel.Current.ModelName : CpuModel.Current.UnknownReason;
<summary>
</summary>
public static void Print()
{
Console.WriteLine($$"""
Startup info:
* OS : {{RuntimeInformation.OSDescription}}
* CPU Arch : {{RuntimeInformation.ProcessArchitecture}}
* CPU Model: {{cpuModel}}
* CPU Cores: {{Environment.ProcessorCount}}
""");
}
}
出力結果です。全部の環境でCPUモデルが正しく取得できていることがわかります。GitHub ActionsのホストランナーはAzure VMなのですが、Ubuntu ARM64ではNeoverse-N2、Windows ARM64ではCobalt 100が使われているのが興味深いですね。Azure Cobaltドキュメントにあるように、Azure Cobalt 100はNeoverse-N2ベースのカスタムSoCなので、整合性が取れています。
Startup info:
* OS : Ubuntu 24.04.3 LTS
* CPU Arch : X64
* CPU Model: AMD EPYC 7763 64-Core Processor
* CPU Cores: 4
Startup info:
* OS : Ubuntu 24.04.3 LTS
* CPU Arch : Arm64
* CPU Model: Neoverse-N2
* CPU Cores: 4
Startup info:
* OS : Microsoft Windows 10.0.26100
* CPU Arch : X64
* CPU Model: AMD EPYC 7763 64-Core Processor
* CPU Cores: 4
Startup info:
* OS : Microsoft Windows 10.0.26200
* CPU Arch : Arm64
* CPU Model: Cobalt 100
* CPU Cores: 4
Startup info:
* OS : macOS 15.7.1
* CPU Arch : X64
* CPU Model: Intel(R) Core(TM) i7-8700B CPU @ 3.20GHz
* CPU Cores: 4
Startup info:
* OS : macOS 26.0.1
* CPU Arch : Arm64
* CPU Model: Apple M1 (Virtual)
* CPU Cores: 3
まとめ
前回はx86_64環境のみの対応でしたが、今回はARM64環境も含めてC#でCPUモデルを取得する方法を紹介しました。今回はマッピングを書きましたが、マッピングテーブルをメンテし続ける必要があるのは面倒なのでlscpuコマンドを実行して省力もよいでしょう。実行頻度やパフォーマンス要件に応じて使い分けてください。
参考