Performance Skill
Profiling Workflow
Chrome DevTools
- Performance tab: Record during playback, look for long tasks (>50ms)
- Memory tab: Take heap snapshots before/after operations, check for leaks
- Performance Monitor: Watch JS heap size, DOM nodes, layouts/sec in real-time
- Layers panel: Identify unnecessary compositing layers (GPU memory)
Key Metrics
- Frame budget: 16.6ms at 60fps — anything longer causes jank
- Tick budget: IterablePlayer caps at 300ms per tick
- GC pressure: Frequent minor GCs indicate excessive allocation
- Transfer size: Transferable objects (ArrayBuffer) should use zero-copy transfer
Common Bottlenecks
1. Message Processing (Player → Pipeline)
- Symptom: Dropped frames during high-rate playback
- Cause: Too many messages per tick, deserialization cost
- Fix: Batch processing, Worker-based deserialization, subscription filtering
2. Render State Building (Pipeline → Panel)
- Symptom: All panels re-render even when their data hasn't changed
- Cause: Missing memoization in
renderState.ts, non-stable references
- Fix: Ensure
buildRenderState returns same reference when data unchanged
3. 3D Scene Updates (Panel rendering)
- Symptom: Low FPS in 3D panel with many objects
- Cause: Per-frame geometry creation, excessive draw calls
- Fix:
DynamicBufferGeometry reuse, instanced rendering, frustum culling
4. Chart Rendering (Plot panel)
- Symptom: Plot panel laggy with many data points
- Cause: Chart.js processing 50k+ points on main thread
- Fix: Worker-based dataset building (50k cap per series), OffscreenCanvas
5. Memory Pressure (Caching)
- Symptom: Browser tab crashes or becomes unresponsive
- Cause: Cache exceeds budget, large messages retained
- Fix: Respect the 600MB player-level message cache budget (
CachingIterableSource), evict behind read head, lazy deserialization. Note: this is separate from the 500MB default HTTP-layer cache in RemoteFileReadable/CachedFilelike used for remote file reads.
Optimization Patterns
Zero-Copy Transfer
// Transfer ArrayBuffer to Worker (not copy)
Comlink.transfer({ buffer: myArrayBuffer }, [myArrayBuffer]);
// After transfer, myArrayBuffer.byteLength === 0 (detached)
Object Pooling (3D)
// Reuse Vector3 instances instead of creating new ones
const tempVec = new THREE.Vector3();
function updatePosition(x: number, y: number, z: number) {
tempVec.set(x, y, z);
mesh.position.copy(tempVec);
}
Structural Sharing (State)
// Only create new object if data actually changed
const newMessages = messages !== prevMessages ? [...messages, ...newBatch] : prevMessages;
Debounced Emission
// Coalesce rapid state updates
#scheduleEmit() {
if (this.#emitScheduled) return;
this.#emitScheduled = true;
queueMicrotask(() => {
this.#emitScheduled = false;
this.#emitStateImpl();
});
}
Subscription Filtering
// Only request data for topics panels actually need
const activeTopics = mergeSubscriptions(allPanelSubscriptions);
player.setSubscriptions(activeTopics); // Player only iterates these
Memory Management
Identifying Leaks
- Take heap snapshot A (baseline)
- Perform operation (open/close panel, play/seek)
- Force GC (DevTools → Memory → Collect garbage)
- Take heap snapshot B
- Compare: Objects in B not in A = potential leaks
Common Leak Sources
- Unremoved event listeners (especially on
window or document)
- Unreleased Comlink proxies (Worker not disposed)
- Retained message references in closed panels
- Subscription callbacks not unsubscribed on unmount
Prevention
FinalizationRegistry for Worker proxy cleanup (see ComlinkWrap)
useEffect cleanup functions for all subscriptions
WeakRef / WeakMap for caches that shouldn't prevent GC
- Explicit
.dispose() calls in panel unmount
Benchmarking
- Project benchmark suite:
benchmark/ directory
- Run:
cd benchmark && yarn start
- Measures: message throughput, deserialization speed, render time
- Use for before/after comparison when optimizing
1---2name: performance3description: Deep performance optimization knowledge for the Lichtblick codebase. Covers profiling techniques, common bottlenecks, memory management patterns, and optimization strategies specific to real-time data visualization.4---56# Performance Skill78## Profiling Workflow910### Chrome DevTools111. **Performance tab**: Record during playback, look for long tasks (>50ms)122. **Memory tab**: Take heap snapshots before/after operations, check for leaks133. **Performance Monitor**: Watch JS heap size, DOM nodes, layouts/sec in real-time144. **Layers panel**: Identify unnecessary compositing layers (GPU memory)1516### Key Metrics17- **Frame budget**: 16.6ms at 60fps — anything longer causes jank18- **Tick budget**: IterablePlayer caps at 300ms per tick19- **GC pressure**: Frequent minor GCs indicate excessive allocation20- **Transfer size**: Transferable objects (ArrayBuffer) should use zero-copy transfer2122## Common Bottlenecks2324### 1. Message Processing (Player → Pipeline)25- **Symptom**: Dropped frames during high-rate playback26- **Cause**: Too many messages per tick, deserialization cost27- **Fix**: Batch processing, Worker-based deserialization, subscription filtering2829### 2. Render State Building (Pipeline → Panel)30- **Symptom**: All panels re-render even when their data hasn't changed31- **Cause**: Missing memoization in `renderState.ts`, non-stable references32- **Fix**: Ensure `buildRenderState` returns same reference when data unchanged3334### 3. 3D Scene Updates (Panel rendering)35- **Symptom**: Low FPS in 3D panel with many objects36- **Cause**: Per-frame geometry creation, excessive draw calls37- **Fix**: `DynamicBufferGeometry` reuse, instanced rendering, frustum culling3839### 4. Chart Rendering (Plot panel)40- **Symptom**: Plot panel laggy with many data points41- **Cause**: Chart.js processing 50k+ points on main thread42- **Fix**: Worker-based dataset building (50k cap per series), OffscreenCanvas4344### 5. Memory Pressure (Caching)45- **Symptom**: Browser tab crashes or becomes unresponsive46- **Cause**: Cache exceeds budget, large messages retained47- **Fix**: Respect the 600MB player-level message cache budget (`CachingIterableSource`), evict behind read head, lazy deserialization. Note: this is separate from the 500MB default HTTP-layer cache in `RemoteFileReadable`/`CachedFilelike` used for remote file reads.4849## Optimization Patterns5051### Zero-Copy Transfer52```typescript53// Transfer ArrayBuffer to Worker (not copy)54Comlink.transfer({ buffer: myArrayBuffer }, [myArrayBuffer]);55// After transfer, myArrayBuffer.byteLength === 0 (detached)56```5758### Object Pooling (3D)59```typescript60// Reuse Vector3 instances instead of creating new ones61const tempVec = new THREE.Vector3();62function updatePosition(x: number, y: number, z: number) {63 tempVec.set(x, y, z);64 mesh.position.copy(tempVec);65}66```6768### Structural Sharing (State)69```typescript70// Only create new object if data actually changed71const newMessages = messages !== prevMessages ? [...messages, ...newBatch] : prevMessages;72```7374### Debounced Emission75```typescript76// Coalesce rapid state updates77#scheduleEmit() {78 if (this.#emitScheduled) return;79 this.#emitScheduled = true;80 queueMicrotask(() => {81 this.#emitScheduled = false;82 this.#emitStateImpl();83 });84}85```8687### Subscription Filtering88```typescript89// Only request data for topics panels actually need90const activeTopics = mergeSubscriptions(allPanelSubscriptions);91player.setSubscriptions(activeTopics); // Player only iterates these92```9394## Memory Management9596### Identifying Leaks971. Take heap snapshot A (baseline)982. Perform operation (open/close panel, play/seek)993. Force GC (DevTools → Memory → Collect garbage)1004. Take heap snapshot B1015. Compare: Objects in B not in A = potential leaks102103### Common Leak Sources104- Unremoved event listeners (especially on `window` or `document`)105- Unreleased Comlink proxies (Worker not disposed)106- Retained message references in closed panels107- Subscription callbacks not unsubscribed on unmount108109### Prevention110- `FinalizationRegistry` for Worker proxy cleanup (see `ComlinkWrap`)111- `useEffect` cleanup functions for all subscriptions112- `WeakRef` / `WeakMap` for caches that shouldn't prevent GC113- Explicit `.dispose()` calls in panel unmount114115## Benchmarking116117- Project benchmark suite: `benchmark/` directory118- Run: `cd benchmark && yarn start`119- Measures: message throughput, deserialization speed, render time120- Use for before/after comparison when optimizing