# Travels Performance & Memory Benchmarks

This directory contains performance and memory benchmarks comparing Redux-undo, Zundo, and Travels.

## Test Scripts

### 1. `memory-performance-test.js` - Simulated implementations

Uses simplified simulated implementations to quickly compare core differences.

**Pros:**

- No dependencies to install
- Fast to run
- Clearly shows core algorithm differences

**Run:**

```bash
node --expose-gc memory-performance-test.js
```

### 2. `real-library-benchmark.js` - Real libraries

Uses real npm packages to reflect real-world scenarios.

**Pros:**

- Real environment behavior
- Accurate performance numbers
- Includes full library overhead

**Run:**

```bash
# Install dependencies
npm install

# Run benchmark
npm run test:real

# Or run manually
node --expose-gc real-library-benchmark.js
```

### 3. `matrix-benchmark.js` - Scenario matrix

Runs a multi-scenario synthetic benchmark that compares a full-snapshot history stack with Travels. It covers different state sizes, update shapes, and repeated rounds, then reports `median/p95` for every metric.

The matrix script reports `setState/update (ms)` as average time per update within each round. The older fixed-scenario scripts report total time for their full update loop.

**Run:**

```bash
# Default matrix: 10KB, 100KB, 1MB states; 5 rounds
npm run test:matrix

# Full matrix: includes 5MB state and 7 rounds
npm run test:full

# Lightweight CI smoke guard
npm run test:ci
```

## Test Scenarios

The original simulated and real-library scripts use the same fixed scenario:

- **Initial object size**: ~100 KB (nested objects, arrays)
- **Number of operations**: 100 updates
- **Update type**: Small changes (only 2 fields per update)
- **Metrics**:
  - Memory usage
  - setState/dispatch performance
  - Undo performance
  - Redo performance
  - Serialized size (persistence)
  - Serialization/deserialization performance

The matrix benchmark expands coverage:

| Dimension | Default matrix | Full matrix |
| --- | --- | --- |
| State sizes | 10KB, 100KB, 1MB | 10KB, 100KB, 1MB, 5MB |
| Update types | small patch, large patch, array insert/delete, deep object, Map/Set runtime | same |
| Rounds | 5 | 7 |
| Reported statistics | median, p95 | median, p95 |

The CI mode is intentionally smaller: 10KB and 100KB states, 20 updates, 3 rounds, and a persistence smoke guard for compact patch-history scenarios. It should catch obvious regressions without pretending that CI runners provide lab-quality latency numbers.

## Why `--expose-gc`?

The `--expose-gc` flag allows manual garbage collection (GC), which helps:

1. Measure memory usage more accurately
2. Reduce GC timing interference
3. Produce more stable results

## Metrics Explained

### Memory usage

Measure memory growth after 100 operations.

- **Redux-undo / Zundo**: store full state snapshots
- **Travels**: store JSON Patch

### setState performance

Measure total time for 100 state updates.

In the matrix benchmark, `setState/update (ms)` is normalized to average milliseconds per update so scenarios with different iteration counts are easier to compare.

### Undo/Redo performance

Measure the time to perform 50 consecutive undos and redos.

### Serialization

Measure serialized history size and serialization/deserialization times.

**Important when:**

- Persisting to localStorage
- Using IndexedDB
- Cross tab/worker messaging
- Cloud sync

## Expected Results

Based on design principles, expected results:

| Metric             | Redux-undo | Zundo   | Travels  |
| ------------------ | ---------- | ------- | -------- |
| Memory             | High       | High    | Low ⭐   |
| setState           | Fast ⭐    | Fast ⭐ | Medium   |
| Undo/Redo          | Fast ⭐    | Fast ⭐ | Medium   |
| Serialized size    | Large      | Large   | Small ⭐ |
| Serialization time | Slow       | Slow    | Fast ⭐  |

### Why is Travels setState relatively slower?

Because Travels generates JSON Patch, which involves:

1. Calculating the diff
2. Creating patch objects

**But the overhead is worth it:**

- Much smaller memory footprint
- Much faster serialization
- Standardized operation log

### When Travels shines

Travels has clear advantages in:

1. ✅ **Large state, small updates**
   - 1MB document, change one field
   - Redux-undo: store two 1MB snapshots
   - Travels: store one small patch

