Skip to content

Commit

Permalink
Implement an twiggy monos for finding monomorphization bloat
Browse files Browse the repository at this point in the history
Fixes #6
  • Loading branch information
fitzgen committed Apr 18, 2018
1 parent fe437dd commit aa3330d
Show file tree
Hide file tree
Showing 14 changed files with 420 additions and 1 deletion.
165 changes: 165 additions & 0 deletions analyze/analyze.rs
Original file line number Diff line number Diff line change
Expand Up @@ -526,3 +526,168 @@ pub fn paths(items: &mut ir::Items, opts: &opt::Paths) -> Result<Box<traits::Emi

Ok(Box::new(paths) as Box<traits::Emit>)
}

#[derive(Debug)]
struct Monos {
monos: Vec<MonosEntry>,
}

#[derive(Debug, PartialEq, Eq)]
struct MonosEntry {
generic: String,
insts: Vec<ir::Id>,
total: u32,
approx_potential_savings: u32,
}

impl PartialOrd for MonosEntry {
fn partial_cmp(&self, rhs: &MonosEntry) -> Option<cmp::Ordering> {
Some(self.cmp(rhs))
}
}

impl Ord for MonosEntry {
fn cmp(&self, rhs: &MonosEntry) -> cmp::Ordering {
rhs.approx_potential_savings
.cmp(&self.approx_potential_savings)
.then(self.insts.cmp(&rhs.insts))
.then(self.generic.cmp(&rhs.generic))
}
}

impl traits::Emit for Monos {
fn emit_text(&self, items: &ir::Items, dest: &mut io::Write) -> Result<(), traits::Error> {
let mut table = Table::with_header(vec![
(Align::Right, "Apprx. Bloat Bytes".into()),
(Align::Right, "Apprx. Bloat %".into()),
(Align::Right, "Bytes".into()),
(Align::Right, "%".into()),
(Align::Left, "Monomorphizations".to_string()),
]);

for entry in &self.monos {
let total_percent = (f64::from(entry.total)) / (f64::from(items.size())) * 100.0;
let approx_potential_savings_percent =
(f64::from(entry.approx_potential_savings)) / (f64::from(items.size())) * 100.0;
table.add_row(vec![
entry.approx_potential_savings.to_string(),
format!("{:.2}%", approx_potential_savings_percent),
entry.total.to_string(),
format!("{:.2}%", total_percent),
entry.generic.clone(),
]);

for &id in &entry.insts {
let item = &items[id];

let size = item.size();
let size_percent = (f64::from(size)) / (f64::from(items.size())) * 100.0;

table.add_row(vec![
"".into(),
"".into(),
size.to_string(),
format!("{:.2}%", size_percent),
format!(" {}", item.name()),
]);
}
}

write!(dest, "{}", &table)?;
Ok(())
}

fn emit_json(&self, items: &ir::Items, dest: &mut io::Write) -> Result<(), traits::Error> {
let mut arr = json::array(dest)?;

for entry in &self.monos {
let mut obj = arr.object()?;
obj.field("generic", &entry.generic[..])?;

obj.field(
"approximate_monomorphization_bloat_bytes",
entry.approx_potential_savings,
)?;
let approx_potential_savings_percent =
(f64::from(entry.approx_potential_savings)) / (f64::from(items.size())) * 100.0;
obj.field(
"approximate_monomorphization_bloat_percent",
approx_potential_savings_percent,
)?;

obj.field("total_size", entry.total)?;
let total_percent = (f64::from(entry.total)) / (f64::from(items.size())) * 100.0;
obj.field("total_size_percent", total_percent)?;

let mut monos = obj.array("monomorphizations")?;
for &id in &entry.insts {
let item = &items[id];

let mut obj = monos.object()?;
obj.field("name", item.name())?;

let size = item.size();
obj.field("shallow_size", size)?;

let size_percent = (f64::from(size)) / (f64::from(items.size())) * 100.0;
obj.field("shallow_size_percent", size_percent)?;
}
}

Ok(())
}
}

