RetailMeNotSandbox / redux-mount-store

Redux store enhancer that makes it possible to mount sub-stores
MIT License
2 stars 3 forks source link

Immutable object transactions #9

Closed lawnsea closed 8 years ago

lawnsea commented 8 years ago

As described in #7, repeatedly performing immutable updates can be very slow. One part of the problem is that object-path-immutable doesn't provide a facility for performing multiple operations on an object and then returning a single new object with all affected paths cloned. For example:

let o = { foo: { bar: {} } };
o = objectPathImmutable.set(o, 'foo.bar.applesauce', 'chutney');
o = objectPathImmutable.set(o, 'foo.bar.jam', 'marmelade');

Both foo and bar are cloned twice in the above code. What we would like instead is to perform an immutable transaction. This patch implements an API that supports the same operations as object-path-immutable, but performed as a transaction.

This is partial progress toward fixing #7.


Benchmark

Code

`quick-benchmark.js` ``` javascript 'use strict'; const immutable = require('object-path-immutable'); const immutableTransaction = require('./lib/object-path-immutable-transaction'); const uuid = require('uuid'); const lorem = require('lorem-ipsum'); const assert = require('assert'); require('console.table'); function hrtimeToMs(hrtime) { return hrtime[0] * 1000 + hrtime[1] / 1e6; } function generateData(N) { const data = []; for (let i = 0; i < N; i++) { data.push({ id: uuid.v4(), value: lorem({ count: 10, units: 'words' }) }); } return data; } function runTrial(initial, data, fn) { const start = process.hrtime(); data.reduce(fn, initial); return hrtimeToMs(process.hrtime(start)); } const original = { foo: { } }; const results = [ 100, 200, 400, 800 ].map(N => { const data = generateData(N); const result = { N }; let control; result['object-path-immutable'] = runTrial(original, data, (o, datum, i) => { o = immutable.set(o, `foo.${datum.id}`, datum.value); if (i === N - 1) { control = o; } return o; }); result['object-path-immutable-transaction'] = runTrial(original, data, (o, datum, i) => { if (i === 0) { o = immutableTransaction(o); } o = o.set(`foo.${datum.id}`, datum.value); if (i === N - 1) { o = o.commit(); assert.deepEqual(control, o); } return o; }); return result; }); console.table(results); ```

Results

$ node quick-benchmark.js
N    object-path-immutable  object-path-immutable-transaction
---  ---------------------  ---------------------------------
100  5.735478               3.18941
200  13.796613              1.59718
400  51.963328              2.004868
800  209.149855             3.027543

lawnsea commented 8 years ago

Paging @slimelabs and @lzilioli

lzilioli commented 8 years ago

@lawnsea I would like to see where this method is going to be used to better assess this PR

lawnsea commented 8 years ago

I would like to see where this method is going to be used to better assess this PR

@lzilioli it will be used to apply a batch of deferred mounts and unmounts as a single transaction.

I'll add some comments to ensureCloned to make it more clear what it's up to. I've deferred extensive documentation for this module I hope to land a modified version into object-path-immutable, which currently supports a non-transactional chained syntax.

lawnsea commented 8 years ago

@lzilioli @slimelabs updated with responses to y'all's feedback

cappslock commented 8 years ago

Left some comments, generally looks great. Why the hell doesn't this exist already? Super useful.

lawnsea commented 8 years ago

@lzilioli @slimelabs I've responded to all outstanding feedback. Any last comments before I land this?

cappslock commented 8 years ago

Go pho it

lzilioli commented 8 years ago

LGTM