Skip to content

Commit 1023109

Browse files
authored
Add amd64 CPU topology tree enumeration (#179)
* Add amd64 CPU topology tree enumeration Add a topology tree with a fixed hierarchy System → Package → Group → Core → Thread, where a Group is a last-level-cache (L3) sharing domain — an AMD CCX/CCD or an Intel LLC tile. Cores carry their type (performance/efficiency) and every level answers aggregate queries over what's below it (NumCores, NumThreads, CoresByType, TotalCache) without double-counting shared caches, so e.g. sys.TotalCache(3, Unified) is the total L3 across all CCDs. - New topology sub-package: pure types + query methods + a tree String(); no dependencies of its own. - CPUID parsing/assembly stays in package cpuid: x2APIC decomposition from leaves 0x1F/0x0B, cache sharing from 0x04/0x8000001D, hybrid core type from 0x1A, AMD node id and SMT from 0x8000001E. - CPU.Topology(): cheap single-core snapshot (default, no thread pinning). ScanTopology(): opt-in exact per-core walk that pins to each CPU (linux/amd64, windows/amd64 via x/sys, already a dependency), falling back to the snapshot with ErrTopologyScanUnavailable elsewhere and on non-x86. - cmd/cpuid: add -topology to print the tree and cache totals. - Tests replay every recorded per-logical-CPU CPUID dump and validate the built tree against the dumps' Package/Core/Thread ground-truth headers; internal/samplecpu supplies a fixture for the unit tests and examples. Example: ``` λ cpuid -topology System: AMD (scanned) — 1 package(s), 16 core(s), 32 thread(s) Package #0 Group #0 [L3 32MB] Core #0 (Performance) threads: 0, 1 [L1d 48KB L1i 32KB L2 1MB] Core #1 (Performance) threads: 2, 3 [L1d 48KB L1i 32KB L2 1MB] Core #2 (Performance) threads: 4, 5 [L1d 48KB L1i 32KB L2 1MB] Core #3 (Performance) threads: 6, 7 [L1d 48KB L1i 32KB L2 1MB] Core #4 (Performance) threads: 8, 9 [L1d 48KB L1i 32KB L2 1MB] Core #5 (Performance) threads: 10, 11 [L1d 48KB L1i 32KB L2 1MB] Core #6 (Performance) threads: 12, 13 [L1d 48KB L1i 32KB L2 1MB] Core #7 (Performance) threads: 14, 15 [L1d 48KB L1i 32KB L2 1MB] Group #1 [L3 32MB] Core #8 (Performance) threads: 16, 17 [L1d 48KB L1i 32KB L2 1MB] Core #9 (Performance) threads: 18, 19 [L1d 48KB L1i 32KB L2 1MB] Core #10 (Performance) threads: 20, 21 [L1d 48KB L1i 32KB L2 1MB] Core #11 (Performance) threads: 22, 23 [L1d 48KB L1i 32KB L2 1MB] Core #12 (Performance) threads: 24, 25 [L1d 48KB L1i 32KB L2 1MB] Core #13 (Performance) threads: 26, 27 [L1d 48KB L1i 32KB L2 1MB] Core #14 (Performance) threads: 28, 29 [L1d 48KB L1i 32KB L2 1MB] Core #15 (Performance) threads: 30, 31 [L1d 48KB L1i 32KB L2 1MB] Total cache: L1D 768KB, L1I 512KB, L2 16MB, L3 64MB ``` * Add sample cpu * Fix feedback * Apply fix
1 parent 4e17f59 commit 1023109

12 files changed

Lines changed: 1547 additions & 0 deletions

cmd/cpuid/main.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323

2424
var js = flag.Bool("json", false, "Output as JSON")
2525
var level = flag.Int("check-level", 0, "Check microarchitecture level. Exit code will be 0 if supported")
26+
var topo = flag.Bool("topology", false, "Print the CPU topology tree (System > Package > Group > Core > Thread)")
2627

2728
func main() {
2829
flag.Parse()
@@ -38,6 +39,22 @@ func main() {
3839
log.Printf("Microarchitecture level %d is supported. Max level is %d.", *level, cpuid.CPU.X64Level())
3940
os.Exit(0)
4041
}
42+
if *topo {
43+
sys, err := cpuid.ScanTopology()
44+
if err != nil {
45+
fmt.Fprintln(os.Stderr, "Note:", err)
46+
}
47+
if *js {
48+
b, err := json.MarshalIndent(sys, "", " ")
49+
if err != nil {
50+
panic(err)
51+
}
52+
fmt.Println(string(b))
53+
} else {
54+
fmt.Print(sys.String())
55+
}
56+
os.Exit(0)
57+
}
4158
if *js {
4259
info := struct {
4360
cpuid.CPUInfo

cpuid.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -479,6 +479,7 @@ func init() {
479479
// exported CPU variable.
480480
func Detect() {
481481
// Set defaults
482+
topoSnapshot.Store(nil)
482483
CPU.ThreadsPerCore = 1
483484
CPU.Cache.L1I = -1
484485
CPU.Cache.L1D = -1

internal/samplecpu/samplecpu.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Copyright (c) 2026 Klaus Post, released under MIT License. See LICENSE file.
2+
3+
// Package samplecpu provides a synthetic, deterministic CPU topology for the
4+
// cpuid module's own tests, examples and documentation. It is internal on
5+
// purpose: it is a fixture, not part of the public API.
6+
package samplecpu
7+
8+
import "github.com/klauspost/cpuid/v2/topology"
9+
10+
// Topology returns a synthetic topology: one package split into two groups (as
11+
// a hybrid CPU's tiles or an AMD CPU's CCDs would be) — a performance group of
12+
// two SMT cores and an efficiency group of four single-threaded cores, each
13+
// core with private L1/L2 and each group with a shared L3. It is not read from
14+
// any real CPU; use cpuid.ScanTopology or cpuid.CPU.Topology for that.
15+
func Topology() *topology.System {
16+
core := func(id int, ct topology.CoreType, threads ...int) topology.Core {
17+
c := topology.Core{ID: id, Type: ct, Caches: []topology.Cache{
18+
{Level: 1, Type: topology.Data, Size: 32 << 10, LineSize: 64, SharedThreads: len(threads)},
19+
{Level: 1, Type: topology.Instruction, Size: 32 << 10, LineSize: 64, SharedThreads: len(threads)},
20+
{Level: 2, Type: topology.Unified, Size: 1 << 20, LineSize: 64, SharedThreads: len(threads)},
21+
}}
22+
for _, t := range threads {
23+
c.Threads = append(c.Threads, topology.Thread{APICID: uint32(t), ID: t})
24+
}
25+
return c
26+
}
27+
group := func(id, shared int, cores ...topology.Core) topology.Group {
28+
return topology.Group{ID: id, Cores: cores,
29+
Caches: []topology.Cache{{Level: 3, Type: topology.Unified, Size: 16 << 20, LineSize: 64, SharedThreads: shared}}}
30+
}
31+
return &topology.System{
32+
Vendor: "GenuineIntel",
33+
Scanned: true,
34+
Online: 8,
35+
Packages: []topology.Package{{Groups: []topology.Group{
36+
group(0, 4, core(0, topology.Performance, 0, 1), core(1, topology.Performance, 2, 3)),
37+
group(1, 4,
38+
core(2, topology.Efficiency, 4), core(3, topology.Efficiency, 5),
39+
core(4, topology.Efficiency, 6), core(5, topology.Efficiency, 7)),
40+
}}},
41+
}
42+
}

0 commit comments

Comments
 (0)