mcpField
G
gpui-performance

gpui-performance

github.com/geoffjay开发854 次安装52 周前更新
代码仓库

关于 gpui-performance

面向 GPUI 应用开发者的性能优化指南,涵盖渲染、布局、内存和运行时效率。内容包括减少无效重绘、缓存计算、虚拟滚动及使用性能分析工具。

主要功能

  1. 01减少不必要的重绘
  2. 02优化布局与虚拟滚动
  3. 03管理订阅和内存
  4. 04缓存计算并批量更新
  5. 05使用分析与基准测试工具

适用场景

  1. 01排查 GPUI 应用的渲染卡顿
  2. 02分析应用的 CPU 与内存占用
  3. 03优化大型列表和频繁状态更新

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/geoffjay/claude-plugins --skill gpui-performance

安装后在 Claude Code 中输入/gpui-performance确认已加载。

详细介绍

GPUI 性能优化

元数据

此技能全面介绍如何优化 GPUI 应用的渲染性能、内存效率和整体运行速度。

说明

渲染优化
了解渲染周期
State Change → cx.notify() → Render → Layout → Paint → Display

要点:

  • 仅在状态实际发生变化时调用 cx.notify()
  • 尽量减少 render() 方法中的工作
  • 缓存开销较大的计算结果
  • 减少元素数量和嵌套层级
避免不必要的渲染
// BAD: Renders on every frame
impl MyComponent {
    fn start_animation(&mut self, cx: &mut ViewContext<Self>) {
        cx.spawn(|this, mut cx| async move {
            loop {
                cx.update(|_, cx| cx.notify()).ok();  // Forces rerender!
                Timer::after(Duration::from_millis(16)).await;
            }
        }).detach();
    }
}

// GOOD: Only render when state changes
impl MyComponent {
    fn update_value(&mut self, new_value: i32, cx: &mut ViewContext<Self>) {
        if self.value != new_value {
            self.value = new_value;
            cx.notify();  // Only notify on actual change
        }
    }
}
优化订阅更新
// BAD: Always rerenders on model change
let _subscription = cx.observe(&model, |_, _, cx| {
    cx.notify();  // Rerenders even if nothing relevant changed
});

// GOOD: Selective updates
let _subscription = cx.observe(&model, |this, model, cx| {
    let data = model.read(cx);

    // Only rerender if relevant field changed
    if data.relevant_field != this.cached_field {
        this.cached_field = data.relevant_field.clone();
        cx.notify();
    }
});
记忆化模式
use std::cell::RefCell;
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};

struct MemoizedComponent {
    model: Model<Data>,
    cached_result: RefCell<Option<(u64, String)>>,  // (hash, result)
}

impl MemoizedComponent {
    fn expensive_computation(&self, cx: &ViewContext<Self>) -> String {
        let data = self.model.read(cx);

        // Calculate hash of input
        let mut hasher = DefaultHasher::new();
        data.relevant_fields.hash(&mut hasher);
        let hash = hasher.finish();

        // Return cached if unchanged
        if let Some((cached_hash, cached_result)) = &*self.cached_result.borrow() {
            if *cached_hash == hash {
                return cached_result.clone();
            }
        }

        // Compute and cache
        let result = perform_expensive_computation(&data);
        *self.cached_result.borrow_mut() = Some((hash, result.clone()));
        result
    }
}
布局性能
尽量简化布局
// BAD: Deep nesting
div()
    .flex()
    .child(
        div()
            .flex()
            .child(
                div()
                    .flex()
                    .child(
                        div().child("Content")
                    )
            )
    )

// GOOD: Flat structure
div()
    .flex()
    .flex_col()
    .gap_4()
    .child("Header")
    .child("Content")
    .child("Footer")
尽可能使用固定尺寸
// BETTER: Fixed sizes (no layout calculation)
div()
    .w(px(200.))
    .h(px(100.))
    .child("Fixed size")

// SLOWER: Dynamic sizing (requires layout calculation)
div()
    .w_full()
    .h_full()
    .child("Dynamic size")
避免布局抖动
// BAD: Reading layout during render
impl Render for BadComponent {
    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
        let width = cx.window_bounds().get_bounds().size.width;
        // Using width immediately causes layout thrashing
        div().w(width)
    }
}

// GOOD: Cache layout-dependent values
struct GoodComponent {
    cached_width: Pixels,
}

impl GoodComponent {
    fn on_window_resize(&mut self, cx: &mut ViewContext<Self>) {
        let width = cx.window_bounds().get_bounds().size.width;
        if self.cached_width != width {
            self.cached_width = width;
            cx.notify();
        }
    }
}
长列表使用虚拟滚动
struct VirtualList {
    items: Vec<String>,
    scroll_offset: f32,
    viewport_height: f32,
    item_height: f32,
}

