Purely Fast

82bafb0432ce4ccc9dcc26f94d5fe5bc?s=47 Minko Gechev
November 07, 2017

Purely Fast

One thing is sure - performance matters! Blocking the main thread of an application, causing frame drops, is one of the most efficient ways to get rid of a significant portion of our users. Fortunately, the day could be saved, thanks to functional programming. Coming with, at first, abstract ideas, functional programming brings the concepts of immutability and purity, which combined together can dramatically improve the rendering performance of our application.

In this talk, we'll apply concepts from functional programming in the face of pure components, pure pipes and persistent data structures. We'll demonstrate in details how we can use them to build high-performant Angular applications.

Links:
https://github.com/mgechev/purely-fast
https://github.com/mgechev/purely-fast-benchmarks

82bafb0432ce4ccc9dcc26f94d5fe5bc?s=128

Minko Gechev

November 07, 2017
Tweet

Transcript

  1. 2.
  2. 6.
  3. 14.

    @Component(...) export class EmployeeListComponent { @Input() data: EmployeeData[]; @Output() remove

    = new EventEmitter<EmployeeData>(); @Output() add = new EventEmitter<string>(); handleKey(event: any) { ... } calculate(num: number) { return fibonacci(num); } }
  4. 15.

    @Component(...) export class EmployeeListComponent { @Input() data: EmployeeData[]; @Output() remove

    = new EventEmitter<EmployeeData>(); @Output() add = new EventEmitter<string>(); handleKey(event: any) { ... } calculate(num: number) { return fibonacci(num); } }
  5. 17.

    @Component(...) export class EmployeeListComponent { @Input() data: EmployeeData[]; @Output() remove

    = new EventEmitter<EmployeeData>(); @Output() add = new EventEmitter<string>(); handleKey(event: any) { ... } calculate(num: number) { return fibonacci(num); } }
  6. 18.

    const fibonacci = n => { if (n === 1

    || n === 2) return 1; return fibonacci(n - 1) + fibonacci(n - 2); };
  7. 22.
  8. 24.
  9. 27.
  10. 30.

    twitter.com/mgechev With OnPush change detection will be triggered when the

    framework, with reference check, determines that any of the inputs of a component has changed…
  11. 31.

    twitter.com/mgechev Lets think of EmployeeListComponent as a function, where: What

    does this mean? • Inputs are function’s arguments • Rendered component is function’s result
  12. 32.

    const f = EmployeeListComponent; const data = [e1]; // Will

    trigger CD f({ data: data }); data.push(e2); // Will not trigger CD f({ data: data }); // Will trigger CD f({ data: data.slice() }); Pseudo code (not Angular)
  13. 33.

    const f = EmployeeListComponent; const data = [e1]; // Will

    trigger CD f({ data: data }); data.push(e2); // Will not trigger CD f({ data: data }); // Will trigger CD f({ data: data.slice() }); Pseudo code (not Angular)
  14. 34.

    const f = EmployeeListComponent; const data = [e1]; // Will

    trigger CD f({ data: data }); data.push(e2); // Will not trigger CD f({ data: data }); // Will trigger CD f({ data: data.slice() }); Pseudo code (not Angular)
  15. 35.

    const f = EmployeeListComponent; const data = [e1]; // Will

    trigger CD f({ data: data }); data.push(e2); // Will not trigger CD f({ data: data }); // Will trigger CD f({ data: data.slice() }); Pseudo code (not Angular)
  16. 39.
  17. 40.

    twitter.com/mgechev Immutable.js helps: • We get a new reference on

    change • We do not copy the entire data structure
  18. 42.
  19. 45.
  20. 46.

    twitter.com/mgechev With OnPush change detection will be triggered when the

    framework, with reference check, determines that any of the inputs of a component has changed…or when an event in the component is triggered
  21. 51.
  22. 56.

    const fibonacci = n => { if (n === 1

    || n === 2) return 1; return fibonacci(n - 1) + fibonacci(n - 2); };
  23. 57.

    const fibonacci = n => { if (n === 1

    || n === 2) return 1; return fibonacci(n - 1) + fibonacci(n - 2); }; // Two properties // - No side effects // - Same result for same arguments
  24. 60.

    twitter.com/mgechev Angular executes a pure pipe only when it detects

    a change to the input value. A pure change is either a change to a primitive input value (String, Number, Boolean, Symbol) or a changed object reference (Date, Array, Function, Object).
  25. 61.

    @Pipe({ name: 'calculate', pure: true }) export class CalculatePipe {

    transform(num: number) { return fibonacci(num); } }
  26. 64.
  27. 67.
  28. 68.
  29. 70.
  30. 78.

    const memoize = require('lodash.memoize'); const fibonacci = memoize((num: number): number

    => { if (num === 1 || num === 2) return 1; return fibonacci(num - 1) + fibonacci(num - 2); });
  31. 79.

    const memoize = require('lodash.memoize'); const fibonacci = memoize((num: number): number

    => { if (num === 1 || num === 2) return 1; return fibonacci(num - 1) + fibonacci(num - 2); });
  32. 80.
  33. 81.
  34. 84.
  35. 85.

    Pure Pipes 27 | calculate 27 | calculate 27 |

    calculate fib(27) fib(27) 196418 196418
  36. 86.

    Pure Pipes 27 | calculate 27 | calculate 27 |

    calculate fib(27) fib(27) fib(27) 196418 196418 196418
  37. 90.

    27 | calculate 27 | calculate 27 | calculate fib(27)

    196418 cache fib(27) Memoization 27 27
  38. 91.

    27 | calculate 27 | calculate 27 | calculate fib(27)

    196418 cache fib(27) 196418 Memoization 27 27
  39. 92.

    27 | calculate 27 | calculate 27 | calculate fib(27)

    196418 cache fib(27) 196418 fib(27) Memoization 27 27 27
  40. 93.

    27 | calculate 27 | calculate 27 | calculate fib(27)

    196418 cache fib(27) 196418 fib(27) 196418 Memoization 27 27 27
  41. 95.

    twitter.com/mgechev Pattern… for their last input • On push performs

    “memoization” • Pure pipes are “memoized”
  42. 97.

    @Directive({selector: '[ngFor][ngForOf]'}) export class NgForOf<T> implements DoCheck, OnChanges { ...

    constructor(private _differs: IterableDiffers) {} ngDoCheck(): void { const changes = this._differ.diff(this.ngForOf); if (changes) this._applyChanges(changes); } ... } How NgForOf works
  43. 98.

    @Directive({selector: '[ngFor][ngForOf]'}) export class NgForOf<T> implements DoCheck, OnChanges { ...

    constructor(private _differs: IterableDiffers) {} ngDoCheck(): void { const changes = this._differ.diff(this.ngForOf); if (changes) this._applyChanges(changes); } ... } How NgForOf works
  44. 99.

    @Directive({selector: '[ngFor][ngForOf]'}) export class NgForOf<T> implements DoCheck, OnChanges { ...

    constructor(private _differs: IterableDiffers) {} ngDoCheck(): void { const changes = this._differ.diff(this.ngForOf); if (changes) this._applyChanges(changes); } ... } How NgForOf works
  45. 102.

    export class DifferableList<T> { changes = new LinkedList<IterableChangeRecord<T>>(); constructor(private data

    = List<T>([])) {} unshift(data: T) { const result = new DifferableList<T>(this.data.unshift(data)); result.changes.add({ ... }); return result; } ... [Symbol.iterator]() { return new DifferableListIterator<T>(this); } }
  46. 103.

    export class DifferableList<T> { changes = new LinkedList<IterableChangeRecord<T>>(); constructor(private data

    = List<T>([])) {} unshift(data: T) { const result = new DifferableList<T>(this.data.unshift(data)); result.changes.add({ ... }); return result; } ... [Symbol.iterator]() { return new DifferableListIterator<T>(this); } }
  47. 104.

    export class DifferableList<T> { changes = new LinkedList<IterableChangeRecord<T>>(); constructor(private data

    = List<T>([])) {} unshift(data: T) { const result = new DifferableList<T>(this.data.unshift(data)); result.changes.add({ ... }); return result; } ... [Symbol.iterator]() { return new DifferableListIterator<T>(this); } }
  48. 105.

    export class DifferableList<T> { changes = new LinkedList<IterableChangeRecord<T>>(); constructor(private data

    = List<T>([])) {} unshift(data: T) { const result = new DifferableList<T>(this.data.unshift(data)); result.changes.add({ ... }); return result; } ... [Symbol.iterator]() { return new DifferableListIterator<T>(this); } }
  49. 106.

    export class DifferableList<T> { changes = new LinkedList<IterableChangeRecord<T>>(); constructor(private data

    = List<T>([])) {} unshift(data: T) { const result = new DifferableList<T>(this.data.unshift(data)); result.changes.add({ ... }); return result; } ... [Symbol.iterator]() { return new DifferableListIterator<T>(this); } }
  50. 108.

    export class DifferableListDiffer<V> implements IterableDiffer<V>, IterableChanges<V> { ... diff(collection: NgIterable<V>):

    DifferableListDiffer<V> | null { const changes = this._data.changes; this._changes = changes; if (changes.size() > 0) { this._data.changes = new LinkedList<IterableChangeRecord<V>>(); return this; } else { return null; } } }
  51. 109.

    export class DifferableListDiffer<V> implements IterableDiffer<V>, IterableChanges<V> { ... diff(collection: NgIterable<V>):

    DifferableListDiffer<V> | null { const changes = this._data.changes; this._changes = changes; if (changes.size() > 0) { this._data.changes = new LinkedList<IterableChangeRecord<V>>(); return this; } else { return null; } } }
  52. 112.

    twitter.com/mgechev Lessons learned • No silver bullet • Understand your

    component structure • Understand your data • Application specific benchmarks