Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

sysfs,cpuallocator: add support for hybrid core discovery, preferred allocation. #295

Merged
merged 6 commits into from
Mar 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 86 additions & 13 deletions pkg/cpuallocator/allocator.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,10 +88,10 @@ type topologyCache struct {
pkg map[idset.ID]cpuset.CPUSet
node map[idset.ID]cpuset.CPUSet
core map[idset.ID]cpuset.CPUSet
kind map[sysfs.CoreKind]cpuset.CPUSet

cpuPriorities cpuPriorities // CPU priority mapping
clusters []*cpuCluster // CPU clusters

}

type cpuPriorities [NumCPUPriorities]cpuset.CPUSet
Expand All @@ -101,6 +101,7 @@ type cpuCluster struct {
die idset.ID
cluster idset.ID
cpus cpuset.CPUSet
kind sysfs.CoreKind
}

// IDFilter helps filtering Ids.
Expand Down Expand Up @@ -159,7 +160,14 @@ func (a *allocatorHelper) takeIdlePackages() {
// pick idle packages
pkgs := pickIds(a.sys.PackageIDs(),
func(id idset.ID) bool {
// Consider a package idle if all online preferred CPUs are idle.
// In particular, on hybrid core architectures exclude
// - exclude E-cores from allocations with <= PriorityNormal preference
// - exclude P-cores from allocations with > PriorityLow preferences
cset := a.topology.pkg[id].Difference(offline)
if a.prefer < NumCPUPriorities {
cset = cset.Intersection(a.topology.cpuPriorities[a.prefer])
}
return cset.Intersection(a.from).Equals(cset)
})

Expand All @@ -177,6 +185,9 @@ func (a *allocatorHelper) takeIdlePackages() {
// take as many idle packages as we need/can
for _, id := range pkgs {
cset := a.topology.pkg[id].Difference(offline)
if a.prefer < NumCPUPriorities {
cset = cset.Intersection(a.topology.cpuPriorities[a.prefer])
}
a.Debug(" => considering package %v (#%s)...", id, cset)
if a.cnt >= cset.Size() {
a.Debug(" => taking package %v...", id)
Expand All @@ -200,6 +211,17 @@ func (a *allocatorHelper) takeIdleClusters() {
var (
offline = a.sys.OfflineCPUs()
pickIdle = func(c *cpuCluster) (bool, cpuset.CPUSet) {
// we only take E-clusters for low-prio requests
if a.prefer != PriorityLow && c.kind == sysfs.EfficientCore {
a.Debug(" - omit %s, CPU preference is %s", c, a.prefer)
return false, emptyCPUSet
}
// we only take P-clusters for other than low-prio requests
if a.prefer == PriorityLow && c.kind == sysfs.PerformanceCore {
a.Debug(" - omit %s, CPU preference is %s", c, a.prefer)
return false, emptyCPUSet
}

// we only take fully idle clusters
cset := c.cpus.Difference(offline)
free := cset.Intersection(a.from)
Expand Down Expand Up @@ -692,9 +714,17 @@ func (c *topologyCache) discoverCPUPriorities(sys sysfs.System) {
cpuPriorities = c.discoverCpufreqPriority(sys, id)
}

ecores := c.kind[sysfs.EfficientCore]
ocores := sys.OnlineCPUs().Difference(ecores)

for p, cpus := range cpuPriorities {
source := map[bool]string{true: "sst", false: "cpufreq"}[sstActive]
cset := sysfs.CPUSetFromIDSet(idset.NewIDSet(cpus...))

if p != int(PriorityLow) && ocores.Size() > 0 {
cset = cset.Difference(ecores)
}

log.Debug("package #%d (%s): %d %s priority cpus (%v)", id, source, len(cpus), CPUPriority(p), cset)
prio[p] = prio[p].Union(cset)
}
Expand Down Expand Up @@ -826,7 +856,7 @@ func (c *topologyCache) sstClosPriority(sys sysfs.System, pkgID idset.ID) map[in
func (c *topologyCache) discoverCpufreqPriority(sys sysfs.System, pkgID idset.ID) [NumCPUPriorities][]idset.ID {
var prios [NumCPUPriorities][]idset.ID

// Group cpus by base frequency and energy performance profile
// Group cpus by base frequency, core kind and energy performance profile
freqs := map[uint64][]idset.ID{}
epps := map[sysfs.EPP][]idset.ID{}
cpuIDs := c.pkg[pkgID].List()
Expand Down Expand Up @@ -874,14 +904,19 @@ func (c *topologyCache) discoverCpufreqPriority(sys sysfs.System, pkgID idset.ID
}
}

// All cpus NOT in the lowest performance epp are considered high prio
// All E-cores are unconditionally considered low prio.
// All cpus NOT in the lowest performance epp are considered high prio.
// NOTE: higher EPP value denotes lower performance preference
if len(eppList) > 1 {
epp := cpu.EPP()
if int(epp) < eppList[len(eppList)-1] {
p = PriorityHigh
} else {
p = PriorityLow
if cpu.CoreKind() == sysfs.EfficientCore {
p = PriorityLow
} else {
if len(eppList) > 1 {
epp := cpu.EPP()
if int(epp) < eppList[len(eppList)-1] {
p = PriorityHigh
} else {
p = PriorityLow
}
}
}

Expand All @@ -907,18 +942,24 @@ func (c *topologyCache) discoverCPUClusters(sys sysfs.System) {
die: die,
cluster: cl,
cpus: cpus,
kind: sys.CPU(cpus.List()[0]).CoreKind(),
})
}
}
if len(clusters) > 1 {
log.Debug("package #%d has %d clusters:", id, len(clusters))
for _, cl := range clusters {
log.Debug(" die #%d, cluster #%d: cpus %s",
cl.die, cl.cluster, cl.cpus)
log.Debug(" die #%d, cluster #%d: %s cpus %s",
cl.die, cl.cluster, cl.kind, cl.cpus)
}
c.clusters = append(c.clusters, clusters...)
}
}

c.kind = map[sysfs.CoreKind]cpuset.CPUSet{}
for _, kind := range sys.CoreKinds() {
c.kind[kind] = sys.CoreKindCPUs(kind)
}
}

func (p CPUPriority) String() string {
Expand All @@ -943,6 +984,38 @@ func (c *cpuPriorities) cmpCPUSet(csetA, csetB cpuset.CPUSet, prefer CPUPriority
return 0
}

// For low prio request, favor cpuset with the tightest fit.
if cpuCnt > 0 && prefer == PriorityLow {
prefA := csetA.Intersection(c[prefer]).Size()
prefB := csetB.Intersection(c[prefer]).Size()
// both sets have enough preferred CPUs, return the smaller one (tighter fit)
if prefA >= cpuCnt && prefB >= cpuCnt {
return prefB - prefA
}
// only one set has enough preferred CPUs, return the bigger/only one
if prefA >= cpuCnt || prefB >= cpuCnt {
return prefA - prefB
}
}

// For high prio request, favor the tightest fit falling back to normal prio
if cpuCnt > 0 && prefer == PriorityHigh {
prefA := csetA.Intersection(c[prefer]).Size()
prefB := csetB.Intersection(c[prefer]).Size()
if prefA == 0 && prefB == 0 {
prefA = csetA.Intersection(c[PriorityNormal]).Size()
prefB = csetB.Intersection(c[PriorityNormal]).Size()
}
// both sets have enough preferred CPUs, return the smaller one (tighter fit)
if prefA >= cpuCnt && prefB >= cpuCnt {
return prefB - prefA
}
// only one set has enough preferred CPUs, return the bigger/only one
if prefA >= cpuCnt || prefB >= cpuCnt {
return prefA - prefB
}
}

// Favor cpuset having CPUs with priorities equal to or lower than what was requested
for prio := prefer; prio < NumCPUPriorities; prio++ {
prefA := csetA.Intersection(c[prio]).Size()
Expand Down Expand Up @@ -984,6 +1057,6 @@ func (c *cpuCluster) HasSmallerIDsThan(o *cpuCluster) bool {
}

func (c *cpuCluster) String() string {
return fmt.Sprintf("cluster #%d/%d/%d, %d CPUs (%s)", c.pkg, c.die, c.cluster,
c.cpus.Size(), c.cpus)
return fmt.Sprintf("cluster #%d/%d/%d, %d %s CPUs (%s)", c.pkg, c.die, c.cluster,
c.cpus.Size(), c.kind, c.cpus)
}
Loading