mirror of
https://github.com/JakeHillion/scx.git
synced 2024-11-29 20:50:22 +00:00
Merge branch 'sched-ext:main' into main
This commit is contained in:
commit
6c5d85401d
@ -17,9 +17,20 @@
|
||||
//! hexadecimal string:
|
||||
//!
|
||||
//!```
|
||||
//! let all_zeroes = Cpumask::new();
|
||||
//! let all_zeroes = cpumask::new();
|
||||
//! let str = "0xff00ff00";
|
||||
//! let from_str_mask = Cpumask::from_string(str);
|
||||
//! let from_str_mask = cpumask::from_string(str);
|
||||
//!```
|
||||
//!
|
||||
//! The hexadecimal string also supports the special values "none" and "all",
|
||||
//! respectively to specify no CPU (empty mask) or all CPUs (full mask):
|
||||
//!
|
||||
//!```
|
||||
//! let str = "none";
|
||||
//! let all_zeroes = cpumask::from_string(str);
|
||||
//!
|
||||
//! let str = "all";
|
||||
//! let all_ones = cpumask::from_string(str);
|
||||
//!```
|
||||
//!
|
||||
//! A Cpumask can be queried and updated using its helper functions:
|
||||
@ -75,6 +86,17 @@ impl Cpumask {
|
||||
|
||||
/// Build a Cpumask object from a hexadecimal string.
|
||||
pub fn from_str(cpumask: &String) -> Result<Cpumask> {
|
||||
match cpumask.as_str() {
|
||||
"none" => {
|
||||
let mask = bitvec![u64, Lsb0; 0; *NR_CPU_IDS];
|
||||
return Ok(Self { mask });
|
||||
},
|
||||
"all" => {
|
||||
let mask = bitvec![u64, Lsb0; 1; *NR_CPU_IDS];
|
||||
return Ok(Self { mask });
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
let hex_str = {
|
||||
let mut tmp_str = cpumask
|
||||
.strip_prefix("0x")
|
||||
@ -114,7 +136,9 @@ impl Cpumask {
|
||||
}
|
||||
|
||||
pub fn from_vec(vec: Vec<u64>) -> Self {
|
||||
Self { mask: BitVec::from_vec(vec) }
|
||||
Self {
|
||||
mask: BitVec::from_vec(vec),
|
||||
}
|
||||
}
|
||||
|
||||
/// Return a slice of u64's whose bits reflect the Cpumask.
|
||||
|
@ -61,6 +61,7 @@ pub mod ravg;
|
||||
mod topology;
|
||||
pub use topology::Cache;
|
||||
pub use topology::Core;
|
||||
pub use topology::CoreType;
|
||||
pub use topology::Cpu;
|
||||
pub use topology::Node;
|
||||
pub use topology::Topology;
|
||||
|
@ -74,7 +74,7 @@ use anyhow::Result;
|
||||
use glob::glob;
|
||||
use sscanf::sscanf;
|
||||
use std::collections::BTreeMap;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::path::Path;
|
||||
use std::slice::Iter;
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
@ -97,7 +97,7 @@ lazy_static::lazy_static! {
|
||||
|
||||
#[derive(Debug, Clone, Eq, Hash, PartialEq)]
|
||||
pub enum CoreType {
|
||||
Big,
|
||||
Big { turbo: bool },
|
||||
Little,
|
||||
}
|
||||
|
||||
@ -421,7 +421,7 @@ fn create_insert_cpu(
|
||||
cpu_id: usize,
|
||||
node: &mut Node,
|
||||
online_mask: &Cpumask,
|
||||
avg_cpu_freq: Option<usize>,
|
||||
avg_cpu_freq: Option<(usize, usize)>,
|
||||
) -> Result<()> {
|
||||
// CPU is offline. The Topology hierarchy is read-only, and assumes
|
||||
// that hotplug will cause the scheduler to restart. Thus, we can
|
||||
@ -463,16 +463,16 @@ fn create_insert_cpu(
|
||||
});
|
||||
|
||||
let core_type = match avg_cpu_freq {
|
||||
Some(avg_freq) => {
|
||||
if max_freq >= avg_freq {
|
||||
CoreType::Big
|
||||
} else if max_freq < avg_freq {
|
||||
CoreType::Little
|
||||
Some((avg_base_freq, top_max_freq)) => {
|
||||
if max_freq == top_max_freq {
|
||||
CoreType::Big { turbo: true }
|
||||
} else if base_freq >= avg_base_freq {
|
||||
CoreType::Big { turbo: false }
|
||||
} else {
|
||||
CoreType::Big
|
||||
CoreType::Little
|
||||
}
|
||||
}
|
||||
None => CoreType::Big,
|
||||
None => CoreType::Big { turbo: false },
|
||||
};
|
||||
|
||||
let core = cache.cores.entry(core_id).or_insert(Core {
|
||||
@ -525,24 +525,28 @@ fn read_cpu_ids() -> Result<Vec<usize>> {
|
||||
Ok(cpu_ids)
|
||||
}
|
||||
|
||||
fn avg_cpu_freq() -> Option<usize> {
|
||||
let mut avg_freq = 0;
|
||||
// Return the average base frequency across all CPUs and the highest maximum frequency.
|
||||
fn avg_cpu_freq() -> Option<(usize, usize)> {
|
||||
let mut top_max_freq = 0;
|
||||
let mut avg_base_freq = 0;
|
||||
let mut nr_cpus = 0;
|
||||
let cpu_paths = glob("/sys/devices/system/cpu/cpu[0-9]*").ok()?;
|
||||
for cpu_path in cpu_paths.filter_map(Result::ok) {
|
||||
let mut buf = PathBuf::from(&cpu_path);
|
||||
buf.push("cpufreq");
|
||||
buf.push("scaling_max_freq");
|
||||
let max_freq = read_file_usize(buf.as_path()).unwrap_or(0);
|
||||
if max_freq > 0 {
|
||||
avg_freq += max_freq;
|
||||
let freq_path = cpu_path.join("cpufreq");
|
||||
let max_freq = read_file_usize(&freq_path.join("scaling_max_freq")).unwrap_or(0);
|
||||
let base_freq = read_file_usize(&freq_path.join("base_frequency")).unwrap_or(max_freq);
|
||||
if base_freq > 0 {
|
||||
if max_freq > top_max_freq {
|
||||
top_max_freq = max_freq;
|
||||
}
|
||||
avg_base_freq += base_freq;
|
||||
nr_cpus += 1;
|
||||
}
|
||||
}
|
||||
if avg_freq == 0 {
|
||||
if avg_base_freq == 0 {
|
||||
return None;
|
||||
}
|
||||
Some(avg_freq / nr_cpus)
|
||||
Some((avg_base_freq / nr_cpus, top_max_freq))
|
||||
}
|
||||
|
||||
fn create_default_node(online_mask: &Cpumask) -> Result<Vec<Node>> {
|
||||
|
@ -48,6 +48,7 @@ use scx_utils::scx_ops_load;
|
||||
use scx_utils::scx_ops_open;
|
||||
use scx_utils::uei_exited;
|
||||
use scx_utils::uei_report;
|
||||
use scx_utils::CoreType;
|
||||
use scx_utils::Cpumask;
|
||||
use scx_utils::Topology;
|
||||
use scx_utils::UserExitInfo;
|
||||
@ -65,61 +66,28 @@ enum Powermode {
|
||||
fn get_primary_cpus(mode: Powermode) -> std::io::Result<Vec<usize>> {
|
||||
let topo = Topology::new().unwrap();
|
||||
|
||||
// Iterate over each CPU directory and collect CPU ID and its base operational frequency to
|
||||
// distinguish between fast and slow cores.
|
||||
let mut cpu_freqs = Vec::new();
|
||||
let mut max_cpu_freqs = Vec::new();
|
||||
for core in topo.cores().into_iter() {
|
||||
for (cpu_id, cpu) in core.cpus() {
|
||||
cpu_freqs.push((*cpu_id, cpu.base_freq()));
|
||||
max_cpu_freqs.push((*cpu_id, cpu.max_freq()));
|
||||
}
|
||||
}
|
||||
if cpu_freqs.is_empty() {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
let cpus: Vec<usize> = topo
|
||||
.cores()
|
||||
.into_iter()
|
||||
.flat_map(|core| core.cpus())
|
||||
.filter_map(|(cpu_id, cpu)| match (&mode, &cpu.core_type) {
|
||||
// Turbo mode: only add turbo-boosted CPUs
|
||||
(Powermode::Turbo, CoreType::Big { turbo: true }) |
|
||||
// Performance mode: add all the Big CPUs (either Turbo or non-Turbo)
|
||||
(Powermode::Performance, CoreType::Big { .. }) |
|
||||
// Powersave mode: add all the Little CPUs
|
||||
(Powermode::Powersave, CoreType::Little) => Some(*cpu_id),
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Find the smallest maximum frequency.
|
||||
let min_freq = cpu_freqs.iter().map(|&(_, freq)| freq).min().unwrap();
|
||||
|
||||
// Find the highest maximum frequency.
|
||||
let max_freq = max_cpu_freqs.iter().map(|&(_, freq)| freq).max().unwrap();
|
||||
|
||||
// Check if all CPUs have the smallest frequency.
|
||||
let all_have_min_freq = cpu_freqs.iter().all(|&(_, freq)| freq == min_freq);
|
||||
|
||||
let selected_cpu_ids: Vec<usize> = if mode == Powermode::Turbo {
|
||||
// Turbo: return the CPUs with the highest max frequency.
|
||||
max_cpu_freqs
|
||||
.into_iter()
|
||||
.filter(|&(_, freq)| freq == max_freq)
|
||||
.map(|(cpu_id, _)| cpu_id)
|
||||
.collect()
|
||||
} else if all_have_min_freq || mode == Powermode::Powersave {
|
||||
// Powersave: return the CPUs with the smallest base frequency.
|
||||
cpu_freqs
|
||||
.into_iter()
|
||||
.filter(|&(_, freq)| freq == min_freq)
|
||||
.map(|(cpu_id, _)| cpu_id)
|
||||
.collect()
|
||||
} else if mode == Powermode::Performance {
|
||||
// Performance: return the CPUs with a base frequency greater than the minimum.
|
||||
cpu_freqs
|
||||
.into_iter()
|
||||
.filter(|&(_, freq)| freq > min_freq)
|
||||
.map(|(cpu_id, _)| cpu_id)
|
||||
.collect()
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
|
||||
Ok(selected_cpu_ids)
|
||||
Ok(cpus)
|
||||
}
|
||||
|
||||
// Convert an array of CPUs to the corresponding cpumask of any arbitrary size.
|
||||
fn cpus_to_cpumask(cpus: &Vec<usize>) -> String {
|
||||
if cpus.is_empty() {
|
||||
return String::from("0x0");
|
||||
return String::from("all");
|
||||
}
|
||||
|
||||
// Determine the maximum CPU ID to create a sufficiently large byte vector.
|
||||
@ -157,11 +125,7 @@ fn parse_cpumask(cpu_str: &str) -> Result<Cpumask, anyhow::Error> {
|
||||
}
|
||||
"auto" => Cpumask::new(),
|
||||
_ if !cpu_str.is_empty() => Cpumask::from_str(&cpu_str.to_string()),
|
||||
_ => {
|
||||
let mut cpumask = Cpumask::new()?;
|
||||
cpumask.setall();
|
||||
Ok(cpumask)
|
||||
}
|
||||
_ => Cpumask::from_str(&"all".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
@ -222,9 +186,10 @@ struct Opts {
|
||||
/// - "performance" = automatically detect and use the fastest CPUs
|
||||
/// - "powersave" = automatically detect and use the slowest CPUs
|
||||
/// - "auto" = automatically detect the CPUs based on the current energy profile
|
||||
/// - "all" = all CPUs assigned to the primary domain
|
||||
///
|
||||
/// By default all CPUs are used for the primary scheduling domain.
|
||||
#[clap(short = 'm', long, default_value = "", value_parser = parse_cpumask)]
|
||||
#[clap(short = 'm', long, default_value = "all", value_parser = parse_cpumask)]
|
||||
primary_domain: Cpumask,
|
||||
|
||||
/// Disable L2 cache awareness.
|
||||
@ -437,8 +402,7 @@ impl<'a> Scheduler<'a> {
|
||||
// If no turbo-boosted CPUs are selected, use an empty CPU mask, so that tasks are
|
||||
// scheduled directly to the primary domain, bypassing the turbo boost domain.
|
||||
if cpus.is_empty() {
|
||||
let mut cpumask = Cpumask::new()?;
|
||||
cpumask
|
||||
Cpumask::new()?
|
||||
} else {
|
||||
Cpumask::from_str(&cpus_to_cpumask(&cpus))?
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user