Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

make isEqual() decide equality of property values. #10166

Closed

Conversation

cowboyd
Copy link
Contributor

@cowboyd cowboyd commented Jan 7, 2015

For vanilla properties, calling set with the current value, will not
fire any property changes. Currently, this comparison of "sameness"
happens with the === operator which compares object identity.

This change uses the isEqual method to compare property values
instead. In most cases, this exactly the same as ===, but does allow
for programmer-defined objects to decide whether they are equal to each
other and whether notifications should be fired when one replaces the
other.

To define a simple, immutable value object today that works seemlessly
with the Ember change notification system today, you have to make an
identity map coupled with a factory method (which could be an overridden
version of create) such that:

Car.for("ford", "mustang") === Car.for("ford", "mustang") //=> same object

Until such time as WeakReference is a thing in javascript, this
approach becomes unwieldy for types that have a large or perhaps infinite
number of values, since ensuring referential integrity means retaining
every instance ever created.

By allowing types to define their own notion of equality as pertains to
property access, you allow programmers to enrich the vocabulary of basic
property values without having to worry about over-chattiness, or worse
infinite-loopiness of observer notification.

For vanilla properties, calling `set` with the current value, will not
fire any property changes. Currently, this comparison of "sameness"
happens with the `===` operator which compares object identity.

This change uses the `isEqual` method to compare property values
instead. In most cases, this exactly the same as `===`, but does allow
for programmer-defined objects to decide whether they are equal to each
other and whether notifications should be fired when one replaces the
other.

To define a simple, immutable value object today that works seemlessly
with the Ember change notification system today, you have to make an
identity map coupled with a factory method (which could be an overridden
version of `create`) such that:

Car.for("ford", "mustang") === Car.for("ford", "mustang") //=> same object

Until such time as `WeakReference` is a thing in javascript, this
approach becomes unwieldy for types that have a large or perhaps infinite
number of values, since ensuring referential integrity means retaining
every instance ever created.

By allowing types to define their own notion of equality as pertains to
property access, you allow programmers to enrich the vocabulary of basic
property values without having to worry about over-chattiness, or worse
infinite-loopiness of observer notification.
@rwjblue
Copy link
Member

rwjblue commented Jan 7, 2015

It seems good, but it is in a very concerning area of the codebase. This could massively tank performance if not carefully considered and tested.

@ahacking
Copy link

ahacking commented Jan 7, 2015

Maybe an addon based on https://www.npmjs.com/package/ember-computed-change-gate would be more appropriate for providing isEqual() based equality checking where desired so mainline performance is not impacted.

@cowboyd
Copy link
Contributor Author

cowboyd commented Jan 7, 2015

@rwjblue agree 100% The total running time of the test suite was not affected, but a more comprehensive benchmark should be taken into consideration. I couldn't figure out how to run the benchmarks in the benchmarks/, and I couldn't tell from the benchmarks/README whether it was something anybody actually used. How does ember catch performance regressions?

@rwjblue
Copy link
Member

rwjblue commented Jan 7, 2015

@cowboyd - We have recently started using https://github.com/eviltrout/ember-performance for performance profiling. Might grab a copy of that and report some before/after numbers of the various benchmarks.

@mixonic
Copy link
Member

mixonic commented Jan 8, 2015

@cowboyd ember-performance is pretty easy to use, take a look. There are micro and macro benchmarks and you will obviously want the former. You should try timing a variety of types.

@cowboyd
Copy link
Contributor Author

cowboyd commented Jan 8, 2015

Thanks for the tips. I ran the suite twice: both against the current canary build and with this change applied. These four runs were done on Chrome only (is there a policy on which browsers we want to do performance checks on?) And here are the results:

Note that the link-to/active benchmark was not run due to eviltrout/ember-performance#34

Before

1.

Ember Version: 1.11.0-beta.1+canary.dca9c74d
User Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36

