Skip to content
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
5 changes: 5 additions & 0 deletions BENCHMARKING.md
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,11 @@ recommended to combine this with `--no-fork`. For example:
└─ 481.09 Link
```

If a benchmark has shown a significant increase in say CPU cycles or instructions, then it can be
useful to check which phase or phases that increase has occurred in. You can get per-phase cycle and
instruction counts by running with `--time=cycles,instructions`. To see the full list of counters,
search `args.rs` for "branch-misses".

### Samply

To look for hot functions and to check how the work distribution looks between threads, you can use
Expand Down
28 changes: 24 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ object = { version = "0.37.0", default-features = false, features = [
"archive",
] }
os_info = "3.0.0"
perf-event = "0.4.8"
postcard = { version = "1.1.1", features = ["use-std"] }
rayon = "1.2.1"
rstest = "0.25.0"
Expand Down
3 changes: 3 additions & 0 deletions libwild/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ winnow = { workspace = true }
zstd = { workspace = true }
glob = "0.3.2"

[target.'cfg(target_os = "linux")'.dependencies]
perf-event = { workspace = true }

[dev-dependencies]
ar = "0.9.0"

Expand Down
47 changes: 44 additions & 3 deletions libwild/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ pub struct Args {
pub(crate) sym_info: Option<String>,
pub(crate) merge_strings: bool,
pub(crate) debug_fuel: Option<AtomicI64>,
pub(crate) time_phases: bool,
pub(crate) validate_output: bool,
pub(crate) version_script_path: Option<PathBuf>,
pub(crate) debug_address: Option<u64>,
Expand Down Expand Up @@ -82,6 +81,10 @@ pub struct Args {
/// specified substrings.
pub(crate) gc_stats_ignore: Vec<String>,

/// If `Some`, then we'll time how long each phase takes. We'll also measure the specified
/// counters, if any.
pub(crate) time_phase_options: Option<Vec<CounterKind>>,

pub(crate) verbose_gc_stats: bool,

pub(crate) save_dir: SaveDir,
Expand All @@ -107,6 +110,19 @@ pub struct Args {
jobserver_client: Option<Client>,
}

#[derive(Clone, Copy)]
pub enum CounterKind {
Cycles,
Instructions,
CacheMisses,
BranchMisses,
PageFaults,
PageFaultsMinor,
PageFaultsMajor,
L1dRead,
L1dMiss,
}

/// Represents a command-line argument that specifies the number of threads to use,
/// triggering activation of the thread pool.
pub struct ActivatedArgs {
Expand Down Expand Up @@ -267,7 +283,7 @@ impl Default for Args {
is_dynamic_executable: AtomicBool::new(false),
dynamic_linker: None,
output_kind: None,
time_phases: false,
time_phase_options: None,
num_threads: None,
strip_all: false,
strip_debug: false,
Expand Down Expand Up @@ -522,8 +538,10 @@ pub(crate) fn parse<F: Fn() -> I, S: AsRef<str>, I: Iterator<Item = S>>(input: F
"none" => {}
other => warn_unsupported(&format!("--icf={other}"))?,
}
} else if let Some(rest) = long_arg_split_prefix("time=") {
args.time_phase_options = Some(parse_time_phase_options(rest)?);
} else if long_arg_eq("time") {
args.time_phases = true;
args.time_phase_options = Some(Vec::new());
} else if let Some(rest) = long_arg_split_prefix("threads=") {
args.num_threads = Some(NonZeroUsize::try_from(rest.parse::<usize>()?)?);
} else if long_arg_eq("threads") {
Expand Down Expand Up @@ -1072,6 +1090,29 @@ fn warn_unsupported(opt: &str) -> Result {
Ok(())
}

fn parse_time_phase_options(input: &str) -> Result<Vec<CounterKind>> {
input.split(',').map(|s| s.parse()).collect()
}

impl FromStr for CounterKind {
type Err = crate::error::Error;

fn from_str(s: &str) -> Result<Self> {
Ok(match s {
"cycles" => CounterKind::Cycles,
"instructions" => CounterKind::Instructions,
"cache-misses" => CounterKind::CacheMisses,
"branch-misses" => CounterKind::BranchMisses,
"page-faults" => CounterKind::PageFaults,
"page-faults-minor" => CounterKind::PageFaultsMinor,
"page-faults-major" => CounterKind::PageFaultsMajor,
"l1d-read" => CounterKind::L1dRead,
"l1d-miss" => CounterKind::L1dMiss,
other => bail!("Unsupported performance counter `{other}`"),
})
}
}

#[cfg(test)]
mod tests {
use super::SILENTLY_IGNORED_FLAGS;
Expand Down
12 changes: 10 additions & 2 deletions libwild/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ pub(crate) mod output_section_part_map;
pub(crate) mod output_trace;
pub(crate) mod parsing;
pub(crate) mod part_id;
#[cfg(target_os = "linux")]
pub(crate) mod perf;
#[cfg(not(target_os = "linux"))]
#[path = "perf_unsupported.rs"]
pub(crate) mod perf;
pub(crate) mod program_segments;
pub(crate) mod resolution;
pub(crate) mod riscv64;
Expand Down Expand Up @@ -68,6 +73,9 @@ use tracing_subscriber::util::SubscriberInitExt;
/// Runs the linker and cleans up associated resources. Only use this function if you've OK with
/// waiting for cleanup.
pub fn run(args: Args) -> error::Result {
// Note, we need to setup tracing before we activate the thread pool. In particular, we need to
// initialise the timing module before the worker threads are started, otherwise the threads
// won't contribute to counters such as --time=cycles,instructions etc.
setup_tracing(&args)?;
let args = args.activate_thread_pool()?;
let linker = Linker::new();
Expand All @@ -79,8 +87,8 @@ pub fn run(args: Args) -> error::Result {
/// called once and only if nothing else has already set the global tracing dispatcher. Calling this
/// is optional. If it isn't called, no tracing-based features will function. e.g. --time.
pub fn setup_tracing(args: &Args) -> Result<(), AlreadyInitialised> {
if args.time_phases {
timing::init_tracing()
if let Some(opts) = args.time_phase_options.as_ref() {
timing::init_tracing(opts)
} else if args.print_allocations.is_some() {
debug_trace::init()
} else {
Expand Down
60 changes: 60 additions & 0 deletions libwild/src/perf.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
use crate::args::CounterKind;

pub(crate) struct CounterList {
counters: Vec<perf_event::Counter>,
}

impl CounterList {
pub(crate) fn from_kinds(opts: &[CounterKind]) -> Self {
let counters = opts
.iter()
.filter_map(|kind| {
perf_event::Builder::new()
.inherit(true)
.kind(counter_to_perf_event(*kind))
.build()
.ok()
})
.collect();

CounterList { counters }
}

pub(crate) fn start(&mut self) {
for counter in &mut self.counters {
let _ = counter.reset();
let _ = counter.enable();
}
}

pub(crate) fn disable_and_read(&mut self) -> Vec<u64> {
self.counters
.iter_mut()
.filter_map(|counter| counter.disable().ok().and_then(|()| counter.read().ok()))
.collect()
}
}

fn counter_to_perf_event(kind: CounterKind) -> perf_event::events::Event {
match kind {
CounterKind::Cycles => perf_event::events::Hardware::CPU_CYCLES.into(),
CounterKind::Instructions => perf_event::events::Hardware::INSTRUCTIONS.into(),
CounterKind::CacheMisses => perf_event::events::Hardware::CACHE_MISSES.into(),
CounterKind::BranchMisses => perf_event::events::Hardware::BRANCH_MISSES.into(),
CounterKind::PageFaults => perf_event::events::Software::PAGE_FAULTS.into(),
CounterKind::PageFaultsMinor => perf_event::events::Software::PAGE_FAULTS_MIN.into(),
CounterKind::PageFaultsMajor => perf_event::events::Software::PAGE_FAULTS_MAJ.into(),
CounterKind::L1dRead => perf_event::events::Cache {
which: perf_event::events::WhichCache::L1D,
operation: perf_event::events::CacheOp::READ,
result: perf_event::events::CacheResult::ACCESS,
}
.into(),
CounterKind::L1dMiss => perf_event::events::Cache {
which: perf_event::events::WhichCache::L1D,
operation: perf_event::events::CacheOp::READ,
result: perf_event::events::CacheResult::MISS,
}
.into(),
}
}
19 changes: 19 additions & 0 deletions libwild/src/perf_unsupported.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
use crate::args::CounterKind;

pub(crate) struct CounterList {}

impl CounterList {
pub(crate) fn from_kinds(_opts: &[CounterKind]) -> Self {
CounterList {}
}

pub(crate) fn start(&self) {
let _ = self;
}

#[allow(clippy::unused_self)]
pub(crate) fn disable_and_read(&self) -> Vec<u64> {
let _ = self;
Vec::new()
}
}
Loading