impl Render for VirtualList {
    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
        // Calculate visible range
        let start_index = (self.scroll_offset / self.item_height).floor() as usize;
        let visible_count = (self.viewport_height / self.item_height).ceil() as usize;
        let end_index = (start_index + visible_count).min(self.items.len());

        // Only render visible items
        div()
            .h(px(self.viewport_height))
            .overflow_y_scroll()
            .on_scroll(cx.listener(|this, event, cx| {
                this.scroll_offset = event.scroll_offset.y;
                cx.notify();
            }))
            .child(
                div()
                    .h(px(self.items.len() as f32 * self.item_height))
                    .child(
                        div()
                            .absolute()
                            .top(px(start_index as f32 * self.item_height))
                            .children(
                                self.items[start_index..end_index]
                                    .iter()
                                    .map(|item| {
                                        div()
                                            .h(px(self.item_height))
                                            .child(item.as_str())
                                    })
                            )
                    )
            )
    }
}
内存管理
防止内存泄漏
// LEAK: Subscription not stored
impl BadView {
    fn new(model: Model<Data>, cx: &mut ViewContext<Self>) -> Self {
        cx.observe(&model, |_, _, cx| cx.notify());  // Leak!
        Self { model }
    }
}

// CORRECT: Store subscription
struct GoodView {
    model: Model<Data>,
    _subscription: Subscription,  // Cleaned up on Drop
}

impl GoodView {
    fn new(model: Model<Data>, cx: &mut ViewContext<Self>) -> Self {
        let _subscription = cx.observe(&model, |_, _, cx| cx.notify());
        Self { model, _subscription }
    }
}
避免循环引用
// BAD: Circular reference
struct CircularRef {
    self_view: Option<View<Self>>,  // Circular!
}

// GOOD: Use weak references or redesign
struct NoCycle {
    other_view: View<OtherView>,  // No cycle
}
有界集合
use std::collections::VecDeque;

const MAX_HISTORY: usize = 100;

struct BoundedHistory {
    items: VecDeque<Item>,
}

impl BoundedHistory {
    fn add_item(&mut self, item: Item) {
        self.items.push_back(item);

        // Maintain size limit
        while self.items.len() > MAX_HISTORY {
            self.items.pop_front();
        }
    }
}
重用分配
struct BufferedComponent {
    buffer: String,  // Reused across operations
}

impl BufferedComponent {
    fn format_data(&mut self, data: &[Item]) -> &str {
        self.buffer.clear();  // Reuse allocation

        for item in data {
            use std::fmt::Write;
            write!(&mut self.buffer, "{}\n", item.name).ok();
        }

        &self.buffer
    }
}
性能分析策略
使用 cargo-flamegraph 进行 CPU 性能分析
# Install
cargo install flamegraph

# Profile application
cargo flamegraph --bin your-app

# With specific features
cargo flamegraph --bin your-app --features profiling

# Opens flamegraph.svg showing CPU time distribution
内存性能分析
# valgrind (Linux)
valgrind --tool=massif --massif-out-file=massif.out ./target/release/your-app
ms_print massif.out

# heaptrack (Linux)
heaptrack ./target/release/your-app
heaptrack_gui heaptrack.your-app.*.gz

# Instruments (macOS)
instruments -t "Allocations" ./target/release/your-app
自定义性能监控
use std::time::Instant;

struct PerformanceMonitor {
    frame_times: VecDeque<Duration>,
    max_samples: usize,
}

impl PerformanceMonitor {
    fn new() -> Self {
        Self {
            frame_times: VecDeque::with_capacity(100),
            max_samples: 100,
        }
    }

    fn record_frame(&mut self, duration: Duration) {
        self.frame_times.push_back(duration);

        if self.frame_times.len() > self.max_samples {
            self.frame_times.pop_front();
        }

        // Warn if frame is slow (> 16ms for 60fps)
        if duration.as_millis() > 16 {
            eprintln!("⚠️  Slow frame: {}ms", duration.as_millis());
        }
    }

    fn average_fps(&self) -> f64 {
        if self.frame_times.is_empty() {
            return 0.0;
        }

        let total: Duration = self.frame_times.iter().sum();
        let avg = total / self.frame_times.len() as u32;
        1000.0 / avg.as_millis() as f64
    }

    fn percentile(&self, p: f64) -> Duration {
        let mut sorted: Vec<_> = self.frame_times.iter().copied().collect();
        sorted.sort();

        let index = (sorted.len() as f64 * p) as usize;
        sorted[index.min(sorted.len() - 1)]
    }
}

