angular-cn/packages/core/test/render3/perf
Misko Hevery 76698d38f7 refactor(ivy): Add style reconciliation algorithm (#34004)
This change introduces class/style reconciliation algorithm for DOM elements.
NOTE: The code is not yet hooked up, it will be used by future style algorithm.

Background:
Styling algorithm currently has [two paths](https://hackmd.io/@5zDGNGArSxiHhgvxRGrg-g/rycZk3N5S)
when computing how the style should be rendered.
1. A direct path which concatenates styling and uses `elemnent.className`/`element.style.cssText` and
2. A merge path which uses internal data structures and uses `element.classList.add/remove`/`element.style[property]`.

The situation is confusing and hard to follow/maintain. So a future PR will remove the merge-path and do everything with
direct-path. This however breaks when some other code adds class or style to the element without Angular's knowledge.
If this happens instead of switching from direct-path to merge-path algorithm, this change provides a different mental model
whereby we always do `direct-path` but the code which writes to the DOM detects the situation and reconciles the out of bound write.

The reconciliation process is as follows:
1. Detect that no one has modified `className`/`cssText` and if so just write directly (fast path).
2. If out of bounds write did occur, switch from writing using `className`/`cssText` to `element.classList.add/remove`/`element.style[property]`.
   This does require that the write function computes the difference between the previous Angular expected state and current Angular state.
   (This requires a parser. The advantage of having a parser is that we can support `style="width: {{exp}}px" kind of bindings.`)
   Compute the diff and apply it in non destructive way using `element.classList.add/remove`/`element.style[property]`

Properties of approach:
- If no out of bounds style modification:
  - Very fast code path: Just concatenate string in right order and write them to DOM.
  - Class list order is preserved
- If out of bounds style modification detected:
  - Penalty for parsing
  - Switch to non destructive modification: `element.classList.add/remove`/`element.style[property]`
  - Switch to alphabetical way of setting classes.

PR Close #34004
2020-01-24 12:21:39 -08:00
..
class_binding test(ivy): `class_binding` benchmark fixes (#34242) 2019-12-11 09:50:23 -08:00
directive_inputs perf(ivy): add micro-benchmark focused on directive input update (#33798) 2019-11-19 11:55:59 -08:00
directive_instantiate perf(ivy): fix creation time micro-benchmarks (#34031) 2019-11-25 12:47:58 -05:00
duplicate_map_based_style_and_class_bindings test(ivy): introduce a benchmark for duplicate map-based style/class bindings (#33608) 2019-11-07 20:34:26 +00:00
duplicate_style_and_class_bindings test(ivy): introduce a benchmark for duplicate style/class bindings (#33600) 2019-11-07 17:50:33 +00:00
element_text_create perf(ivy): fix creation time micro-benchmarks (#34031) 2019-11-25 12:47:58 -05:00
interpolation perf(ivy): convert all node-based benchmark to use a testing harness (#32699) 2019-09-16 09:31:15 -07:00
listeners perf(ivy): fix creation time micro-benchmarks (#34031) 2019-11-25 12:47:58 -05:00
map_based_style_and_class_bindings refactor(ivy): move `styling/instructions.ts` to `instructions/styling.ts` (#32731) 2019-09-18 15:06:39 -07:00
ng_template perf(ivy): fix creation time micro-benchmarks (#34031) 2019-11-25 12:47:58 -05:00
noop_change_detection refactor(ivy): Intruduce LFrame to store global instruction information (#33178) 2019-10-24 14:42:15 -07:00
property_binding perf(ivy): convert all node-based benchmark to use a testing harness (#32699) 2019-09-16 09:31:15 -07:00
property_binding_update refactor(ivy): improve micro-benchmark profiling (#32647) 2019-09-12 13:06:38 -07:00
style_and_class_bindings perf(ivy): move attributes array into component def (#32798) 2019-10-09 13:16:55 -07:00
style_binding refactor(ivy): move `styling/instructions.ts` to `instructions/styling.ts` (#32731) 2019-09-18 15:06:39 -07:00
BUILD.bazel refactor(ivy): Add style reconciliation algorithm (#34004) 2020-01-24 12:21:39 -08:00
README.md docs: update run instructions (#34090) 2019-11-27 10:39:17 -08:00
micro_bench.ts refactor(ivy): Add style reconciliation algorithm (#34004) 2020-01-24 12:21:39 -08:00
noop_renderer.ts test(ivy): support `className` in micro benchmarks (#33392) 2019-10-25 09:17:52 -07:00
noop_renderer_spec.ts test(ivy): support `className` in micro benchmarks (#33392) 2019-10-25 09:17:52 -07:00
profile_all.js build: migrate references and scripts that set to build with ivy via compile=aot to use config=ivy (#33983) 2019-11-26 16:38:40 -05:00
profile_in_browser.html build: migrate references and scripts that set to build with ivy via compile=aot to use config=ivy (#33983) 2019-11-26 16:38:40 -05:00
setup.ts perf(ivy): fix creation time micro-benchmarks (#34031) 2019-11-25 12:47:58 -05:00
shared.ts test(ivy): introduce a benchmark for duplicate style/class bindings (#33600) 2019-11-07 17:50:33 +00:00
split_class_list.ts refactor(ivy): Add style reconciliation algorithm (#34004) 2020-01-24 12:21:39 -08:00

README.md

Build

yarn bazel build //packages/core/test/render3/perf:${BENCHMARK}_lib.min_debug.es2015.js --config=ivy

Run

node dist/bin/packages/core/test/render3/perf/${BENCHMARK}_lib.min_debug.es2015.js

Profile

node --no-turbo-inlining --inspect-brk dist/bin/packages/core/test/render3/perf/${BENCHMARK}_lib.min_debug.es2015.js

then connect with a debugger (the --inspect-brk option will make sure that benchmark execution doesn't start until a debugger is connected and the code execution is manually resumed).

The actual benchmark code has calls that will start (console.profile) and stop (console.profileEnd) a profiling session.

Deoptigate

yarn add deoptigate
yarn deoptigate dist/bin/packages/core/test/render3/perf/${BENCHMARK}_lib.min_debug.es2015.js

Run All

To run all of the benchmarks use the profile_all.js script:

node packages/core/test/render3/perf/profile_all.js

NOTE: This command will build all of the tests, so there is no need to do so manually.

Optionally use the --write command to save the run result to a file for later comparison.

node packages/core/test/render3/perf/profile_all.js --write baseline.json

Comparing Runs

If you have saved the baseline (as described in the step above) you can use it to get change in performance like so:

node packages/core/test/render3/perf/profile_all.js --read baseline.json

The resulting output should look something like this:

┌────────────────────────────────────┬─────────┬──────┬───────────┬───────────┬───────┐
│              (index)               │  time   │ unit │ base_time │ base_unit │   %   │
├────────────────────────────────────┼─────────┼──────┼───────────┼───────────┼───────┤
│       directive_instantiate        │ 276.652 │ 'ms' │  286.292  │   'ms'    │ -3.37 │
│        element_text_create         │ 262.868 │ 'ms' │  260.031  │   'ms'    │ 1.09  │
│           interpolation            │ 257.733 │ 'us' │  260.489  │   'us'    │ -1.06 │
│             listeners              │  1.997  │ 'us' │   1.985   │   'us'    │  0.6  │
│ map_based_style_and_class_bindings │  10.07  │ 'ms' │   9.786   │   'ms'    │  2.9  │
│       noop_change_detection        │ 93.256  │ 'us' │  91.745   │   'us'    │ 1.65  │
│          property_binding          │ 290.777 │ 'us' │  280.586  │   'us'    │ 3.63  │
│      property_binding_update       │ 588.545 │ 'us' │  583.334  │   'us'    │ 0.89  │
│      style_and_class_bindings      │  1.061  │ 'ms' │   1.047   │   'ms'    │ 1.34  │
│           style_binding            │ 543.841 │ 'us' │  545.385  │   'us'    │ -0.28 │
└────────────────────────────────────┴─────────┴──────┴───────────┴───────────┴───────┘

Notes

To run the benchmark use bazel run <benchmark_target>, example:

  • yarn bazel run --config=ivy //packages/core/test/render3/perf:noop_change_detection

To profile, append _profile to the target name and attach a debugger via chrome://inspect, example:

  • yarn bazel run --config=ivy //packages/core/test/render3/perf:noop_change_detection_profile

To interactively edit/rerun benchmarks use ibazel instead of bazel.