Shopify/batman — pull request #627 by hornairs (2012-12-20 23:11:43+00:00)
Title: Lazily pushed smarter source trackers

@jamesmacaulay @nciagra @kristianpd please review!

Some perf improvements I got from :santa: ! :gift: :gift_heart: :christmas_tree:

Each time an accessor is executed, we need to keep track of all the other `get`s done in that accessor so that when those `get`ted properties change we can recompute that accessor and propagate the change. This is implemented by pushing a source tracker set to a global stack of source tracker sets each time a get starts, and then when any get happens inside a get, adding that inner property to the source tracker set at the top of the stack and then recursing.

These source tracker sets used to be bonified `SimpleSet`s, which @kristianpd recently changed to be arrays. I thought this would have broken things since it removes the uniqueness constraint but I think it ends up causing superfluous accessor recalculations. More on that in another PR.

In any case, creating these arrays and pushing them onto this stack for every single get seems a little heavy handed. This PR switches it up to simply set a boolean flag on the stack that says, hey, I should have an empty array at the top of me, so when you go to add something to that array its on you to make it. This has the effect that if when the accessor is calculated and no inner gets are done, no array is ever instantiated, nor pushed, because no one tries to push to it. This boolean flag is then reset at the end of a calculation.

This also switches the source tracker stack to be a constant in the file accessed using closure scope instead of as an object property on `Batman.Property` to avoid that object lookup every time we do a get.

Raw data:

On this branch:
```
getting with no sources x 837 ops/sec ±0.59% (97 runs sampled)
getting with 1 source x 218 ops/sec ±2.41% (88 runs sampled)
getting with 3 sources x 99.33 ops/sec ±1.95% (76 runs sampled)
```

On master:
```
getting with no sources x 679 ops/sec ±1.40% (99 runs sampled)
getting with 1 source x 203 ops/sec ±1.61% (88 runs sampled)
getting with 3 sources x 104 ops/sec ±2.33% (78 runs sampled)
```

The important graph is this one, comparing on average how long it takes to recompute an uncached accessor with 0 other sources:

![Screen Shot 2012-12-20 at 6 02 21 PM](https://f.cloud.github.com/assets/158950/25855/cdc94500-4af9-11e2-98a3-f0c75a07acfa.png)

Changes on the left, master on the right. 19% faster on the branch which seems to be within the error bars of 0.59% and 1.4%.

The other two cases are for an accessor with one source and an accessor with three sources, which shouldn't really benefit from this change:

![Screen Shot 2012-12-20 at 6 02 31 PM](https://f.cloud.github.com/assets/158950/25853/cdb65544-4af9-11e2-9040-a4e1cd195cb8.png)
![Screen Shot 2012-12-20 at 6 02 27 PM](https://f.cloud.github.com/assets/158950/25854/cdc1a264-4af9-11e2-9020-c566e72cbb15.png)

It looks like one test is faster on the branch and one isnt, but I think that is just tiller being silly and not starting the graph's Y axis at 0. On the branch we have x1 = 218 and x2 = 99, and on master we have x1 = 203 and x2 = 104, both with roughly 2% error bars on both points and branches, which I think suggests there was no change at all for these cases :)

Thoughts?

PS: If you have any thoughts for dealing with dummy source trackers (like the kind that are added on every `set`) feel free to pipe in!