/// Find all retaining paths for the given items.
pub fn monos(items: &mut ir::Items, opts: &opt::Monos) -> Result<Box<traits::Emit>, traits::Error> {
let mut monos = BTreeMap::new();
for item in items.iter() {
if let Some(generic) = item.monomorphization_of() {
monos
.entry(generic)
.or_insert(BTreeSet::new())
.insert(item.id());
}
}

let mut monos: Vec<_> = monos
.into_iter()
.filter_map(|(generic, insts)| {
if insts.len() <= 1 {
return None;
}

let max = insts.iter().map(|id| items[*id].size()).max().unwrap();
let total = insts.iter().map(|id| items[*id].size()).sum();
let size_per_inst = total / (insts.len() as u32);
let approx_potential_savings =
cmp::min(size_per_inst * (insts.len() as u32 - 1), total - max);

let generic = generic.to_string();

let mut insts: Vec<_> = insts.into_iter().collect();
insts.sort_by(|a, b| {
let a = &items[*a];
let b = &items[*b];
b.size().cmp(&a.size())
});
insts.truncate(if opts.only_generics {
0
} else {
opts.max_monos as usize
});

Some(MonosEntry {
generic,
insts,
total,
approx_potential_savings,
})
})
.collect();

monos.sort();
monos.truncate(opts.max_generics as usize);

Ok(Box::new(Monos { monos }) as Box<traits::Emit>)
}
13 changes: 12 additions & 1 deletion ir/ir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,17 @@ impl Item {
&self.name
}
}

/// The the name of the generic function that this is a monomorphization of
/// (if any).
#[inline]
pub fn monomorphization_of(&self) -> Option<&str> {
if let ItemKind::Code(ref code) = self.kind {
code.monomorphization_of()
} else {
None
}
}
}

impl PartialOrd for Item {
Expand Down Expand Up @@ -562,7 +573,7 @@ impl Code {
// If the '<' doesn't come before the '>', then we aren't looking at a
// generic function instantiation. If there isn't anything proceeding
// the '<', then we aren't looking at a generic function instantiation
// (most likely looking at a trait method's implementation, like
// (most likely looking at a Rust trait method's implementation, like
// `<MyType as SomeTrait>::trait_method()`).
if close_bracket < open_bracket || open_bracket == 0 {
return None;
Expand Down
78 changes: 78 additions & 0 deletions opt/definitions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ pub enum Options {
/// graph.
#[structopt(name = "paths")]
Paths(Paths),

/// List the generic function monomorphizations that are contributing to
/// code bloat.
#[structopt(name = "monos")]
Monos(Monos),
}

/// List the top code size offenders in a binary.
Expand Down Expand Up @@ -237,3 +242,76 @@ impl Paths {
self.max_paths = max_paths;
}
}

/// List the generic function monomorphizations that are contributing to
/// code bloat.
#[derive(Clone, Debug, Default)]
#[derive(StructOpt)]
#[wasm_bindgen]
pub struct Monos {
/// The path to the input binary to size profile.
#[structopt(parse(from_os_str))]
pub input: path::PathBuf,

/// The destination to write the output to. Defaults to `stdout`.
#[structopt(short = "o", default_value = "-")]
pub output_destination: OutputDestination,

/// The format the output should be written in.
#[structopt(short = "f", long = "format", default_value = "text")]
pub output_format: traits::OutputFormat,

/// Hide individual monomorphizations and only show the generic functions.
#[structopt(short = "g", long = "only-generics")]
pub only_generics: bool,

/// The maximum number of generics to list.
#[structopt(short = "m", long = "max-generics", default_value = "10")]
pub max_generics: u32,

/// The maximum number of individual monomorphizations to list for each
/// generic function.
#[structopt(short = "n", long = "max-monos", default_value = "10")]
pub max_monos: u32,
}

#[wasm_bindgen]
impl Monos {
/// Construct a new, default `Monos`.
pub fn new() -> Monos {
Monos::default()
}

/// Hide individual monomorphizations and only show the generic functions.
pub fn only_generics(&self) -> bool {
self.only_generics
}

/// The maximum number of generics to list.
pub fn max_generics(&self) -> u32 {
self.max_generics
}

/// The maximum number of individual monomorphizations to list for each
/// generic function.
pub fn max_monos(&self) -> u32 {
self.max_monos
}

/// Set whether to hide individual monomorphizations and only show the
/// generic functions.
pub fn set_only_generics(&mut self, do_it: bool) {
self.only_generics = do_it;
}

/// Set the maximum number of generics to list.
pub fn set_max_generics(&mut self, max: u32) {
self.max_generics = max;
}

/// Set the maximum number of individual monomorphizations to list for each
/// generic function.
pub fn set_max_monos(&mut self, max: u32) {
self.max_monos = max;
}
}
17 changes: 17 additions & 0 deletions opt/opt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ impl CommonOptions for Options {
Options::Top(ref top) => top.input(),
Options::Dominators(ref doms) => doms.input(),
Options::Paths(ref paths) => paths.input(),
Options::Monos(ref monos) => monos.input(),
}
}

Expand All @@ -54,6 +55,7 @@ impl CommonOptions for Options {
Options::Top(ref top) => top.output_destination(),
Options::Dominators(ref doms) => doms.output_destination(),
Options::Paths(ref paths) => paths.output_destination(),
Options::Monos(ref monos) => monos.output_destination(),
}
}