.--------------------------------------------------------------------------.
|                    Ember Performance Suite - Results                     |
|--------------------------------------------------------------------------|
|              Name               |    Speed    | Error | Samples |  Mean  |
|---------------------------------|-------------|-------|---------|--------|
| Baseline: Render List           |      391.53 |  2.75 |     148 |   2.55 |
| Baseline: Handlebars List       |         448 |     3 |     280 |   2.23 |
| Ember.get                       |   827073.29 |  0.81 |      98 |      0 |
| Ember.set                       |   389376.14 |  0.58 |      97 |      0 |
| Ember.set (primed)              |   394123.87 |   0.6 |      97 |      0 |
| Ember.get (primed)              |   762802.53 |  0.43 |      97 |      0 |
| Ember.run                       |   172532.27 |  2.68 |      80 |      0 |
| Ember.LinkView.create           |    86228.07 |  2.95 |      82 |      0 |
| Ember.View.create               |   233957.09 |  3.16 |      77 |      0 |
| Baseline: Object initialization | 91863773.58 |  0.62 |      96 |      0 |
| Ember.Object.create             |   257312.67 |  2.34 |      82 |      0 |
| Render List                     |       35.52 | 19.44 |     304 |  28.15 |
| Render List (Unbound)           |       48.76 |  7.73 |     362 |  20.51 |
| Render Complex List             |        9.85 | 77.01 |     107 | 101.48 |
| Render Simple Ember List        |       50.44 |  8.91 |     361 |  19.83 |
| Render List with link-to        |       12.25 | 49.17 |     128 |  81.61 |
| Render link-to                  |      442.04 |  4.91 |     797 |   2.26 |
'--------------------------------------------------------------------------'

2.

Ember Version: 1.11.0-beta.1+canary.dca9c74d
User Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36

.---------------------------------------------------------------------------.
|                     Ember Performance Suite - Results                     |
|---------------------------------------------------------------------------|
|              Name               |    Speed    | Error  | Samples |  Mean  |
|---------------------------------|-------------|--------|---------|--------|
| Baseline: Render List           |       413.7 |   2.04 |     151 |   2.42 |
| Baseline: Handlebars List       |      437.17 |      3 |     327 |   2.29 |
| Ember.get                       |   765494.94 |   0.52 |      99 |      0 |
| Ember.set                       |   359709.31 |   0.46 |      97 |      0 |
| Ember.set (primed)              |   362395.64 |   1.65 |      94 |      0 |
| Ember.get (primed)              |   792156.38 |   0.59 |      95 |      0 |
| Ember.run                       |   204401.38 |   0.81 |      96 |      0 |
| Ember.LinkView.create           |    99323.08 |   2.66 |      87 |      0 |
| Ember.View.create               |   308701.46 |   1.56 |      95 |      0 |
| Baseline: Object initialization | 73709980.91 |   0.76 |      95 |      0 |
| Ember.Object.create             |   285125.05 |   0.61 |      99 |      0 |
| Render List                     |       29.09 |  15.82 |     263 |  34.38 |
| Render List (Unbound)           |       47.69 |  12.32 |     354 |  20.97 |
| Render Complex List             |        9.37 | 150.48 |     101 | 106.75 |
| Render Simple Ember List        |       50.08 |   7.72 |     359 |  19.97 |
| Render List with link-to        |       12.11 |  57.24 |     127 |   82.6 |
| Render link-to                  |      547.27 |   3.68 |     822 |   1.83 |
'---------------------------------------------------------------------------'

After

1.

Ember Version: 1.11.0-beta.1+canary.0b604582
User Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36

