Uniview

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=full

URL Parameters

The benchmark supports query parameters for quick access:

ParameterValuesDescription
demobenchmarkSelect benchmark demo
updatefull | incrementalUpdate mode
frameworkreact | solidPlugin framework
runtimeworker | node-serverRuntime 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):

MetricDescriptionTypical Values
Operations performedTotal button clicksUser-dependent
Last operationTime for most recent operation5-15ms
Avg time/operationMean operation latency6-10ms
Messages in last opRPC messages sent1-5 (incremental), 1 (full)
Avg messages/opMean messages per operation1.5-3 (incremental)
Avg bytes/opMean bandwidth per operation50-200KB

Message Metrics (Per Message)

These metrics measure the RPC communication efficiency:

MetricDescriptionFull TreeIncremental
Total messagesCumulative RPC callsLowerHigher
Total bytesCumulative bandwidthHigherLower*
Bytes/messageAverage message size~87KB~69KB
Time/messageSerialization 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.0

Incremental Mode:

Last operation: 6.30ms
Total messages: 202
Total bytes: 13.95 MB
Bytes/message: 69,081
Avg messages/op: 2.0

Analysis

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

  1. React/Solid Reconciler detects changes during render
  2. MutationCollector records each change type
  3. End of commit flushes mutations to RPC
  4. 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 lookups
  • textIndex: 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

  1. Batch Updates: Group related state changes to reduce mutation count
  2. Stable IDs: Ensure list items have stable keys for efficient diffing
  3. Avoid Deep Nesting: Flatten tree structure where possible
  4. 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

On this page