Performance of persistence mechanisms

Link to TestResult on 2GHz Core i7Units
Firefox 31.2Chrome 38.0IE 11

circular.forage Pass Pass Pass bool
circular.forage_on_ls Fail Fail Fail bool
circular.rhaboo Pass Pass Pass bool

proto.forage Fail Fail Fail bool
proto.forage_on_ls Fail Fail Fail bool
proto.rhaboo Pass Pass Pass bool

sparse.forage Fail Fail Fail bool
sparse.forage_on_ls Fail Fail Fail bool
sparse.rhaboo Pass Pass Pass bool

many-ints.forage 120 50 1.900 ms/write
many-ints.forage_on_ls 0.280 0.110 0.240 ms/write
many-ints.rhaboo 0.027 0.050 0.040 ms/write 0.012 0.015 0.190 ms/write

array.forage 0.023 | 137* 0.004 | 58 0.004 | 2.500 ms/element
array.forage_on_ls 0.010 | 0.400 0.001 | 0.240 0.005 | 0.800 ms/element
array.rhaboo 0.065 | 0.065 0.100 | 0.100 0.035 | 0.090 ms/element 0.300 | 0.100 0.300 | 0.090 0.002 | 0.600 ms/element

readarray.forage ms/write
readarray.forage_on_ls ms/write
readarray.rhaboo ms/write ms/write

objects.forage 126,000 55,000 16,000 ms
objects.forage_on_ls 5700 6800 8800 ms
objects.rhaboo 75 100 55 ms 2800 1700 5000 ms


*Yup, that's one hundred and thirty seven ms per element
*Falls back to localStorage


The timings are subjectively observed means over several attempts.

The conditions usually favour disk and memory caches being populated

Each test is run on rhaboo, on localForage using its chosen driver and on localForage forced to use localStorage. Some tests are also run on store.js, but not if its clear that they have no chance.

Circular tests

Here we see that rhaboo and IndexedDB can deal with structures where child properties reference their containing ancestors, a simple example of which is defined in circular.js and used in this test. JSON.stringify detects such cases and explicitly chickens out.

Prototype tests

Here we have a standard OO structure in proto.js which we hope to recover the prototype chain of. Only rhaboo attempts this.

Sparse tests

Here we have the array from hell containing nulls, undefineds, deleted entries, deleted entries just before length, non-numerically named properties and even some well-behaved entries. The restored array is inspected for accuracy in every possible way.

Only rhaboo passes this test

Many integers tests

Here we have 10 integers independently stored in the medium and we consecutively overwrite them. For localForage these are 10 different keys. For rhaboo, a root persistent is always an object and cannot be a mere integer, so we use 10 properties in a single persistent.

The results indicate that IndexedDB is spectacularly inefficient when asked to store lots of little things. It is, however, a lot more efficient when asked to store larger objects.

Rhaboo beats everything in this scenario.

Array tests

Here we have an array of 1000 integers which we first create and then stepwise overwrite.

We see that rhaboo is slower at creating the array but faster at making small updates to it. Both statements become more true with increasing array length. This is the expected result because rhaboo splits large objects over several localStorage entries without the benefit of native code. Stringifying is native and therefore fast, but wasteful if a large object is re-stringified for the sake of a small change.

Object tests

In this case we have an array of 1000 references to one of three medium-sized objects. We initialise them without timing and then measure the time taken to overwrite them 1000 times with references to another of the same three objects.

Rhaboo wins because each of the three objects is stored only once. The other mechanisms don't notice that there are only three of them and store them 1000 times over. Rhaboo has already stored the three objects during initialisation and merely needs to update references to them during the timed run. This resembles the behaviour of JS objects in memory.