# Performance Report ### General `object-observer` is purposed to be a low-level library. It is designed to track and deliver changes in a __synchronous__ way, being __async__ possible as opt in. As a such, I've put some effort to optimize it to have the least possible footprint on the consuming application. Generally speaking, the framework implies some overhead on the following, when operating on __observed__ data sets: - mutations of an observed objects: proxying the changes, detecting if there are any interested observers/listeners, building and delivering the changes - reading from observed arrays: detection of read property is performed in order to supply array mutation methods like `shift`, `push`, `splice`, `reverse` etc - mutation of __values__ that are objects / arrays: additional overhead comes from attaching / detaching those to the observed graph, proxying newcomers, revoking removed ones, creating internal system observers Pay attention: __each and every__ object / array (including all the nested ones) added to the observed tree processed by means of cloning and turning into observed one; in the same way, __each and every__ object / array removed from the observed tree is being 'restored' (proxy revoked and cloned object returned, but not to the actual original object). Tests described below are covering most of those flows. __Overall, `object-observer`'s impact on the application is negligible from both, CPU and memory aspects.__ ### Hardware All of the benchmarks below were performed on __MacBook Pro__ (model 2019, Ventura 13.2.1), plugged in at the moment of tests: - CPU 2.6 GHz 6-Core Intel Core i7 - 16 GB 2667 MHz DDR4 ### Tests ##### __CASE 1__ - creating observables, mutating nested primitive properties of an observable 1. __Creating__ in loop 100,000 observable from the object below, having few primitive properties, one non-observable nested object level 1 (Date), one nested object level 1, one nested object level 2 and one nested array level 1: ```javascript let person = { name: 'Anna Guller', accountCreated: new Date(), age: 20, address: { city: 'Dreamland', street: { name: 'Hope', apt: 123 } }, orders: [] }; // creation, while storing the result on the same variable for (let i = 0; i < creationIterations; i++) { observable = Observable.from(person); } ``` 2. Last observable created in previous step is used to __mutate__ nested primitive property, while 2 observers added to watch for the changes, as following: ```javascript // add listeners/callbacks Observable.observe(observable, changes => { if (!changes.length) throw new Error('expected to have at least one change in the list'); else changesCountA += changes.length; }); Observable.observe(observable, changes => { if (!changes) throw new Error('expected changes list to be defined'); else changesCountB += changes.length; }); // deep mutation performed in a loop of 1,000,000 for (let i = 0; i < mutationIterations; i++) { observable.address.street.apt = i; } ``` 3. Then the same setup is used to __add__ 1,000,000 nested primitive properties, as following: ```javascript for (let i = 0; i < mutationIterations; i++) { observable.address.street[i] = i; } ``` 4. Finally, those newly added properties are also being __deleted__, as following: ```javascript for (let i = 0; i < mutationIterations; i++) { delete observable.address.street[i]; } ``` All of those mutations are being watched by the listeners mentioned above and the counters are being verified to match the expectations. Below are results of those tests, where the time shown is of a single operation in average. All times are given in 'ms', meaning that cost of a single operation on Chromiums/NodeJS is usually half to few nanoseconds. Firefox values are slightly higher (worse).
| create observable 100,000 times |
mutate primitive depth L3; 1M times |
add primitive depth L3; 1M times |
delete primitive depth L3; 1M times |
|
|---|---|---|---|---|
| 0.001 ms | 0.0004 ms | 0.0006 ms | 0.0005 ms | |
| 0.001 ms | 0.0004 ms | 0.0006 ms | 0.0005 ms | |
| 0.0047 ms | 0.0007 ms | 0.0007 ms | 0.0011 ms | |
| 0.0016 ms | 0.001 ms | 0.001 ms | 0.001 ms |
| push 100,000 objects | replace nested array 100,000 times | pop 100,000 objects | |
|---|---|---|---|
| 0.002 ms | 0.003 ms | 0.0008 ms | |
| 0.002 ms | 0.003 ms | 0.0008 ms | |
| 0.0077 ms | 0.0096 ms | 0.0011 ms | |
| 0.005 ms | 0.005 ms | 0.001 ms |