.--------------------------------------------------------------------------.
|                    Ember Performance Suite - Results                     |
|--------------------------------------------------------------------------|
|              Name               |    Speed    | Error | Samples |  Mean  |
|---------------------------------|-------------|-------|---------|--------|
| Baseline: Render List           |      383.81 |  1.99 |     147 |   2.61 |
| Baseline: Handlebars List       |      459.77 |  2.99 |     320 |   2.17 |
| Ember.get                       |   845638.47 |  0.59 |      99 |      0 |
| Ember.set                       |   365506.67 |   0.4 |      99 |      0 |
| Ember.set (primed)              |   380634.02 |   0.7 |      97 |      0 |
| Ember.get (primed)              |   677842.88 |  0.64 |      93 |      0 |
| Ember.run                       |   168107.01 |   3.9 |      80 |      0 |
| Ember.LinkView.create           |    82917.93 |  3.36 |      84 |      0 |
| Ember.View.create               |   239800.88 |  3.08 |      79 |      0 |
| Baseline: Object initialization | 88821730.65 |  0.55 |      96 |      0 |
| Ember.Object.create             |   260273.98 |  1.97 |      87 |      0 |
| Render List                     |       28.48 | 14.68 |     259 |  35.11 |
| Render List (Unbound)           |       51.66 | 11.76 |     372 |  19.36 |
| Render Complex List             |        9.65 | 96.72 |     106 | 103.67 |
| Render Simple Ember List        |       50.49 |  8.09 |     361 |  19.81 |
| Render List with link-to        |       12.18 | 66.08 |     127 |  82.07 |
| Render link-to                  |      379.04 |  5.07 |     774 |   2.64 |
'--------------------------------------------------------------------------'

2.

Ember Version: 1.11.0-beta.1+canary.0b604582
User Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36

.--------------------------------------------------------------------------.
|                    Ember Performance Suite - Results                     |
|--------------------------------------------------------------------------|
|              Name               |    Speed    | Error | Samples |  Mean  |
|---------------------------------|-------------|-------|---------|--------|
| Baseline: Render List           |      383.81 |  1.99 |     147 |   2.61 |
| Baseline: Handlebars List       |      448.95 |     3 |     343 |   2.23 |
| Ember.get                       |   732997.02 |  0.76 |      93 |      0 |
| Ember.set                       |   363546.51 |  0.78 |      96 |      0 |
| Ember.set (primed)              |   376007.15 |  0.83 |     100 |      0 |
| Ember.get (primed)              |   655090.95 |  0.74 |      95 |      0 |
| Ember.run                       |   169382.84 |  3.58 |      77 |      0 |
| Ember.LinkView.create           |     85324.2 |  2.62 |      83 |      0 |
| Ember.View.create               |   237745.17 |  3.06 |      77 |      0 |
| Baseline: Object initialization | 90792003.64 |   0.4 |      96 |      0 |
| Ember.Object.create             |   240624.48 |  2.28 |      88 |      0 |
| Render List                     |       36.05 | 11.58 |     306 |  27.74 |
| Render List (Unbound)           |       49.79 | 10.98 |     360 |  20.08 |
| Render Complex List             |        9.48 | 99.14 |     103 | 105.53 |
| Render Simple Ember List        |       50.13 |  7.11 |     358 |  19.95 |
| Render List with link-to        |       11.73 | 55.45 |     123 |  85.23 |
| Render link-to                  |      551.47 |  4.05 |     825 |   1.81 |
'--------------------------------------------------------------------------'

I don't see anything that screams disaster here, or even indicates a real degradation, but we should definitely explore more. What do you think would be a sufficient sample set?

<conjecture>

In practice, this change might actually save on net computation since it is more restrictive about when change notifications are dispatched, and it is the notifications, not the change itself that is expensive.

</conjecture>

@ahacking
Copy link

ahacking commented Jan 8, 2015

I've composed a summary table of the object accessors and initialization rows to make comparison more obvious.

There is still quite a delta across the board with those benchmarks, in some cases its an improvement and others not, so I'm not sure how reliable the figures are in actuality. The baseline object initialization has a large delta. What is the difference in benchmark sets 1 and 2?

