Mutative - 10x faster than Immer

2022-12-31

Mutative - A JavaScript library for efficient immutable updates. In the benchmark below, it is faster than a naive handcrafted reducer and more than 10x faster than Immer.

Motivation

Writing immutable updates by hand is usually difficult, prone to errors, and cumbersome. Immer helps us write simpler immutable updates with “mutative” logic.

However, Immer can introduce runtime overhead. In the benchmark below, Immer performs much worse when auto-freeze is disabled, while Mutative disables auto-freeze by default. In scenarios such as cross-process communication or remote data transfer, repeatedly freezing immutable data can also become expensive.

There are also other areas to improve, such as better type inference, non-intrusive marking, support for more immutable data types, safer immutable updates, and additional edge cases.

This is why Mutative was created.

Repo: https://github.com/unadlib/mutative

Mutative vs Immer Performance

Mutative passed all of Immer’s test cases.

Measured in ops/sec while updating 50K arrays and 1K objects. Higher is better (view source). [Mutative v0.5.0 vs Immer v10.0.1]

Benchmark

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Naive handcrafted reducer - No Freeze x 3,692 ops/sec ±1.28% (95 runs sampled)
Mutative - No Freeze x 5,425 ops/sec ±1.70% (93 runs sampled)
Immer - No Freeze x 5.08 ops/sec ±0.58% (17 runs sampled)

Mutative - Freeze x 818 ops/sec ±1.49% (96 runs sampled)
Immer - Freeze x 357 ops/sec ±0.79% (92 runs sampled)

Mutative - Patches and No Freeze x 746 ops/sec ±0.83% (96 runs sampled)
Immer - Patches and No Freeze x 5.08 ops/sec ±0.25% (17 runs sampled)

Mutative - Patches and Freeze x 408 ops/sec ±0.28% (96 runs sampled)
Immer - Patches and Freeze x 264 ops/sec ±0.59% (91 runs sampled)

The fastest method is Mutative - No Freeze

Run yarn benchmark to measure performance.

OS: macOS 12.6, CPU: Apple M1 Max, Node.js: 16.14.2

In this benchmark, Immer performs best with auto-freeze enabled. When auto-freeze is disabled for this workload, Mutative has a much larger lead, especially with large data structures.

Overall, Mutative has a huge performance lead over Immer in more performance testing scenarios. Run yarn performance to get all the performance results locally.

Features and Benefits

  • Mutation makes immutable updates - Immutable data structures for objects, arrays, Sets, and Maps.
  • High performance - More than 10x faster than Immer by default in the benchmark above, and even faster than a naive handcrafted reducer.
  • Optional state freezing - Immutable data is not frozen by default.
  • JSON Patch-compatible patches - Can emit RFC 6902-compatible patches when configured.
  • Custom shallow copy - Supports more immutable data types.
  • Immutable and mutable data markers - Allows non-invasive marking.
  • Safer mutable data access in strict mode - Makes immutable updates safer.
  • Reducer support - Works with reducer functions and other immutable state libraries.

Difference between Mutative and Immer

Mutative Immer
Custom shallow copy
Strict mode
No data freeze by default
Non-invasive marking
Complete freeze data
Non-global config
async draft function
RFC 6902-compatible patch output (configurable)

Mutative has fewer bugs such as accidental draft escapes than Immer, view details.

Installation

1
yarn install mutative # npm install mutative

Usage

Use create() for draft mutation to get a new state, which also supports currying.

1
2
3
4
5
6
7
8
9
10
11
import { create } from 'mutative';

const baseState = {
foo: 'bar',
list: [{ text: 'todo' }],
};

const state = create(baseState, (draft) => {
draft.foo = 'foobar';
draft.list.push({ text: 'learning' });
});

In this basic example, the changes to the draft are ‘mutative’ within the draft callback, and create() is finally executed with a new immutable state.

create(state, fn, options) - The options argument is optional.

  • strict - boolean, the default is false.

    Forbid accessing non-draftable values in strict mode (unless using unsafe()).

    It is recommended to enable strict in development mode and disable strict in production mode. This keeps explicit returns safer during development while preserving production performance. If you need to return a value that does not contain any current draft, or if you need to return undefined, use rawReturn().

  • enablePatches - boolean | { pathAsArray?: boolean; arrayLengthAssignment?: boolean; }, the default is false.

    Enable patches and return patches/inversePatches.

    To control the generated patch shape, configure pathAsArray and arrayLengthAssignment. pathAsArray defaults to true; when enabled, paths are arrays. Set it to false to emit JSON Pointer strings. arrayLengthAssignment defaults to true; when enabled, array length changes are included in patches. For RFC 6902-compatible JSON Patch output, use JSON Pointer paths and disable array length assignment. This may have additional performance cost. View related discussions.

  • enableAutoFreeze - boolean, the default is false.

    Enable autoFreeze, return a frozen state, and enable circular reference checking only in development mode.

  • mark - (target) => ('mutable'|'immutable'|function)

    Set a marker to determine whether a value is mutable or immutable. It can also return a shallow copy function. autoFreeze and patches should both be disabled when using this option.

FAQs

  • Why does Mutative have such good performance?

Mutative focuses its optimizations on shallow copies, lazy drafts, the finalization process, and related internal paths.

  • I’m already using Immer, can I migrate smoothly to Mutative?

Yes. Unless you must support Internet Explorer, Mutative supports almost all Immer features, and you can usually migrate from Immer to Mutative smoothly.

Migration is also not possible in React Native environments that do not support Proxy.

Conclusion

Mutative is inspired by Immer.

Mutative focuses on efficient immutable updates, performance improvements, and better APIs for a stronger development experience. If you think Mutative is useful, feel free to give it a star.

Repo: https://github.com/unadlib/mutative