Benchmark
Performance testing and comparison between update modes
The benchmark plugin helps you measure and compare performance between full-tree and incremental update modes. It's useful for understanding the trade-offs and validating performance improvements.
Running the Benchmark
Quick Start
# Start the full demo environment
cd examples/host-svelte-demo
pnpm dev:all
# Open benchmark with incremental mode
open http://localhost:5173?demo=benchmark&update=incremental
# Or full-tree mode for comparison
open http://localhost:5173?demo=benchmark&update=fullURL Parameters
The benchmark supports query parameters for quick access:
| Parameter | Values | Description |
|---|---|---|
demo | benchmark | Select benchmark demo |
update | full | incremental | Update mode |
framework | react | solid | Plugin framework |
runtime | worker | node-server | Runtime mode |
Example: http://localhost:5173?demo=benchmark&framework=react&update=incremental&runtime=worker
Benchmark Configuration
Test Load
The benchmark initializes with 1000 items and allows up to 2000 items maximum. Each item contains:
- Unique ID
- 20 words of lorem ipsum text (~100-150 characters)
This provides sufficient stress for modern computers while remaining responsive.
Operations
Manual Operations:
- Add 10 Items - Appends 10 new items to the list
- Remove 10 Items - Removes last 10 items
- Update All Texts - Changes text on all items (stress test)
- Update Single Item - Modifies one random item
Auto-Benchmark:
- Runs 50 cycles alternating between add and modify
- Auto-stops at 500 cycles or when reaching max items
- Measures sustained performance under load
Metrics Explained
Operation Metrics (Per Click)
These metrics measure performance from the user's perspective (each button click):
| Metric | Description | Typical Values |
|---|---|---|
| Operations performed | Total button clicks | User-dependent |
| Last operation | Time for most recent operation | 5-15ms |
| Avg time/operation | Mean operation latency | 6-10ms |
| Messages in last op | RPC messages sent | 1-5 (incremental), 1 (full) |
| Avg messages/op | Mean messages per operation | 1.5-3 (incremental) |
| Avg bytes/op | Mean bandwidth per operation | 50-200KB |
Message Metrics (Per Message)
These metrics measure the RPC communication efficiency:
| Metric | Description | Full Tree | Incremental |
|---|---|---|---|
| Total messages | Cumulative RPC calls | Lower | Higher |
| Total bytes | Cumulative bandwidth | Higher | Lower* |
| Bytes/message | Average message size | ~87KB | ~69KB |
| Time/message | Serialization overhead | ~0.1μs | ~0.08μs |
*Note: Incremental mode may show higher total bytes in some scenarios due to message overhead. This is more noticeable with small trees.
Performance Comparison
Typical Results (1000 items, React)
Full Tree Mode:
Last operation: 8.10ms
Total messages: 101
Total bytes: 8.76 MB
Bytes/message: 86,697
Avg messages/op: 1.0Incremental Mode:
Last operation: 6.30ms
Total messages: 202
Total bytes: 13.95 MB
Bytes/message: 69,081
Avg messages/op: 2.0Analysis
When Incremental is Better:
- Large lists with frequent small updates
- Adding/removing individual items
- Text updates on existing nodes
- Bandwidth-constrained environments
When Full Tree is Better:
- Small trees (< 100 items)
- Complete re-renders (theming, layout changes)
- Simple apps with minimal updates
- When message overhead exceeds payload savings
How Incremental Updates Work
Mutation Types
Instead of sending the entire tree, incremental mode sends mutations:
// Append new child
type AppendChildMutation = {
type: "appendChild";
parentId: string;
node: UINode;
};
// Update text content
type SetTextMutation = {
type: "setText";
textNodeId: string;
text: string;
};
// Update props
type SetPropsMutation = {
type: "setProps";
nodeId: string;
props: Record<string, JSONValue>;
};Collection Process
- React/Solid Reconciler detects changes during render
- MutationCollector records each change type
- End of commit flushes mutations to RPC
- Host applies mutations to existing tree
Host-Side Application
The host maintains a MutableTree that applies mutations:
const mutableTree = new MutableTree();
// On full tree update
mutableTree.init(newTree);
// On incremental update
mutableTree.applyMutations(mutations);The tree uses indexing for O(1) lookups:
nodeIndex: Map<string, UINode>- Element lookupstextIndex: Map<string, TextInfo>- Text node tracking
Best Practices
Choosing Update Mode
Use Incremental When:
- List has 100+ items
- Frequent individual item updates
- Network bandwidth is limited
- Latency matters (real-time apps)
Use Full Tree When:
- Simple forms or small UIs
- Complete re-renders are common
- Debugging rendering issues
- Plugin doesn't support incremental
Optimizing Performance
- Batch Updates: Group related state changes to reduce mutation count
- Stable IDs: Ensure list items have stable keys for efficient diffing
- Avoid Deep Nesting: Flatten tree structure where possible
- Measure First: Use benchmark to validate assumptions
Debugging
High message count?
- Check for unstable component keys
- Look for unnecessary re-renders
- Consider batching state updates
Large message sizes?
- Reduce prop payload sizes
- Avoid passing large objects
- Use references/IDs instead of data
Implementation Details
Plugin Side
// Enable incremental mode
import { startWorkerPlugin } from "@uniview/react-runtime";
startWorkerPlugin({
App: MyBenchmarkApp,
mode: "incremental", // "full" | "incremental"
});Host Side
Incremental mode is transparent to hosts. The controller handles both modes:
const controller = createWorkerController({
pluginUrl: "/plugins/benchmark-incremental.worker.js",
});
// Works with both full and incremental plugins
controller.subscribe((tree) => renderTree(tree));Future Improvements
- Batching: Group rapid mutations into single RPC call
- Compression: Delta compression for text updates
- Virtual Scrolling: Render only visible items
- Memory Profiling: Track heap usage during benchmarks