Name 1 BEFORE 1 AFTER DELTA CHANGE % 2 BEFORE 2 AFTER DELTA CHANGE %
Ember.get 827073.29 845638.47 18565.18 2.24% 765494.94 732997.02 -32497.92 -4.245%
Ember.set 389376.14 365506.67 -23869.47 -6.13% 359709.31 363546.51 3837.2 1.067%
Ember.set (primed) 394123.87 380634.02 -13489.85 -3.42% 362395.64 376007.15 13611.51 3.756%
Ember.get (primed) 762802.53 677842.88 -84959.65 -11.14% 792156.38 655090.95 -137065.43 -17.303%
Baseline: Object initialization 91863773.58 88821730.65 -3042042.93 -3.31% 73709980.91 90792003.64 17082022.73 23.175%
Ember.Object.create 257312.67 260273.98 2961.31 1.15% 285125.05 240624.48 -44500.57 -15.607%

@cowboyd
Copy link
Contributor Author

cowboyd commented Jan 9, 2015

@ahacking That is much easier to read, thanks.

To answer your question, there is no difference between 1 and 2, they are both running canary before and canary + isEqual patch after, so four times, one right after another, before and after, before and after.

As you can see, there are some significant deltas between the runs on unpatched Canary without any changes, so I don't think we can conclude anything other than there aren't any gross degradations that we wouldn't expected to see as normal variation upon subsequent runs of the benchmarks against the unchanged codebase. However, I don't think that serves as proof enough. The question is, what would?

@ahacking
Copy link

I think the test needs be be repeatable so that variances between runs are reduced as much as possible. This may mean restarting the browser from a clean slate each time. Then you have OS variances due to memory, swap, I/O and whatever random activity on the local LAN happens to be consuming IO interrupts and stealing away CPU to worry about as well. Maybe the OS and other variances can be discounted if some raw JS benchmarks are run and it results in low skew between runs. Kind acts as a test run self check.

That's why when I saw the baseline having so much variance I couldn't really treat the results as meaningful.

There is a good writeup on benchmark testing on JS by the jsPerf author.

Maybe Ember needs to adopt the use of benchmark.js ?? I think it would be really useful to have solid perf tests so performance gains and regressions can be easily and reliably determined.

@mmun
Copy link
Member

mmun commented Jan 10, 2015

@ahacking We are extremely interested in contributors wanting to work on a benchmark suite. benchmark.js is great for microbenchmarks.

@cowboyd
Copy link
Contributor Author

cowboyd commented Jan 12, 2015

I agree @ahacking. There are two issues I see, the first is that there is too much variance in the mean between runs agains the same codebase (the two before and after runs in my dataset were taken about twenty minutes apart). It appears that, as you pointed out, there is a lot of context that can change significantly with time.

The second, is that there is no way to compare benchmarks between different ember versions without running two suites and then eyeballing, and even then its hard to be sure about the results since its hard to determine in what way the computational context has changed in between runs.

Assuming that the computational context changes very little over very short periods of time, one approach might be to interleave the sampling from the two different Ember versions, so that the same number of samples are subjected to the same temporal effects. In other words, for each benchmark, take twenty samples against version A, twenty against version B, another twenty against version A, another twenty against version B. If the initial assumption holds, and there is no performance regression, then we would expect the same mean (or one verifiable with a p-value analysis).

I don't know how feasible this is, and Statistics aren't my long suit, but having an automated process to compare to versions that would reliably spot performance differences would be nice to have.

@ahacking
Copy link

@cowboyd, @mmun

I've been thinking if it would be possible to load each ember/benchmark in separate iframes and then test each version of the framework with benchmark.js and using oasis.js for messaging each iframe instance. That would allow benchmark.js to use its approach for removing variance and still keep each ember version isolated.

@stefanpenner
Copy link
Member

this actually seems like a pretty hefty change, we should think about it more closely.

@rwjblue
Copy link
Member

rwjblue commented Aug 2, 2015

@stefanpenner - agreed. In theory we could make isEqual return immediately from a === check (moving

up), but even with that tweak this is still concerning to me....

@Serabe
Copy link
Member

Serabe commented Apr 16, 2017

@stefanpenner @rwjblue should we close this?

@stefanpenner
Copy link
Member

@Serabe I believe so.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants