mirror of
https://github.com/slint-ui/slint.git
synced 2025-10-02 06:41:14 +00:00

This removes a lot of allocations and speeds up the compiler step a bit. Sadly, this patch is very invasive as it touches a lot of files. That said, each individual hunk is pretty trivial. For a non-trivial real-world example, the impact is significant, we get rid of ~29% of all allocations and improve the runtime by about 4.8% (measured until the viewer loop would start). Before: ``` Benchmark 1: ./target/release/slint-viewer ../slint-perf/app.slint Time (mean ± σ): 664.2 ms ± 6.7 ms [User: 589.2 ms, System: 74.0 ms] Range (min … max): 659.0 ms … 682.4 ms 10 runs allocations: 4886888 temporary allocations: 857508 ``` After: ``` Benchmark 1: ./target/release/slint-viewer ../slint-perf/app.slint Time (mean ± σ): 639.5 ms ± 17.8 ms [User: 556.9 ms, System: 76.2 ms] Range (min … max): 621.4 ms … 666.5 ms 10 runs allocations: 3544318 temporary allocations: 495685 ```
173 lines
6.1 KiB
Rust
173 lines
6.1 KiB
Rust
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
|
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
|
|
|
|
//! Do not read twice the same property, store in a local variable instead
|
|
|
|
use crate::expression_tree::*;
|
|
use crate::langtype::Type;
|
|
use crate::object_tree::*;
|
|
use smol_str::{format_smolstr, SmolStr};
|
|
use std::cell::RefCell;
|
|
use std::collections::HashMap;
|
|
|
|
pub fn deduplicate_property_read(component: &Component) {
|
|
visit_all_expressions(component, |expr, ty| {
|
|
if matches!(ty(), Type::Callback { .. }) {
|
|
// Callback handler can't be optimizes because they can have side effect.
|
|
// But that's fine as they also do not register dependencies
|
|
return;
|
|
}
|
|
process_expression(expr, &DedupPropState::default());
|
|
});
|
|
}
|
|
|
|
#[derive(Default)]
|
|
struct PropertyReadCounts {
|
|
counts: HashMap<NamedReference, usize>,
|
|
/// If at least one element of the map has duplicates
|
|
has_duplicate: bool,
|
|
/// if there is an assignment of a property we currently disable this optimization
|
|
has_set: bool,
|
|
}
|
|
|
|
#[derive(Default)]
|
|
struct DedupPropState<'a> {
|
|
parent_state: Option<&'a DedupPropState<'a>>,
|
|
counts: RefCell<PropertyReadCounts>,
|
|
}
|
|
|
|
impl<'a> DedupPropState<'a> {
|
|
fn add(&self, nr: &NamedReference) {
|
|
if self.parent_state.map_or(false, |pc| pc.add_from_children(nr)) {
|
|
return;
|
|
}
|
|
let mut use_counts = self.counts.borrow_mut();
|
|
let use_counts = &mut *use_counts;
|
|
let has_duplicate = &mut use_counts.has_duplicate;
|
|
use_counts
|
|
.counts
|
|
.entry(nr.clone())
|
|
.and_modify(|c| {
|
|
if *c == 1 {
|
|
*has_duplicate = true;
|
|
}
|
|
*c += 1
|
|
})
|
|
.or_insert(1);
|
|
}
|
|
|
|
fn add_from_children(&self, nr: &NamedReference) -> bool {
|
|
if self.parent_state.map_or(false, |pc| pc.add_from_children(nr)) {
|
|
return true;
|
|
}
|
|
let mut use_counts = self.counts.borrow_mut();
|
|
let use_counts = &mut *use_counts;
|
|
if let Some(c) = use_counts.counts.get_mut(nr) {
|
|
if *c == 1 {
|
|
use_counts.has_duplicate = true;
|
|
}
|
|
*c += 1;
|
|
true
|
|
} else {
|
|
false
|
|
}
|
|
}
|
|
|
|
fn get_mapping(&self, nr: &NamedReference) -> Option<SmolStr> {
|
|
self.parent_state.and_then(|pr| pr.get_mapping(nr)).or_else(|| {
|
|
if self.counts.borrow().counts.get(nr).map_or(false, |c| *c > 1) {
|
|
Some(format_smolstr!("tmp_{}_{}", nr.element().borrow().id, nr.name()))
|
|
} else {
|
|
None
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
fn process_expression(expr: &mut Expression, old_state: &DedupPropState) {
|
|
if old_state.counts.borrow().has_set {
|
|
return;
|
|
}
|
|
let new_state = DedupPropState { parent_state: Some(old_state), ..DedupPropState::default() };
|
|
collect_unconditional_read_count(expr, &new_state);
|
|
process_conditional_expressions(expr, &new_state);
|
|
if new_state.counts.borrow().has_set {
|
|
old_state.counts.borrow_mut().has_set = true;
|
|
return;
|
|
}
|
|
do_replacements(expr, &new_state);
|
|
if new_state.counts.borrow().has_duplicate {
|
|
let mut stores = vec![];
|
|
for (nr, count) in &new_state.counts.borrow().counts {
|
|
if *count > 1 {
|
|
let new_name = new_state.get_mapping(nr).unwrap();
|
|
stores.push(Expression::StoreLocalVariable {
|
|
name: new_name,
|
|
value: Box::new(Expression::PropertyReference(nr.clone())),
|
|
});
|
|
}
|
|
}
|
|
stores.push(std::mem::take(expr));
|
|
*expr = Expression::CodeBlock(stores);
|
|
}
|
|
}
|
|
|
|
// Collect all use of variable and their count, only in non conditional expression
|
|
fn collect_unconditional_read_count(expr: &Expression, result: &DedupPropState) {
|
|
match expr {
|
|
Expression::PropertyReference(nr) => {
|
|
result.add(nr);
|
|
}
|
|
//Expression::RepeaterIndexReference { element } => {}
|
|
//Expression::RepeaterModelReference { element } => {}
|
|
Expression::BinaryExpression { lhs, rhs: _, op: '|' | '&' } => {
|
|
lhs.visit(|sub| collect_unconditional_read_count(sub, result))
|
|
}
|
|
Expression::Condition { condition, .. } => {
|
|
condition.visit(|sub| collect_unconditional_read_count(sub, result))
|
|
}
|
|
Expression::SelfAssignment { .. } => {
|
|
result.counts.borrow_mut().has_set = true;
|
|
}
|
|
_ => expr.visit(|sub| collect_unconditional_read_count(sub, result)),
|
|
}
|
|
}
|
|
|
|
fn process_conditional_expressions(expr: &mut Expression, state: &DedupPropState) {
|
|
if state.counts.borrow().has_set {
|
|
return;
|
|
}
|
|
match expr {
|
|
Expression::BinaryExpression { lhs, rhs, op: '|' | '&' } => {
|
|
lhs.visit_mut(|sub| process_conditional_expressions(sub, state));
|
|
process_expression(rhs, state);
|
|
}
|
|
Expression::Condition { condition, true_expr, false_expr } => {
|
|
condition.visit_mut(|sub| process_conditional_expressions(sub, state));
|
|
process_expression(true_expr, state);
|
|
process_expression(false_expr, state);
|
|
}
|
|
Expression::SelfAssignment { .. } => {
|
|
state.counts.borrow_mut().has_set = true;
|
|
}
|
|
_ => expr.visit_mut(|sub| process_conditional_expressions(sub, state)),
|
|
}
|
|
}
|
|
|
|
fn do_replacements(expr: &mut Expression, state: &DedupPropState) {
|
|
match expr {
|
|
Expression::PropertyReference(nr) => {
|
|
if let Some(name) = state.get_mapping(nr) {
|
|
let ty = expr.ty();
|
|
*expr = Expression::ReadLocalVariable { name, ty };
|
|
}
|
|
}
|
|
Expression::BinaryExpression { lhs, rhs: _, op: '|' | '&' } => {
|
|
lhs.visit_mut(|sub| do_replacements(sub, state));
|
|
}
|
|
Expression::Condition { condition, .. } => {
|
|
condition.visit_mut(|sub| do_replacements(sub, state));
|
|
}
|
|
_ => expr.visit_mut(|sub| do_replacements(sub, state)),
|
|
}
|
|
}
|