Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

JS Fest 2019. Max Koretskiy. A sneak peek into super optimized code in JS frameworks

33 views

Published on

Very few developers have the need to write super optimized JavaScript code. In application development, we tend to favor readability over optimization. But that’s not the case with frameworks. Developers who use frameworks expect them to run as fast as possible. In fact, speed is often a defining characteristic when choosing a framework. There are techniques that make code run faster. You’ve probably heard about linked lists, monomorphism, and bitmasks, right? Maybe you've even used some. Well, you can find all these and a bunch of other interesting approaches in the sources of most popular JS frameworks.
Over the past year, I’ve seen a lot while reverse-engineering Angular and React. In this talk, I want to share my findings with you. Some of you may end up applying them at work. And others, who knows, may even end up writing the next big framework.

Published in: Education
  • Be the first to comment

  • Be the first to like this

JS Fest 2019. Max Koretskiy. A sneak peek into super optimized code in JS frameworks

  1. 1. Why Angular and React are so fast? Max Koretskyi aka Wizard Developer Advocate
  2. 2. 2015 20162014 2017 2018 A failed startup that made me learn to program and gave broad programming experience SO activity that build my knowledge base and created a foundation for blogging Angular In Depth that helped me build strong community and establish reputation as an Angular expert Public speaking that helped me meet awesome people and become GDE and MVP Developer Advocate job that allows me to to develop programming and marketing skills at the same time where 100-hour work weeks got me in 5 years
  3. 3.  maxkoretskyi.com/connecting-the-dots Find out more at
  4. 4. Monomorphism Bit fields & Bit masks Bloom filters Benedikt Meurer, V8 optimizations lead engineer Alex Rickabught, Angular core team Dan Ambramov, React core team Kudos to Optimization techniques in Angular and React
  5. 5. We use one type for all View nodes so that property access in loops stay monomorphic! Misko Hevery, technical lead of Angular Angular sources comments
  6. 6. Fiber node … shares the same hidden class. Never add fields outside of construction in `ReactFiber` Contributing To React Fiber guidelines Sebastian Markbåge, technical lead of React
  7. 7. • What is hidden class and why is it shared by fiber and view nodes? Questions we need to answer • What is monomophic property access and why is it important? • What is a fiber node in React & a view node in Angular?
  8. 8. Representing a template in Angular @Component({ template: ` <h1>The title is: {{title}}</h1> <h2>My hero is: {{hero}}</h2> ` }) export class AppComponent { title = 'Tour of Heroes'; hero = 'Windstorm'; } Type: h1 Type: h2 View Nodes bindings: { text: "Tour of Heroes" } bindings: { text: " 'Windstorm" }
  9. 9. Representing a template in React class App extends Component { state = { title: 'Tour of Heroes', hero: 'Windstorm' }; render() { return ( [ <h1>The title is: {this.state.title}</h1>, <h2>My hero is: {this.state.hero}</h2> ] ) } } Type: h1 Type: h2 props: { children: "Tour of Heroes" } props: { children: " Windstorm " } Fiber Nodes
  10. 10. Fiber and View nodes are used a lot when processing changes properties could easily be accessed over 10 000* times function updateNode(node, …) { let value = node.property; } *100 components x 10 elements x 10 function calls
  11. 11. Locating value of an object's property in memory is a complicated process
  12. 12. let object = { x: 5, y: 6 }; JSObject 5 6 Property information Offset: 0 [[Writable]]: true [[Enumerable]]: true [[Configurable]]: true Shape 'x' 'y' Property information Offset: 1 [[Writable]]: true [[Enumerable]]: true [[Configurable]]: true Shapes aka Hidden Class (Maps in V8)
  13. 13. let object = { x: 5, y: 6 }; JSObject 5 6 Property information Offset: 0 ... Shape 'x' 'y' Property information Offset: 1 ... Location in memory with offsets 0 0 0 0 0 1 1 00 0 0 0 0 1 0 1 property `x` address offset 0 bytes 5 property `y` address offset 1 byte 6 object pointer
  14. 14. Why do we need shapes?
  15. 15. 5 6 JSObject a Property information Shape 'x' 'y' Property information JSObject b 7 8 let a = { x: 5, y: 6 }; let b = { x: 7, y: 8 }; Shapes help reduce memory footprint
  16. 16. let a = { x: 5, y: 6 } let b = { x: 7, y: 8 } b.w = 9; b.z = 10; JSObject a 5 6 Shape 'x' 'y' JSObject b 7 8 Shape 'w' Shape 'z' 9 10 Transition chains
  17. 17. Inline Caching (IC) to the rescue
  18. 18. getX({x: a}) function getX(o) { return o.x; } Optimization through Inline Cache shape offsetstate function 'getX' feedback vector xmono Property information Offset: 0 ... Shape(x) 'x' 0
  19. 19. Monomorphic property access getX(a); getX(b); getX(c); function getX(o) { return o.x; } let a = { x: 5, y: 6 }; let b = { x: 7, y: 8 }; let b = { x: 7, y: 8 }; a function only saw one type of a shape
  20. 20. Polymorphic property access getX(a); getX(b); getX(c); getX(D); function getX(o) { return o.x; } let a = {x: 5, y: 6}; let b = {y: 7, x: 8}; // different order let b = {y: 7, x: 8, z: 5}; // new properties let d = Object.create({}, {}; // different prototype a function only saw up to 4 different types of a shape
  21. 21. Megamorphic property access Monomorphic prop access maybe up to 100 times faster than megamorphic. a function saw more than 4 different types of a shape
  22. 22. Frameworks enforce the same shape (hidden class) for fiber and view nodes to enable monomorphic property access
  23. 23. Template node types Fiber node (React) Template element View node (Angular) HostComponent HTMLElementNode TypeElement HostText HTMLTextNode TypeText FunctionComponent, ClassComponent Component Component
  24. 24. type Element { fieldA; fieldB; } type Text { fieldC; field; } type Component { fieldE; fieldF; } Property access is not monormophic type Node { tag: nodeType; fieldA | null; fieldB | null; fieldC | null; fieldD | null; fieldE | null; fieldF | null; } Property access is monormophic
  25. 25. type Fiber { tag: integer, ... } interface NodeDef { flags: bitfield; ... } FunctionComponent = 0; ClassComponent = 1; IndeterminateComponent = 2; HostRoot = 3; HostPortal = 4; HostComponent = 5; HostText = 6; Fragment = 7; … None = 0, TypeElement = 1 << 0, TypeText = 1 << 1, ProjectedTemplate = 1 << 2, ... TypeDirective = 1 << 14, Component = 1 << 15, Type definitions in Angular and React
  26. 26. function beginWork(fiberNode, ...) { ... switch (fiberNode.tag) { case FunctionalComponent: {...} case ClassComponent: {...} case HostComponent: return updateHostComponent(fiberNode, ...); case ... } } Branching by node type in React
  27. 27. Bit fields (vector) & Bit masks
  28. 28. Bit field is just an array of bits let bitField = 0b01010001; // 81 0 1 0 1 0 0 0 1
  29. 29. React uses bit fields to encode effects
  30. 30. Effects in React Fiber architecture • Render phase (build a list of effects) • Process changes from setState • Update props on child elements • Call lifecycle methods (shouldComponentUpdate etc.) The result of the phase is a tree of fiber nodes marked with side-effects. • Commit phase (apply effects) • Update DOM • Call lifecycle methods (componentDidUpdate etc.) • Update refs
  31. 31. Before render phase: After render phase: 4..toString(2) = 100 type: 'span' effectTag: 0 type: 'span' effectTag: 4 const effectTags = { Placement = : 0b000010; Update : 0b000100; PlacementAndUpdate : 0b000110; ... } 0 0 0 1 0 0 PlacementUpdate
  32. 32. let effectTag = 0b00000000; 0 0 0 0 0 0 0 0 effectTag = effectTag | effectTags.Update; 0 0 0 0 0 1 0 0 Write with bitwise OR Define bitfield isUpdateEffectSet = !! (effectTag & effectTags.Update); Check with bitwise AND
  33. 33. Branching by effect in React function updateHostEffects(fiberNode) { ... const effectTag = fiberNode.effectTag; if (effectTag & effectTags.Placement) { ... } if (effectTag & effectTags.PlacementAndUpdate) { ... } if (effectTag & effectTags.Update) { ... } ... }
  34. 34. Encoding objects in bit fields let user = { first: true, group: 20, sortKey: 347843 }; allocations in memory for: • a standard JS object • a shape with keys metadata • 3 values stored in the keys
  35. 35. Any way to avoid those allocations?
  36. 36. Encoding objects in bit fields Field Restrictions on values Bits required sortKey less than 1M 20 (220 > 1M) group less than 50 6 (26 = 64) first boolean 1 00000 0 000000 0000000000000000000 sortKeygroupfirst 32 bits
  37. 37. Encoding objects in bit fields let user = 0x 05454ec3; let user = 0b 00000 1 010100 001010100111011000011; let user = { first: true, group: 20, sortKey: 347843 }; Field Decimal Binary sortKey 347843 001010100111011000011 group 20 010100 first true 1
  38. 38. Why bother?
  39. 39. • millions of allocations for objects and values 1 million of objects require VS • one typed array for one million 32-integer values Regular JS object Encoded in a bitfield • a ton of GC (garbage collection) cleanup after each iteration • much larger and potentially fragmented memory usage • almost zero garbage collection • smaller and contiguous memory usage
  40. 40. Bloom filters is element in the set? DEFINITELY NO 100% probability MAYBE varying probability data structure that answers the question
  41. 41. 0 0 0 0 1 0 Is element in a set? Is bits [n1,n2…n] set? Each element is encoded in a bit field let value = "Jonh"; calculateBitNumber(value); // 2
  42. 42. John (2) 0 0 0 0 0 0 0 0 Anna (1)Tom (4) [ "John", "Anna", "Tom" ] let calculateBitValue = (value) => value.charCodeAt(0) % 8 111
  43. 43. Why "yes" is not guaranteed?
  44. 44. Anna (1) 0 0 0 0 1 0 1 1 collisions Tom (4) John (2) Jane (2) Less slots, more collisions
  45. 45. How to increase probability of "Yes"?
  46. 46. Anna (1,6) 0 1 1 0 1 0 1 1 no collisions Tom (4,7) John (2,7) Jane (2,1) Less slots, more collisions
  47. 47. Where does Angular use this? Dependency Injection mechanism
  48. 48. ServiceA ServiceB ServiceC constructor( ServiceA ) {} Injector ServiceD
  49. 49. Hierarchical injectors Dashboard component Widget component SiteAnalytics component WidgetManager Injector Injector WidgetManager Injector Is here? no – go up Is here? no – go up Is here? yes – resolve
  50. 50. Bloom filters Injector Dashboard component Widget component SiteAnalytics component WidgetManager WidgetManager Injector Is here? no – go up Is here? no – go up resolve Injector Bloom filter Bloom filter Bloom filter Is here? maybe – chec injector
  51. 51. How does Angular implement bloom filters?
  52. 52. Bloom filter is 256 bits in length 8 buckets 32 bits 32 bits 32 bits 32 bits 32 bits 32 bits 32 bits 32 bits
  53. 53. A plain counter modulo of 256 to generate hash let counter = 0; function calculateBitValue() { let hash = counter % 256; counter = counter + 1; return hash; } let bitValue1 = calculateBitValue(); // 0 let bitValue2 = calculateBitValue(); // 1 ... let bitValue256 = calculateBitValue(); // 0 let bitValue257 = calculateBitValue(); // 1
  54. 54. No collisions with less than 256 services per Injector "yes" answer is 100%
  55. 55. Ask me anything
  56. 56. Depth 15th of June, 2019 first Angular conference in Kyiv angular-in-depth.org Angular in
  57. 57. maxkoretskyi.com/reverse-engineering maxkoretskyi.com/connecting-the-dots
  58. 58. Follow me to learn fundamentals maxkoretskyi maxkoretskyi maxkoretskyi.com

×