10

I read from different sources that mobx outperforms react renderer and are faster then redux. However if I made couple of tests it shows that adding new data to mobx observables are pretty slow. In react-native environment every milliseconds counts and it's tricky to use solution where even looping over 200 elements and filling array takes more then 100ms As I really enjoy mobx I hope someone can take a look a test code and give me some hints - what I'm doing wrong and how to improve performance.

import {observable, transaction,autorun} from 'mobx'; 
class Runner {

list = observable([]);

run() {


    const start = new Date().getTime();
    transaction(() => {
        for (let i = 0; i < 200; i++) {
            this.list.push({
                id: observable(i),
                one: observable('1'),
                two: '2',
                three: 3,
                x: 'xxxxx',
                y: 'yyyyy',
                z: 'zzzzz',
                z1: 'zzzzz',
                z2: 'zzzzz',
                z3: 'zzzzz',
                z4: 'zzzzz',

            });
        }
    });

    console.log('Execution time: ' + (new Date().getTime() - start) + 'ms services ');
}
}
const runner = new Runner();
autorun(() => console.log(runner.list));
runner.run();

On my laptop it's takes about 120ms to complete. Without observable-s it's takes less then 1ms

Mac
  • 468
  • 1
  • 5
  • 10
  • What's the performance like if you remove `observable` from around the `id` and `one` properties? (I note: you are already observing the entire `list`, which I guess is why you're using `transaction`...?) Note: quickly browsing MobX documentation doesn't reveal any examples where they observing specific properties of objects; most examples show `observable` at the level of whole objects. – searlea Jul 19 '16 at 13:56
  • Removing `observable` from around the `id` and from `one` makes no difference Most notable impact seems to ba cause object size, There is big difference if object I pushing into list contains 1 or 10 properties ( 200 objects with 1 property takes ~40ms) – Mac Jul 19 '16 at 14:40

5 Answers5

31

You are doing nothing fundamentally wrong (except, that, as Robert already indicated, currently all properties are made observable anyway, because observable by default recurses on plain data structures).

The main thing is that you aren't really using MobX yet :) Your test results are correct, observable data structures are a lot more expensive than plain structures. It's a little bit of comparing apples with oranges. Or for a better analogy; It is like benchmarking concatenating strings to produce HTML versus using the DOM to generate HTML. The strings will always win.

However, in the bigger picture of a complete app, things are different. Suppose that you need to change the border color of an element. Then the DOM will probably suddenly be a lot more efficient, as it allows you only to mutate a very specific piece of your HTML, and the DOM is smart enough to decide precisely which pixels need to be repainted on the screen. That would all be a lot harder if you had just a bare string.

MobX is similar, doesn't get its performance from fast data structures, but from its smartness. If an observable array is modified, MobX pinpoints precisely which actions, and components need to be rendered to be consistent with any computation you write. Because MobX is able to establish far more fine-grained 'event listeners' than you would do when writing that kind of stuff by hand, and because MobX can optimize dependency trees, which a human programmer probably won't bother doing, MobX can be very fast. But you have to see it in the bigger picture of the complete lifecycle of your state. If you just want to create some objects and arrays very fast, nothing will beat plain arrays and constructor functions.

I recommend reading this blog by @lavrton https://medium.com/@lavrton/how-to-optimise-rendering-of-a-set-of-elements-in-react-ad01f5b161ae#.enlk3n68g. It nicely demonstrates all the hoops you need to jump through when optimizing manually, just to get close to the speed at which MobX can update components.

I hope that explains your results!

P.S. there are currently a few known cases where MobX can be slow when sorting or cleaning up a large collection. Those will be addressed by the upcoming 2.4.0 release though.

Hossein Arsheia
  • 361
  • 1
  • 4
  • 7
