Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Excessive Cloning Detected in Flamegraph Profiling #70

Open
namana-mecha opened this issue Feb 5, 2025 · 0 comments · May be fixed by #71
Open

Excessive Cloning Detected in Flamegraph Profiling #70

namana-mecha opened this issue Feb 5, 2025 · 0 comments · May be fixed by #71

Comments

@namana-mecha
Copy link
Contributor

While testing profiling mctk with animations, I found that an unusually high number of samples in the Flamegraph originate from cloning.

How to reproduce?

Run Flamegraph on the following code
use mctk_core::animations::{easing_functions::*, Animation};
use mctk_core::context::Model;
use mctk_core::layout::Alignment;
use mctk_core::prelude::*;
use mctk_core::reexports::smithay_client_toolkit::{
    reexports::calloop::{self, channel::Event},
    shell::wlr_layer,
};
use mctk_core::widgets::Text;
use mctk_smithay::layer_shell::layer_surface::LayerOptions;
use mctk_smithay::layer_shell::layer_window::{LayerWindow, LayerWindowParams};
use mctk_smithay::xdg_shell::xdg_window::{self, XdgWindowMessage, XdgWindowParams};
use mctk_smithay::{WindowInfo, WindowMessage, WindowOptions};
use smithay_client_toolkit::reexports::calloop::channel::Sender;
use std::any::Any;
use std::collections::HashMap;
use std::fmt::Debug;
use std::hash::Hash;
use tracing_subscriber::fmt::format;

#[derive(Debug)]
pub enum AppMessage {
    Exit,
}

#[derive(Clone)]
pub struct AppParams {
    app_channel: Option<Sender<AppMessage>>,
}

#[derive(Default)]
pub struct AppState {
    window_sender: Option<Sender<XdgWindowMessage>>,
    app_channel: Option<Sender<AppMessage>>,
    linear_animation: Animation<Linear>,
    ease_in_out_animation: Animation<EaseInOutQuadratic>,
    ease_in_animation: Animation<EaseInQuadratic>,
    ease_out_animation: Animation<EaseOutQuadratic>,
}

impl Debug for AppState {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("AppState").finish()
    }
}

#[component(State = "AppState")]
#[derive(Debug, Default)]
pub struct App {}

#[state_component_impl(AppState)]
impl Component for App {
    fn init(&mut self) {
        self.state = Some(AppState {
            window_sender: None,
            app_channel: None,
            linear_animation: Animation::new(
                std::time::Duration::from_secs(1),
                animations::AnimationRepeat::PingPong,
            ),
            ease_in_out_animation: Animation::new(
                std::time::Duration::from_secs(1),
                animations::AnimationRepeat::PingPong,
            ),
            ease_in_animation: Animation::new(
                std::time::Duration::from_secs(1),
                animations::AnimationRepeat::PingPong,
            ),
            ease_out_animation: Animation::new(
                std::time::Duration::from_secs(1),
                animations::AnimationRepeat::PingPong,
            ),
        });
    }

    fn render_hash(&self, hasher: &mut ComponentHasher) {
        self.state_ref().linear_animation.hash(hasher);
        self.state_ref().ease_in_animation.hash(hasher);
        self.state_ref().ease_out_animation.hash(hasher);
        self.state_ref().ease_in_out_animation.hash(hasher);
    }

    fn props_hash(&self, hasher: &mut ComponentHasher) {
        self.state_ref().linear_animation.hash(hasher);
        self.state_ref().ease_in_animation.hash(hasher);
        self.state_ref().ease_out_animation.hash(hasher);
        self.state_ref().ease_in_out_animation.hash(hasher);
    }

    fn on_tick(&mut self, _event: &mut mctk_core::event::Event<mctk_core::event::Tick>) {
        self.dirty = true;
    }

    fn view(&self) -> Option<Node> {
        let linear_animation = self.state_ref().linear_animation.get_value();
        let linear_label = node!(
            Text::new(txt!(format!("Linear Animation")))
                .style("color", Color::WHITE)
                .style("size", 25.0),
            lay![size: size!(480.0, 50.0), margin: [10.0, 25.0, 0.0, 0.0],]
        );
        let linear_rectangle = node!(
            RoundedRect::new(Color::WHITE, 100.0),
            lay![
                size: size!(50.0, 50.0),
                margin: [25.0, 25.0 + 375.0 * linear_animation , 0.0, 0.0],
            ]
        )
        .key((linear_animation * 1000000.0) as u64);

        let ease_in_out_label = node!(
            Text::new(txt!(format!("Ease In Out")))
                .style("color", Color::WHITE)
                .style("size", 25.0),
            lay![size: size!(480.0, 50.0), margin: [10.0, 25.0, 0.0, 0.0],]
        );
        let ease_in_out_animation = self.state_ref().ease_in_out_animation.get_value();
        let ease_in_out_rectangle = node!(
            RoundedRect::new(Color::WHITE, 100.0),
            lay![
                size: size!(50.0, 50.0),
                margin: [25.0, 25.0 + 375.0 * ease_in_out_animation , 0.0, 0.0],
            ]
        )
        .key((ease_in_out_animation * 1000000.0) as u64);

        let mut base = node!(
            Div::new().bg(Color::BLACK),
            lay![direction: Direction::Column, size: size!(480.0, 480.0)]
        );
        base = base.push(linear_rectangle);
        base = base.push(linear_label);

        base = base.push(ease_in_out_rectangle);
        base = base.push(ease_in_out_label);
        Some(base)
    }

    fn update(&mut self, message: Message) -> Vec<Message> {
        vec![message]
    }
}

// Layer Surface App
#[tokio::main]
async fn main() {
    let id = 1;
    let ui_t = std::thread::spawn(move || {
        let _ = launch_ui(id);
    });
    ui_t.join().unwrap();
}

impl RootComponent<AppParams> for App {
    fn root(&mut self, _: &dyn Any, app_params: &dyn Any) {
        let app_params = app_params.downcast_ref::<AppParams>().unwrap();
        self.state_mut().app_channel = app_params.app_channel.clone();
    }
}

fn launch_ui(id: i32) -> anyhow::Result<()> {
    let assets: HashMap<String, AssetParams> = HashMap::new();
    let mut svgs: HashMap<String, String> = HashMap::new();

    svgs.insert("eye_icon".to_string(), "./src/assets/icons/eye.svg".to_string());

    let mut fonts = cosmic_text::fontdb::Database::new();
    fonts.load_system_fonts();

    let window_opts = WindowOptions {
        height: 480,
        width: 480,
        scale_factor: 1.0,
    };

    println!("id: {id:?}");
    let window_info = WindowInfo {
        id: format!("mctk.examples.animations{}", id),
        title: format!("mctk.examples.animations{}", id),
        namespace: format!("mctk.examples.animations{}", id),
    };

    let (app_channel_tx, app_channel_rx) = calloop::channel::channel();
    let (_, mut event_loop, _) = LayerWindow::open_blocking::<App, AppParams>(
        LayerWindowParams {
            window_info,
            window_opts,
            fonts,
            assets,
            svgs,
            ..Default::default()
        },
        AppParams {
            app_channel: Some(app_channel_tx),
        },
    );

    loop {
        let _ = event_loop.dispatch(None, &mut ());
    }
}

Steps to Run

cargo flamegraph

Additional Information

Observations:

  • The profiling results indicate that a significant portion of execution time is spent on cloning.
  • This might be causing unnecessary performance overhead.

Attachments:

Flamegraph Screenshot 1
Flamegraph Screenshot 2

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
1 participant