Expand All @@ -62,6 +64,7 @@ impl CommonOptions for Options {
Options::Top(ref top) => top.output_format(),
Options::Dominators(ref doms) => doms.output_format(),
Options::Paths(ref paths) => paths.output_format(),
Options::Monos(ref monos) => monos.output_format(),
}
}
}
Expand Down Expand Up @@ -108,6 +111,20 @@ impl CommonOptions for Paths {
}
}

impl CommonOptions for Monos {
fn input(&self) -> &path::Path {
&self.input
}

fn output_destination(&self) -> &OutputDestination {
&self.output_destination
}

fn output_format(&self) -> traits::OutputFormat {
self.output_format
}
}

/// Where to output results.
#[derive(Clone, Debug)]
pub enum OutputDestination {
Expand Down
6 changes: 6 additions & 0 deletions twiggy/tests/expectations/cpp_monos
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Apprx. Bloat Bytes β”‚ Apprx. Bloat % β”‚ Bytes β”‚ % β”‚ Monomorphizations
────────────────────┼────────────────┼───────┼───────┼─────────────────────────
14 β”Š 3.01% β”Š 21 β”Š 4.52% β”Š void generic
β”Š β”Š 7 β”Š 1.51% β”Š void generic<Zero>()
β”Š β”Š 7 β”Š 1.51% β”Š void generic<One>()
β”Š β”Š 7 β”Š 1.51% β”Š void generic<Two>()
50 changes: 50 additions & 0 deletions twiggy/tests/expectations/monos
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
Apprx. Bloat Bytes β”‚ Apprx. Bloat % β”‚ Bytes β”‚ % β”‚ Monomorphizations
────────────────────┼────────────────┼───────┼───────┼────────────────────────────────────────────────────────────────────────────────────────────────────
1977 β”Š 3.40% β”Š 3003 β”Š 5.16% β”Š alloc::slice::merge_sort
β”Š β”Š 1026 β”Š 1.76% β”Š alloc::slice::merge_sort::hb3d195f9800bdad6
β”Š β”Š 1026 β”Š 1.76% β”Š alloc::slice::merge_sort::hfcf2318d7dc71d03
β”Š β”Š 951 β”Š 1.63% β”Š alloc::slice::merge_sort::hcfca67f5c75a52ef
1302 β”Š 2.24% β”Š 3996 β”Š 6.87% β”Š <&'a T as core::fmt::Debug>::fmt
β”Š β”Š 2694 β”Š 4.63% β”Š <&'a T as core::fmt::Debug>::fmt::h1c27955d8de3ff17
β”Š β”Š 568 β”Š 0.98% β”Š <&'a T as core::fmt::Debug>::fmt::hea6a77c4dcddb7ac
β”Š β”Š 433 β”Š 0.74% β”Š <&'a T as core::fmt::Debug>::fmt::hfbacf6f5c9f53bb2
β”Š β”Š 301 β”Š 0.52% β”Š <&'a T as core::fmt::Debug>::fmt::h199e8e1c5752e6f1
973 β”Š 1.67% β”Š 1118 β”Š 1.92% β”Š core::result::unwrap_failed
β”Š β”Š 145 β”Š 0.25% β”Š core::result::unwrap_failed::h9bd27c3a9ad7c001
β”Š β”Š 145 β”Š 0.25% β”Š core::result::unwrap_failed::h4cc73eb9bf19ce32
β”Š β”Š 145 β”Š 0.25% β”Š core::result::unwrap_failed::h137aa4f433aba1a9
β”Š β”Š 138 β”Š 0.24% β”Š core::result::unwrap_failed::ha3e58cfc7f422ab4
β”Š β”Š 138 β”Š 0.24% β”Š core::result::unwrap_failed::h9a7678774db14d67
β”Š β”Š 138 β”Š 0.24% β”Š core::result::unwrap_failed::hcb258ce32bda3d85
β”Š β”Š 138 β”Š 0.24% β”Š core::result::unwrap_failed::ha7651fcaac40f701
β”Š β”Š 131 β”Š 0.23% β”Š core::result::unwrap_failed::hcfddf900474e698a
558 β”Š 0.96% β”Š 714 β”Š 1.23% β”Š <alloc::raw_vec::RawVec<T, A>>::double
β”Š β”Š 156 β”Š 0.27% β”Š <alloc::raw_vec::RawVec<T, A>>::double::h28f86621ee2a10aa
β”Š β”Š 156 β”Š 0.27% β”Š <alloc::raw_vec::RawVec<T, A>>::double::h956450b93bdc9e1e
β”Š β”Š 156 β”Š 0.27% β”Š <alloc::raw_vec::RawVec<T, A>>::double::hcb2fb5861b96a3b0
β”Š β”Š 147 β”Š 0.25% β”Š <alloc::raw_vec::RawVec<T, A>>::double::ha715b4e5cc3c60ae
β”Š β”Š 99 β”Š 0.17% β”Š <alloc::raw_vec::RawVec<T, A>>::double::h77ff8547127c5db2
512 β”Š 0.88% β”Š 798 β”Š 1.37% β”Š std::thread::local::os::destroy_value
β”Š β”Š 286 β”Š 0.49% β”Š std::thread::local::os::destroy_value::hca8124786bee4a79
β”Š β”Š 281 β”Š 0.48% β”Š std::thread::local::os::destroy_value::h094cf4f2a025ba2b
β”Š β”Š 231 β”Š 0.40% β”Š std::thread::local::os::destroy_value::h453d41f6c315da32
234 β”Š 0.40% β”Š 354 β”Š 0.61% β”Š alloc::slice::insert_head
β”Š β”Š 120 β”Š 0.21% β”Š alloc::slice::insert_head::haf6e08236bab8bde
β”Š β”Š 120 β”Š 0.21% β”Š alloc::slice::insert_head::h2cdb84a455761146
β”Š β”Š 114 β”Š 0.20% β”Š alloc::slice::insert_head::hed0e79da03eeec8b
196 β”Š 0.34% β”Š 294 β”Š 0.51% β”Š <core::fmt::Write::write_fmt::Adapter<'a, T> as core::fmt::Write>::write_fmt
β”Š β”Š 98 β”Š 0.17% β”Š <core::fmt::Write::write_fmt::Adapter<'a, T> as core::fmt::Write>::write_fmt::h1b74a5fafe15c8eb
β”Š β”Š 98 β”Š 0.17% β”Š <core::fmt::Write::write_fmt::Adapter<'a, T> as core::fmt::Write>::write_fmt::h24034d1c07bfae93
β”Š β”Š 98 β”Š 0.17% β”Š <core::fmt::Write::write_fmt::Adapter<'a, T> as core::fmt::Write>::write_fmt::h5ebed3e159974658
195 β”Š 0.34% β”Š 270 β”Š 0.46% β”Š <alloc::vec::Vec<T>>::push
β”Š β”Š 75 β”Š 0.13% β”Š <alloc::vec::Vec<T>>::push::h98b02eda22d1ca25
β”Š β”Š 66 β”Š 0.11% β”Š <alloc::vec::Vec<T>>::push::hc927b4bedb35b00d
β”Š β”Š 66 β”Š 0.11% β”Š <alloc::vec::Vec<T>>::push::h5729b9e7651ef67b
β”Š β”Š 63 β”Š 0.11% β”Š <alloc::vec::Vec<T>>::push::h9415ef699ccc65d8
119 β”Š 0.20% β”Š 180 β”Š 0.31% β”Š <core::ops::range::Range<usize> as core::slice::SliceIndex<[T]>>::index_mut
β”Š β”Š 61 β”Š 0.10% β”Š <core::ops::range::Range<usize> as core::slice::SliceIndex<[T]>>::index_mut::hba42cce6d0c0099b
β”Š β”Š 61 β”Š 0.10% β”Š <core::ops::range::Range<usize> as core::slice::SliceIndex<[T]>>::index_mut::hbf8fcfe76c1f6657
β”Š β”Š 58 β”Š 0.10% β”Š <core::ops::range::Range<usize> as core::slice::SliceIndex<[T]>>::index_mut::h1c053f01b6f95d93
95 β”Š 0.16% β”Š 190 β”Š 0.33% β”Š core::fmt::Write::write_fmt
β”Š β”Š 95 β”Š 0.16% β”Š core::fmt::Write::write_fmt::ha5ae3249cacba520
β”Š β”Š 95 β”Š 0.16% β”Š core::fmt::Write::write_fmt::hef4632e1398f5ac8
1 change: 1 addition & 0 deletions twiggy/tests/expectations/monos_json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[{"generic":"alloc::slice::merge_sort","approximate_monomorphization_bloat_bytes":1977,"approximate_monomorphization_bloat_percent":3.396673768125902,"total_size":3003,"total_size_percent":5.159439213799739,"monomorphizations":[{"name":"alloc::slice::merge_sort::hb3d195f9800bdad6","shallow_size":1026,"shallow_size_percent":1.7627654456738369}]},{"generic":"<&'a T as core::fmt::Debug>::fmt","approximate_monomorphization_bloat_bytes":1302,"approximate_monomorphization_bloat_percent":2.2369596591299565,"total_size":3996,"total_size_percent":6.865507525255995,"monomorphizations":[{"name":"<&'a T as core::fmt::Debug>::fmt::h1c27955d8de3ff17","shallow_size":2694,"shallow_size_percent":4.6285478661260395}]}]
6 changes: 6 additions & 0 deletions twiggy/tests/expectations/monos_maxes
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Apprx. Bloat Bytes β”‚ Apprx. Bloat % β”‚ Bytes β”‚ % β”‚ Monomorphizations
────────────────────┼────────────────┼───────┼───────┼────────────────────────────────────────────────────────
1977 β”Š 3.40% β”Š 3003 β”Š 5.16% β”Š alloc::slice::merge_sort
β”Š β”Š 1026 β”Š 1.76% β”Š alloc::slice::merge_sort::hb3d195f9800bdad6
1302 β”Š 2.24% β”Š 3996 β”Š 6.87% β”Š <&'a T as core::fmt::Debug>::fmt
β”Š β”Š 2694 β”Š 4.63% β”Š <&'a T as core::fmt::Debug>::fmt::h1c27955d8de3ff17
Loading

0 comments on commit aa3330d

Please sign in to comment.