2. ✅ **Long history**
   - Keep 100+ history entries
   - Memory and serialization differences amplify

3. ✅ **Persistence**
   - localStorage (5-10MB limits)
   - Cloud sync (less traffic)
   - Cross-environment messaging

4. ✅ **Operation logs needed**
   - Auditing
   - Debugging
   - User behavior analysis

## Run all tests

```bash
npm run test:all
```

This will run in order:

1. Simulated implementations
2. Real libraries
3. Scenario matrix

## Latest Results (Node v22.21.1)

The tables below capture the output from running `yarn test:all` (which executes both benchmark scripts with `node --expose-gc`) on the current machine.

### Simulated implementations (`memory-performance-test.js`)

| Metric               | Redux-undo | Zundo     | Travels  |
| -------------------- | ---------- | --------- | -------- |
| Memory (MB)          | 11.8       | 11.8      | **0.32** |
| setState (ms)        | 42.74      | **41.27** | 88.16    |
| Undo (ms)            | 0.08       | **0.07**  | 18.65    |
| Redo (ms)            | 0.12       | **0.02**  | 20.34    |
| Serialized size (KB) | 11,626.66  | 11,626.46 | **20.6** |
| Serialize (ms)       | 12.86      | 11.88     | **0.06** |
| Deserialize (ms)     | 23.6       | 23.55     | **0.14** |

- Travels keeps simulated history sizes tiny (20.6 KB vs ~11 MB snapshots) and serializes >200x faster, while snapshot stores remain unbeatable for undo/redo latency.
- The Travels simulated setState cost (88 ms for 100 updates) is roughly 2x the snapshot stores, which matches expectations for generating JSON Patch.

### Real libraries (`real-library-benchmark.js`)

| Metric               | Redux-undo | Zundo     | Travels    |
| -------------------- | ---------- | --------- | ---------- |
| Memory (MB)          | 0.05       | **0.04**  | 0.16       |
| setState (ms)        | 0.35       | **0.3**   | 1.81       |
| Undo (ms)            | 0.25       | **0.12**  | 0.69       |
| Redo (ms)            | **0.07**   | 0.15      | 0.27       |
| Serialized size (KB) | 11,742.03  | 11,510.59 | **116.26** |
| Serialize (ms)       | 12.63      | 11.59     | **0.61**   |
| Deserialize (ms)     | 28.87      | 22.66     | **0.42**   |

- Even with real packages, Travels shrinks serialized history by roughly 100x and finishes (de)serialization in well under a millisecond.
- Snapshot-based stacks still win the hot-path operations (setState/undo/redo), so use cases prioritizing raw speed over persistence will still prefer Redux-undo/Zundo.

## Customize parameters

You can modify parameters in the scripts:

```javascript
// Adjust object size
const initialState = generateComplexObject(200); // change to 200KB

// Adjust number of operations
results.push(testTravels(500)); // change to 500 operations
```

## Environment

- Node.js >= 14
- Enough memory (4GB+ recommended)

## Notes

1. **Close other apps**: to improve accuracy

2. **Run multiple times**: V8 JIT and GC affect numbers; average results

3. **Relative differences matter**: absolute values vary by environment

4. **Scenario dependent**:
   - Small state: snapshot-based may be faster
   - Large changes: diff-based less advantageous
   - Large state + small changes + long history: Travels best

## Benchmarking best practices

To get accurate results:

```bash
# 1. Ensure consistent Node.js version
node --version

# 2. Clear npm cache
npm cache clean --force

# 3. Reinstall dependencies
rm -rf node_modules package-lock.json
npm install

# 4. Restart terminal before running
# 5. Close other apps
# 6. Run multiple times and average
for i in {1..3}; do
  echo "=== Run $i ==="
  npm run test:real
  sleep 5
done
```

## Contributing

If you find issues or have suggestions, please open an issue or PR!

## Resources

- [Travels repository](https://github.com/mutativejs/travels)
- [Mutative performance comparison](https://mutative.js.org/docs/getting-started/performance)
- [Redux-undo](https://github.com/omnidan/redux-undo)
- [Zundo](https://github.com/charkour/zundo)
