mobxjs/mobx-state-tree

applySnapshot is slow

lavrton opened this issue · 1 comments

Sandbox link or minimal reproduction code

Code:

import { types, getSnapshot, applySnapshot, applyPatch } from 'mobx-state-tree';
import * as jsonpatch from 'fast-json-patch';

// create simple model
const Shape = types.model('Shape', {
  x: 0,
  y: 0,
  width: 100,
  height: 100,
  rotation: 90,
  fill: 'red',
  stroke: 'black',
  name: ''
}).actions((self) => ({
  set(attrs) {
    Object.assign(self, attrs)
  }
}));

const Group = types.model('Group', {
  shapes: types.array(Shape)
});

// generate large number of data
const group = Group.create({
  shapes: new Array(10000).fill({})
});

// create initial snapshot
const firstSnapshot = getSnapshot(group);
// change shapes
group.shapes[500].set({ x: 20, y: 30 });

// revert change via mobx-state-tree
console.time('revert');
applySnapshot(group, firstSnapshot);
console.timeEnd('revert');


// apply change again
group.shapes[500].set({ x: 20, y: 30 });

// revert changes via manual patch calculations
console.time('revert2');
const secondShapshot = getSnapshot(group);
const patch = jsonpatch.compare(secondShapshot, firstSnapshot);
applyPatch(group, patch);
console.timeEnd('revert2');

Demo: https://codesandbox.io/p/sandbox/mst-perf-test-v93yqt?file=%2Fsrc%2Findex.ts%3A14%2C15

Describe the observed behavior

I have a large store with plenty of models. Every model has many attributes. Occasionally, I save a full snapshot of store into a memory to reload it later. I noticed that applying old snapshot works very slow, even if not much data is changed.

Describe the expected behavior

I would expect that applying a previous snapshot, that has almost the same data, should load very fast. In the demo I am trying to restore old data using two ways:

  1. Applying applySnapshot from mobx-state-tree
  2. Manually calculating patch with an external library and applying it.

On my machine I have 782ms for (1) and 14ms for (2). That is 55x faster.
I was thinking to use my second approach in the code, but looks like the library's patches don't exactly match patches of mst

I don't know mobx-state-tree internals, but I guess it can do something like this internally:

function applySnapshot(target, snapshot) {
   const currentSnap = getSnapshot(target);
   if (currentSnap === snaphot) {
     // do nothing, all good
     return;
   }
   return getStateTreeNode(target).applySnapshot(snapshot)
}

UPD: as I understand mst is doing that, only once on the top level, but not in children.

Hey @lavrton - thanks for filing this issue!

We are currently investigating performance issues and trying to make improvements. You can see some of the conversation and ongoing work here: #2095

I will add your sample code to my own personal benchmarking suite and dig in.

I can't give you a good estimate of when we might improve this, but your report here is going to really help us figure out where we can improve, and because you did a great job writing this up and including some ideas, I expect you'll see improvements in the coming months.

I wish I had a better answer for you, but thanks again for the details, and if you'd like to pitch in, I'm happy to help you get started with the internals. No worries if not, this has been great already.