// Usage in component
impl MyView {
    fn measure_render<F>(&mut self, f: F, cx: &mut ViewContext<Self>)
    where
        F: FnOnce(&mut Self, &mut ViewContext<Self>)
    {
        let start = Instant::now();
        f(self, cx);
        let elapsed = start.elapsed();

        self.perf_monitor.record_frame(elapsed);

        // Log stats periodically
        if self.frame_count % 60 == 0 {
            println!(
                "Avg FPS: {:.1}, p95: {}ms, p99: {}ms",
                self.perf_monitor.average_fps(),
                self.perf_monitor.percentile(0.95).as_millis(),
                self.perf_monitor.percentile(0.99).as_millis(),
            );
        }
    }
}
使用 Criterion 进行基准测试
// benches/component_bench.rs
use criterion::{black_box, criterion_group, criterion_main, Criterion, BenchmarkId};

fn render_benchmark(c: &mut Criterion) {
    let mut group = c.benchmark_group("rendering");

    for size in [10, 100, 1000].iter() {
        group.bench_with_input(
            BenchmarkId::from_parameter(size),
            size,
            |b, &size| {
                b.iter(|| {
                    App::test(|cx| {
                        let items = vec![Item::default(); size];
                        let view = cx.new_view(|cx| {
                            ListView::new(items, cx)
                        });

                        view.update(cx, |view, cx| {
                            black_box(view.render(cx));
                        });
                    });
                });
            }
        );
    }

    group.finish();
}

criterion_group!(benches, render_benchmark);
criterion_main!(benches);
批量更新
// BAD: Multiple individual updates
for item in items {
    self.model.update(cx, |model, cx| {
        model.add_item(item);  // Triggers rerender each time!
        cx.notify();
    });
}

// GOOD: Batch into single update
self.model.update(cx, |model, cx| {
    for item in items {
        model.add_item(item);
    }
    cx.notify();  // Single rerender
});
异步渲染优化
struct AsyncView {
    loading_state: Model<LoadingState>,
}

impl AsyncView {
    fn load_data(&mut self, cx: &mut ViewContext<Self>) {
        let loading_state = self.loading_state.clone();

        // Show loading immediately
        self.loading_state.update(cx, |state, cx| {
            *state = LoadingState::Loading;
            cx.notify();
        });

        // Load asynchronously
        cx.spawn(|_, mut cx| async move {
            // Fetch data
            let data = fetch_data().await?;

            // Update state once
            cx.update_model(&loading_state, |state, cx| {
                *state = LoadingState::Loaded(data);
                cx.notify();
            })?;

            Ok::<_, anyhow::Error>(())
        }).detach();
    }
}
缓存策略
结果缓存
use std::collections::HashMap;

struct CachedRenderer {
    cache: RefCell<HashMap<String, CachedElement>>,
}

impl CachedRenderer {
    fn render_cached(
        &self,
        key: String,
        render_fn: impl FnOnce() -> AnyElement,
    ) -> AnyElement {
        let mut cache = self.cache.borrow_mut();

        cache.entry(key)
            .or_insert_with(|| CachedElement::new(render_fn()))
            .element
            .clone()
    }

    fn invalidate(&self, key: &str) {
        self.cache.borrow_mut().remove(key);
    }
}

资源

性能目标

渲染:

  • 目标:60 FPS(每帧 16.67ms)
  • 渲染 + 布局:约 10ms
  • 绘制:约 6ms
  • 警告:任何超过 16ms 的帧

内存:

  • 监控堆增长情况
  • 警告:持续增长(泄漏)
  • 目标:初始化后保持稳定

启动:

  • 窗口显示:< 100ms
  • 完全交互:< 500ms
性能分析工具

CPU 性能分析:

  • cargo-flamegraph:可视化 CPU 时间
  • perf(Linux):系统级性能分析
  • Instruments(macOS):Apple 的性能分析工具

内存性能分析:

  • valgrind/massif:跟踪内存使用情况
  • heaptrack:跟踪堆分配
  • Instruments:内存分配分析

基准测试:

  • criterion:统计基准测试
  • cargo bench:内置基准测试
  • hyperfine:命令行工具基准测试
最佳实践
  1. 先测量:优化前先进行性能分析
  2. 减少渲染:仅在必要时调用 cx.notify()
  3. 缓存结果:对开销较大的计算进行记忆化
  4. 批量更新:将状态变更分组处理
  5. 虚拟滚动:用于长列表
  6. 扁平布局:避免深层嵌套
  7. 固定尺寸:尽可能使用
  8. 监控内存:留意内存泄漏
  9. 异步加载:不要阻塞 UI
  10. 测试性能:加入基准测试
常见瓶颈
  • 在 render 中订阅(内存泄漏)
  • 在 render 中进行开销较大的计算
  • 组件嵌套过深
  • 不必要的重复渲染
  • 布局抖动
  • 大型列表未使用虚拟化
  • 循环引用导致的内存泄漏
  • 无界集合

评论

支持 Markdown