Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

chore(benchpress): add a ngClass benchmark #15243

Closed
wants to merge 1 commit into from

Conversation

drpicox
Copy link
Contributor

@drpicox drpicox commented Oct 10, 2016

What kind of change does this PR introduce? (Bug fix, feature, docs update, ...): benchmark
Adds a benchmark to measure the impact of changes in ngClass.

What is the current behavior? (You can also link to an open issue here): #15228 (comment)

What is the new behavior (if this is a feature change)? no change

Does this PR introduce a breaking change? no

Please check if the PR fulfills these requirements

Other information:
It adds a benchmark in benchmarks/ng-class-bp
The benchmark was designed to measure the changes introduced by: #15228

It has the following steps:

  • setup: cleanups preview, and create structures, not worth to measure
  • create: creation of a table/list with the selected number of items and applied classes
  • $apply: an apply without changes (compute digest time)
  • update: cost of updating classes (changes ng-classes values)
  • unclass: cost of removing all classes (falses all ngClass values)
  • class: cost of apply classes (applies all classes given already constructed elements)
  • destroy: cost of removing everything from dom (usually not worth to measure)

There are three versions to paint:

  • optimized table: a table with optimized ngClass values ng-class="'success' && todo.completed"
  • unoptimized table: a table with object ngClass syntaxis: ng-class="{success: todo.completed}"
  • list: list of todos with an object ngClass and all cases: ng-class="{completed: todo.completed, important: todo.important, urgent: todo.urgent}"
  • single optimized: it computes a single ngClass using a complex object but optimizing the access ng-class="{success: !!$ctrl.todos, danger: !$ctrl.todos}"
  • single: it computes a single ngClass using a complex object ng-class="{success: $ctrl.todos, danger: !$ctrl.todos}"

I ran them over the previous version of ngClass and the version of #15228 , the results are:

I also modified #15228 to use compile instead of link (commented in #15228 (comment) ). Results are:

  • create:
    • it improves the performance around 5%~10% (faster compile time)
  • other cases:
    • no appreciable changes

So, as a conclusion, it seems that the #15228 optimizes automatically code in the same fashion that an experienced angular programmers would do manually.
In the other hand, given that 1 frame is around 16ms, we should give 8ms to the browser to update, it leaves 8ms for the application to work. Experiments show that digest loop times moves from values like 8 or 10ms to just 1ms in large lists, or 0.01ms in a single big object condition.

@drpicox drpicox changed the title chore(benchpress): add a ngBClass benchmark chore(benchpress): add a ngClass benchmark Oct 10, 2016
drpicox referenced this pull request in gkalpak/angular.js Oct 10, 2016
The cases that should benefit most are:

1. When using large objects as values (e.g.: `{loaded: $ctrl.data}`).
2. When using objects/arrays and there are frequent changes.
3. When there are many `$index` changes (e.g. addition/deletion/reordering in large `ngRepeat`s).

The differences in operations per digest include:

1. `Regular expression (when not changed)`
   **Before:** `equals()`
   **After:**  `toClassString()`

2. `Regular expression (when changed)`
   **Before:** `copy()` + 2 x `arrayClasses()` + `shallowCopy()`
   **After:**  2 x `split()`

3. `One-time expression (when not changed)`
   **Before:** `equals()`
   **After:**  `toFlatValue()` + `equals()`*

4. `One-time expression (when changed)`
   **Before:** `copy()` + 2 x `arrayClasses()` + `shallowCopy()`
   **After:**  `copy()`* + `toClassString()`* + 2 x `split()`

5. `$index modulo changed`
   **Before:** `arrayClasses()`
   **After:**  -

(*): on flatter structure

In large based on angular#14404. Kudos to @drpicox for the initial idea and a big part
of the implementation.

Closes angular#14404
@drpicox drpicox force-pushed the bp-ngClass branch 2 times, most recently from 3969f4a to d095a0c Compare October 10, 2016 17:47
Copy link
Member

@gkalpak gkalpak left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! It is obviously not exhaustive (e.g. no array or one-time bindings or alternating $indexes), but it covers most common usecases (and is of course much better than what we (didn't) have before) 👍

So, if I understand correctly, #15228 is indeed a major improvements in most cases (but a slight de-opt in some). I think we can live with that, given the benefits.

💯 on putting this together and doing the comparisons!

Implementation:<br>
<label>
<input type="radio" ng-model="benchmark.implementation" value="tableOptimized">
Table optimized <code>ng-class="'success' && todo.completed"</code>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems strange. What is it supposed to do?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You probably meant todo.completed && 'success' 😃

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, indeed, I was too quick commiting 👼

</label><br>
<label>
<input type="radio" ng-model="benchmark.implementation" value="list">
Table <code>ng-class="{completed: todo.completed, urgent: todo.urgent, important: todo.important"}</code>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't Table be List?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes.

this.importantPeriodicity = 13;
this.urgentPeriodicity = 29;
this.todos = buildTodos(100);
configureTodos(this.todos, 0, this);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these two lines necessary (since we have the setup step)?

Copy link
Contributor Author

@drpicox drpicox Oct 10, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They are helpful.
These lines helped me to make sure that the benchmark does what it means to do, and they are also helpful because now it shows a small preview of the selection that you have done: list, table, single value.
And I do not rebuild the list of 100 todos at the end of the tests to induce a refresh: the list/table is empty and it is restored doing refresh (which is helpful to clear memory).

@petebacondarwin petebacondarwin added this to the 1.6.x (post 1.6.0) milestone Oct 10, 2016
@drpicox
Copy link
Contributor Author

drpicox commented Oct 10, 2016

I like the result, I usually write guides or check the code in critical zones making sure that my colleagues are using optimized ngClass values. With #15228 seems that it will work well from time 0.

I have rewritted the code a little bit, and fixed the "spelling" problems.

It is not exhaustive because my resources are limited. I have just added few cases that I know that are significative. I expect that with time and other PRs more cases will be added.

One question:

  • is there any existing tool to execute benchpress from command line? (like karma does with tests)

It would be nice to have benchmarks running automatically and follow the chart of performance changes through time.

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

Successfully merging this pull request may close these issues.

4 participants