Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Purely Fast

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

Minko Gechev

November 07, 2017
Tweet

More Decks by Minko Gechev

Other Decks in Programming

Transcript

  1. @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); } }
  2. @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); } }
  3. @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. const fibonacci = n => { if (n === 1

    || n === 2) return 1; return fibonacci(n - 1) + fibonacci(n - 2); };
  5. 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…
  6. 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
  7. 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)
  8. 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)
  9. 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)
  10. 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)
  11. twitter.com/mgechev Immutable.js helps: • We get a new reference on

    change • We do not copy the entire data structure
  12. 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
  13. const fibonacci = n => { if (n === 1

    || n === 2) return 1; return fibonacci(n - 1) + fibonacci(n - 2); };
  14. 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
  15. 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).
  16. @Pipe({ name: 'calculate', pure: true }) export class CalculatePipe {

    transform(num: number) { return fibonacci(num); } }
  17. 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); });
  18. 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); });
  19. Pure Pipes 27 | calculate 27 | calculate 27 |

    calculate fib(27) fib(27) 196418 196418
  20. Pure Pipes 27 | calculate 27 | calculate 27 |

    calculate fib(27) fib(27) fib(27) 196418 196418 196418
  21. 27 | calculate 27 | calculate 27 | calculate fib(27)

    196418 cache fib(27) Memoization 27 27
  22. 27 | calculate 27 | calculate 27 | calculate fib(27)

    196418 cache fib(27) 196418 Memoization 27 27
  23. 27 | calculate 27 | calculate 27 | calculate fib(27)

    196418 cache fib(27) 196418 fib(27) Memoization 27 27 27
  24. 27 | calculate 27 | calculate 27 | calculate fib(27)

    196418 cache fib(27) 196418 fib(27) 196418 Memoization 27 27 27
  25. twitter.com/mgechev Pattern… for their last input • On push performs

    “memoization” • Pure pipes are “memoized”
  26. @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
  27. @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
  28. @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
  29. 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); } }
  30. 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); } }
  31. 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); } }
  32. 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); } }
  33. 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); } }
  34. 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; } } }
  35. 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; } } }
  36. twitter.com/mgechev Lessons learned • No silver bullet • Understand your

    component structure • Understand your data • Application specific benchmarks