mweststrate
  • 4,890
  • 1
  • 16
  • 26
  • Thanks for the elaboration :) – robertklep Jul 19 '16 at 19:48
  • 2
    Michel, my objective was not to benchmark MobX, I found on real react-native + MobX application ( on emulator ) that showing 5 entries on screen was sluggish, this was the reason I made this silly test. On device quite same loop with 5 entries took even more - about 40ms. As react-native doing everything on same javascript thread then 40ms on wrong place will be noticeable One thing I'm not realised is that MobX doing much more. MobX also rendering stuff. So that if I measured 40ms of "loop time" then it's actually means looping over entries + rendering everything on the screen :) – Mac Jul 19 '16 at 20:27
11

observable() makes all values that you push in the array (recursively) observable, which may not be what you need.

For instance, from your example code it may follow that you only want to observe changes to the id and a properties, not to the rest. In which case, you can use the asFlat modifier on your observable array:

const { observable, autorun, transaction, asFlat } = mobx;
....
list = observable(asFlat([]));

This will allow you to observe changes to list itself (new items added, items removed, etc), or to the id and a properties of the list items, but not the rest.

This speeds up your test considerably for me: from 35ms to about 5ms.

robertklep
  • 198,204
  • 35
  • 394
  • 381
  • Using `asFlat` modifier has huge impact (10x perf. increase) and it's makes now sense why adding more properties to the inner object slowed insertion down even more – Mac Jul 19 '16 at 18:31
3

Don't push but replace and you will see the performance many times faster.

When evaluating the library and tried thousands or records (to push the limits) the load would freeze the browser. I changed it to replace and it was a matter of milliseconds.

s1mpl3
  • 1,456
  • 1
  • 10
  • 14
1

I tried your code in a fiddle, the result was 35ms. But usually, for() loops, are more expensive than other alternatives, here is a more faster code using Array.map() with 1000 examples, in my machine performs at ~59ms(try many times to get average):

const { observable, autorun, transaction } = mobx;

class Runner {

  @observable list = [];

  run() {
    const start = new Date().getTime();
    transaction(() => {
     this.list = new Array(1000).fill(0).map((row, i) => {
        return {
          id: observable(i),
          a: observable('1'),
          two: '2',
          three: 3,
          x: 'xxxxx',
          y: 'yyyyy',
          z: 'zzzzz',
          z1: 'zzzzz',
          z2: 'zzzzz',
          z3: 'zzzzz',
          z4: 'zzzzz',
        };
      });
    });
    console.log('Execution time: ' + (new Date().getTime() - start) + 'ms services ');
    console.log('list observable->', this.list);
  }
}
const runner = new Runner();
autorun(() => console.log(runner.list));
runner.run();

jsFiddle

However you should compare a React real case using Redux in order to get a fair conclusion about Mobx observables perfomance when it comes to updating components.

Community
  • 1
  • 1
agualbbus
  • 71
  • 1
  • 4
  • after doing `this.list = new Array(1000).fill..` the `this.list ` will be regular javascript object and not observable - regular javascript objects are fast :) – Mac Jul 19 '16 at 16:04
  • @Mac that is not true, take a look at mobx documention, new assigments to a Mobx observable result in Mobx observable. – agualbbus Jul 19 '16 at 16:43
  • If you are using decoratorst then yes, otherwise you losing reference to the observable. try yourself [jsFiddle](https://jsfiddle.net/macmanaman/7mhuew90/3/) – Mac Jul 19 '16 at 17:13
0

How's the performance if you construct all your new items outside of the transaction?

const start = new Date().getTime();
const newItems = [];
for (let i = 0; i < 200; i++) {
    newItems.push({
        id: observable(i),
        one: observable('1'),
        two: '2',
        three: 3,
        x: 'xxxxx',
        y: 'yyyyy',
        z: 'zzzzz',
        z1: 'zzzzz',
        z2: 'zzzzz',
        z3: 'zzzzz',
        z4: 'zzzzz',

    });
}
transaction(() => {
    Array.prototype.push.apply(this.items, newItems)
});

Then try without the observable around id and one, and then try without the transaction (since all the changes to this.items are now done in a single call to Array.prototype.push...)

searlea
  • 8,173
  • 4
  • 34
  • 37
  • The performance is about same and I can't see any big impact if I remove transaction wrapper. Speed is between 90-120ms – Mac Jul 19 '16 at 15:10