reduce compile time by making output label generation non-generic (#3848)

Split `HasOutputTypeLabel::output_type_label` method implementation non-generic part into a separate function. This significantly reduces compile time without risking any performance regressions. That function is currently only used for debug network visualization using graphviz, but still contributed a significant compilation time.

I've made a single attempt to profile the compiler itself, and it turned out that the compiler spent a significant amount of time trying to resolve `Pattern` implementation for closures in that method. Each of those also had to separately go through codegen and optimization. That happened for each node type in the codebase, per crate. Moving that into separate non-inline function removed all those unnecessary duplicates from. I also took this opportunity to rewrite that small piece of parsing to make it a bit cleaner.

The method of measurement is explained here: https://blog.rust-lang.org/inside-rust/2020/02/25/intro-rustc-self-profile.html
In this specific case, I've used `self-profile` to generate a profile for all crates, then used `crox` and [perfetto](https://ui.perfetto.dev/) to analyze the output.

This also shows a pattern to be aware of - using closures in generic context forces the compiler to make a separate closure type per each instantiation, even if that closure doesn't close on anything and could be a static function. This in effect forces even more instantiations or unnecessary type resolutions for all code paths that touch that closure. Using static functions or separating the non-generic part away in those cases would likely continue to help with compile times and file size.

## Comparison

The measurement was done on same machine under same environment, cleaning the build artifacts inbetween runs.

| | before |  after |
|-|-|-|
|total build time|5m 5.5s|3m 59.0s|
| `ide-view-graph-editor` crate build time| 85.68s | 49.73s |
| `ide-view-component-list-panel-grid` crate build time | 49.88s | 32.07s |
| `ide-view` crate build time | 29.05s | 17.5s |
| `enso_gui.wasm` file size before wasm-opt | 83.6 MB | 80.9 MB |
| `enso_gui.wasm` file size after wasm-opt | 67.3 MB | 65.3 MB |

![image](https://user-images.githubusercontent.com/919491/199633193-64dada16-eb22-4020-8d31-3f24661497aa.png)
This commit is contained in:
Paweł Grabarz 2022-11-03 15:43:56 +01:00 committed by GitHub
parent db13084828
commit 94eff1192a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 15 additions and 14 deletions

View File

@ -1,6 +1,6 @@
# Options intended to be common for all developers.
wasm-size-limit: 15.25 MiB
wasm-size-limit: 14.9 MiB
required-versions:
cargo-watch: ^8.1.1

View File

@ -84,7 +84,7 @@ impl Display for DisabledCallStack {
/// Label of the output type of this FRP node. Used mainly for debugging purposes.
pub trait HasOutputTypeLabel {
/// Output type label of this object.
fn output_type_label(&self) -> String;
fn output_type_label(&self) -> Label;
}
@ -659,24 +659,25 @@ where Def: InputBehaviors
}
}
// FIXME code quality below:
impl<Def> HasOutputTypeLabel for Node<Def>
where Def: HasOutputStatic + InputBehaviors
{
fn output_type_label(&self) -> String {
let label = type_name::<Def>().to_string();
let label = label.split(|c| c == '<').collect::<Vec<_>>()[0];
let mut label = label.split(|c| c == ':').collect::<Vec<_>>();
label.reverse();
let mut label = label[0];
let sfx = "Data";
if label.ends_with(sfx) {
label = &label[0..label.len() - sfx.len()];
}
label.into()
fn output_type_label(&self) -> Label {
type_name_to_output_label(type_name::<Def>())
}
}
// The label transformation logic is a separate non-generic function, so that it can be compiled
// only once for all node types. This has a noticeable impact on compilation time.
// For more details see https://github.com/enso-org/enso/pull/3848
#[inline(never)]
fn type_name_to_output_label(typename: &'static str) -> Label {
let label = typename.split('<').next().unwrap_or(typename);
let label = label.rsplit(':').next().unwrap_or(label);
let label = label.strip_suffix("Data").unwrap_or(label);
label
}
// === InputBehaviors ===