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

Secrets of the DOM Virtual Machine

tomdale
December 01, 2017

Secrets of the DOM Virtual Machine

Most web applications today build their UIs by running JavaScript. But what happens if we instead treat our templates as a specialized functional programming language that can be compiled into optimized bytecode and executed with a virtual machine? In this talk, we'll explore this novel architecture and some of the performance benefits it gives us.

tomdale

December 01, 2017
Tweet

More Decks by tomdale

Other Decks in Programming

Transcript

  1. View Slide

  2. SLOW FAST

    View Slide

  3. D
    O
    W
    N
    LO
    A
    D
    RENDER
    PARSE

    View Slide

  4. Lightweight
    ๏ Downloads fast.
    ๏ Parses fast.
    ๏ Renders fast.
    Responsive
    ๏ Tap or click → updates
    fast.
    ๏ Sophisticated animations.
    ๏ 60fps scrolling.

    View Slide

  5. Lightweight
    ๏ Downloads fast.
    ๏ Parses fast.
    ๏ Renders fast.
    Responsive
    ๏ Tap or click → updates
    fast.
    ๏ Sophisticated animations.
    ๏ 60fps scrolling.
    TENSION

    View Slide

  6. Virtual DOM

    View Slide

  7. VIRTUAL DOM

    View Slide

  8. Virtual DOM
    ๏ Compiles JSX to JavaScript
    ๏ Renders recursively, synchronously
    ๏ Pushes optimization responsibility to application
    code

    View Slide

  9. View Slide

  10. View Slide

  11. 1
    3
    2
    Instant Templates
    60fps Incremental Rendering
    Optimized Updates

    View Slide

  12. 1 Instant Templates

    View Slide

  13. https://medium.com/@samccone/performance-futures-bundling-281543d9a0d5
    DOWNLOAD PARSE COMPILE EXECUTE
    130MS 200MS

    View Slide

  14. https://medium.com/reloading/javascript-start-up-performance-69200f43b201

    View Slide

  15. function Weather(weather) {
    return [
    Current Weather,

    New York, NY
    ,


    78°


    ];
    }
    function Weather(weather) {
    return [React.createElement(
    "h2",
    null,
    "Current Weather"
    ), React.createElement(
    "address",
    null,
    "New York, NY"
    ), React.createElement(
    "div",
    null,
    React.createElement(
    "strong",
    null,
    "78\xB0"
    )
    )];
    }

    View Slide

  16. https://medium.com/reloading/javascript-start-up-performance-69200f43b201

    View Slide

  17. Current Weather

    New York, NY



    {{weather.temperature}}°


    View Slide

  18. COMPILER

    View Slide

  19. COMPILER
    Current Weather

    New York, NY



    {{weather.temperature}}°



    {{@title}}

    View Slide

  20. COMPILER

    View Slide

  21. COMPILER

    View Slide

  22. COMPILER
    ?

    View Slide

  23. COMPILER
    101010
    010101
    101010

    View Slide

  24. BYTECODE

    View Slide

  25. 0x19 0x00 0x1f 0x16 0x01 0x20
    0x16 0x02 0x19 0x03 0x1f 0x16
    0x04 0x20 0x16 0x02 0x19 0x05
    0x1f 0x16 0x06 0x19 0x07 0x1f
    0x16 0x08 0x30 0x15 0x48 0x04
    0x00 0x05 0x09 0x05 0x0a 0x0d
    0x03 0x00 0x39 0x32 0x02 0x2f
    0x36 0x3c 0x00 0x13 0x04 0x0d
    0x03 0x01 0x0f 0x04 0x30 0x0b
    0x13 0x28 0x0b 0x13 0x0b 0x13
    0x28 0x0b 0x13 0x0b 0x13 0x28
    0x0b 0x13 0x3d 0x00 0x07 0x3f
    0x3f 0x04 0x47 0x25 0x40 0x00
    0x04 0x41 0x04 0x4a 0x04 0x45
    0x04 0x46 0x4a 0x04 0x31 0x13
    0x26 0x48 0x0f 0x04 0x33 0x14
    0x18 0x00 0x33 0x14 0x31 0x16
    0x0b 0x20 0x16 0x0c 0x20 0x14
    Current Weather

    New York, NY



    {{weather.temperature}}°


    View Slide

  26. 0x28 PushBlockScope
    0x0b Primitive
    primitive: 0x13 19
    0x0b Primitive
    primitive: 0x13 19
    0x28 PushBlockScope
    0x0b Primitive
    primitive: 0x13 19
    0x0b Primitive
    primitive: 0x13 19
    0x28 PushBlockScope
    0x0b Primitive
    primitive: 0x13 19
    0x3d PushArgs
    names: 0x00 0
    positionals: 0x07 7
    synthetic: 0x3f 319
    0x3f PrepareArgs
    state: 0x04 4
    0x47 BeginComponentTransaction
    0x25 PushDynamicScope
    0x40 CreateComponent
    flags: 0x00 0
    state: 0x04 4
    0x41 RegisterComponentDestructor
    state: 0x04 4
    0x43 GetComponentSelf
    state: 0x04 4
    0x45 GetComponentLayout
    state: 0x04 4
    0x46 InvokeComponentLayout
    0x4a DidRenderLayout
    state: 0x04 4
    0x31 PopFrame
    0x13 PopScope
    0x26 PopDynamicScope
    0x48 CommitComponentTransaction
    0x0f Load
    register: 0x04 4
    0x33 Exit
    0x14 Return
    0x18 DynamicContent
    trusting: 0x00 0
    0x33 Exit
    0x14 Return
    0x31 PopFrame
    0x16 Text
    text: 0x0b "°\n "
    0x20 CloseElement
    0x16 Text
    text: 0x0c "\n"
    0x20 CloseElement
    0x14 Return
    0x19 OpenElement
    tag: 0x00 "h2"
    0x1f FlushElement
    0x16 Text
    text: 0x01 "Current Weather"
    0x20 CloseElement
    0x16 Text
    text: 0x02 "\n\n"
    0x19 OpenElement
    tag: 0x03 "address"
    0x1f FlushElement
    0x16 Text
    text: 0x04 "\n New York, NY\n"
    0x20 CloseElement
    0x16 Text
    text: 0x02 "\n\n"
    0x19 OpenElement
    tag: 0x05 "div"
    0x1f FlushElement
    0x16 Text
    text: 0x06 "\n "
    0x19 OpenElement
    tag: 0x07 "strong"
    0x1f FlushElement
    0x16 Text
    text: 0x08 "\n "
    0x30 PushFrame
    0x15 ReturnTo
    offset: 0x48 72
    0x04 GetVariable
    symbol: 0x00 0
    0x05 GetProperty
    key: 0x09 "weather"
    0x05 GetProperty
    key: 0x0a "temperature"
    0x0d Dup
    register: 0x03 3
    offset: 0x00 0
    0x39 IsComponent
    0x32 Enter
    args: 0x02 2
    0x2f JumpUnless
    to: 0x36 54
    0x3c PushDynamicComponentManager
    meta: 0x00 0
    0x10 Fetch
    register: 0x04 4
    0x0d Dup
    register: 0x03 3
    offset: 0x01 1
    0x0f Load
    register: 0x04 4
    0x30 PushFrame
    0x0b Primitive
    primitive: 0x13 19

    View Slide

  27. 0x28 PushBlockScope
    0x0b Primitive
    primitive: 0x13 19
    0x0b Primitive
    primitive: 0x13 19
    0x28 PushBlockScope
    0x0b Primitive
    primitive: 0x13 19
    0x0b Primitive
    primitive: 0x13 19
    0x28 PushBlockScope
    0x0b Primitive
    primitive: 0x13 19
    0x3d PushArgs
    names: 0x00 0
    positionals: 0x07 7
    synthetic: 0x3f 319
    0x3f PrepareArgs
    state: 0x04 4
    0x47 BeginComponentTransaction
    0x25 PushDynamicScope
    0x40 CreateComponent
    flags: 0x00 0
    state: 0x04 4
    0x41 RegisterComponentDestructor
    state: 0x04 4
    0x43 GetComponentSelf
    state: 0x04 4
    0x45 GetComponentLayout
    state: 0x04 4
    0x46 InvokeComponentLayout
    0x4a DidRenderLayout
    state: 0x04 4
    0x31 PopFrame
    0x13 PopScope
    0x26 PopDynamicScope
    0x48 CommitComponentTransaction
    0x0f Load
    register: 0x04 4
    0x33 Exit
    0x14 Return
    0x18 DynamicContent
    trusting: 0x00 0
    0x33 Exit
    0x14 Return
    0x31 PopFrame
    0x16 Text
    text: 0x0b "°\n "
    0x20 CloseElement
    0x16 Text
    text: 0x0c "\n"
    0x20 CloseElement
    0x14 Return
    0x19 OpenElement
    tag: 0x00 "h2"
    0x1f FlushElement
    0x16 Text
    text: 0x01 "Current Weather"
    0x20 CloseElement
    0x16 Text
    text: 0x02 "\n\n"
    0x19 OpenElement
    tag: 0x03 "address"
    0x1f FlushElement
    0x16 Text
    text: 0x04 "\n New York, NY\n"
    0x20 CloseElement
    0x16 Text
    text: 0x02 "\n\n"
    0x19 OpenElement
    tag: 0x05 "div"
    0x1f FlushElement
    0x16 Text
    text: 0x06 "\n "
    0x19 OpenElement

    View Slide

  28. COMPILER VM
    101010
    010101
    101010
    BUILD TIME BROWSER

    View Slide

  29. COMPILER VM
    101010
    010101
    101010
    BUILD TIME BROWSER

    View Slide

  30. Current Weather

    New York, NY



    78°


    {strings:["h2","Current
    Weather","\n\n","address","\n New York,
    NY\n","div","\n ","strong","\n 78°\n
    ","\n"]}
    19 01 00 00 1F 00 16 01 01 00 20 00 16 01
    02 00 19 01 03 00 1F 00 16 01 04 00 20 00
    16 01 02 00 19 01 05 00 1F 00 16 01 06 00
    19 01 07 00 1F 00 16 01 08 00 20 00 16 01
    09 00 20 00 14 00

    View Slide

  31. https://medium.com/@samccone/performance-futures-bundling-281543d9a0d5
    DOWNLOAD PARSE COMPILE EXECUTE
    130MS 200MS

    View Slide

  32. View Slide

  33. DOWNLOAD EXECUTE
    130MS
    PARSE COMPILE
    200MS

    View Slide

  34. DOWNLOAD EXECUTE
    130MS

    View Slide

  35. DOWNLOAD EXECUTE
    130MS

    View Slide

  36. export class Heap {
    private heap: Uint16Array | Array;
    private table: number[];
    private offset = 0;
    private handle = 0;
    constructor(serializedHeap?: { buffer: ArrayBuffer, table: number[], handle: number }) {
    if (serializedHeap) {
    let { buffer, table, handle } = serializedHeap;
    this.heap = new Uint16Array(buffer);
    this.table = table;
    this.offset = this.heap.length;
    this.handle = handle;
    } else {
    this.heap = new Uint16Array(0x100000);
    this.table = [];
    }
    }
    push(item: number): void {
    this.heap[this.offset++] = item;
    }
    getbyaddr(address: number): number {
    return this.heap[address];
    Glimmer VM uses
    ArrayBuffer directly, no
    parse or intermediate data
    structure.

    View Slide

  37. Demo

    View Slide

  38. View Slide

  39. View Slide

  40. ๏ Tiny runtime
    ๏ Compact bytecode
    ๏ No parse is faster than no parse
    1 Instant Templates

    View Slide

  41. 1
    3
    2
    Instant Templates
    60fps Incremental Rendering
    Optimized Updates

    View Slide

  42. 2 Optimized Updates

    View Slide

  43. Lightweight
    ๏ Downloads fast.
    ๏ Parses fast.
    ๏ Renders fast.
    Responsive
    ๏ Tap or click → updates
    fast.
    ๏ Sophisticated animations.
    ๏ 60fps scrolling.

    View Slide

  44. View Slide

  45. “What makes a React app slow is, most of the
    time, useless rerenders of many components. You
    may have read that the React VirtualDom is super fast.
    That's true, but in a medium size app, a full redraw can
    easily render hundreds of components. Even the fastest
    VirtualDom templating engine can't make that in less
    than 16ms.”

    View Slide

  46. “The React documentation is very clear about the way
    to avoid useless rerenders: shouldComponentUpdate(). …
    it's your job as a developer to check that the props
    of a component didn't change and skip rendering
    altogether in that case.”

    View Slide

  47. GLIMMER VM

    View Slide

  48. APPENDING VM UPDATING VM

    View Slide


  49. {{firstName}}

    View Slide


  50. {{firstName}}

    STATIC
    DYNAMIC

    View Slide

  51. 0x0019 OpenElement
    tag: 0x0000 "div"
    0x001c StaticAttr
    name: 0x0001 "class"
    value: 0x0002 "active"
    namespace: 0x0000 0
    0x001f FlushElement
    0x0004 GetVariable
    symbol: 0x0000 0
    0x0005 GetProperty
    key: 0x0003 "firstName"
    0x0018 DynamicContent
    trusting: 0x0000 0
    0x0020 CloseElement
    0x0014 Return

    {{firstName}}

    View Slide

  52. StaticAttr
    OpenElement
    GetVariable
    GetProperty
    DynamicContent
    CloseElement
    APPEND PROGRAM
    DOM

    View Slide

  53. StaticAttr
    OpenElement
    GetVariable
    GetProperty
    DynamicContent
    CloseElement
    APPEND PROGRAM
    DOM

    View Slide

  54. StaticAttr
    OpenElement
    GetVariable
    GetProperty
    DynamicContent
    CloseElement
    APPEND PROGRAM
    DOM

    View Slide

  55. StaticAttr
    OpenElement
    GetVariable
    GetProperty
    DynamicContent
    CloseElement
    APPEND PROGRAM
    DOM

    View Slide

  56. StaticAttr
    OpenElement
    GetVariable
    GetProperty
    DynamicContent
    CloseElement
    APPEND PROGRAM

    DOM

    View Slide

  57. StaticAttr
    OpenElement
    GetVariable
    GetProperty
    DynamicContent
    CloseElement
    APPEND PROGRAM

    DOM

    View Slide

  58. StaticAttr
    OpenElement
    GetVariable
    GetProperty
    DynamicContent
    CloseElement
    APPEND PROGRAM

    DOM

    View Slide

  59. StaticAttr
    OpenElement
    GetVariable
    GetProperty
    DynamicContent
    CloseElement
    APPEND PROGRAM

    DOM

    View Slide

  60. StaticAttr
    OpenElement
    GetVariable
    GetProperty
    DynamicContent
    CloseElement
    APPEND PROGRAM

    Sean
    DOM

    View Slide

  61. StaticAttr
    OpenElement
    GetVariable
    GetProperty
    DynamicContent
    CloseElement
    APPEND PROGRAM UPDATE PROGRAM

    Sean
    DOM

    View Slide

  62. StaticAttr
    OpenElement
    GetVariable
    GetProperty
    DynamicContent
    CloseElement
    APPEND PROGRAM UPDATE PROGRAM
    UpdateDynamicContent

    Sean
    DOM

    View Slide

  63. StaticAttr
    OpenElement
    GetVariable
    GetProperty
    DynamicContent
    CloseElement
    APPEND PROGRAM UPDATE PROGRAM
    UpdateDynamicContent

    Sean
    DOM

    View Slide

  64. StaticAttr
    OpenElement
    GetVariable
    GetProperty
    DynamicContent
    CloseElement
    APPEND PROGRAM UPDATE PROGRAM
    UpdateDynamicContent

    Sean

    DOM

    View Slide

  65. StaticAttr
    OpenElement
    GetVariable
    GetProperty
    DynamicContent
    CloseElement
    APPEND PROGRAM UPDATE PROGRAM
    UpdateDynamicContent

    Sean

    DOM

    View Slide

  66. StaticAttr
    OpenElement
    GetVariable
    GetProperty
    DynamicContent
    CloseElement
    APPEND PROGRAM UPDATE PROGRAM
    UpdateDynamicContent

    Sean

    DOM

    View Slide

  67. StaticAttr
    OpenElement
    GetVariable
    GetProperty
    DynamicContent
    CloseElement
    APPEND PROGRAM UPDATE PROGRAM
    UpdateDynamicContent


    DOM
    Brendan

    View Slide

  68. StaticAttr
    OpenElement
    GetVariable
    GetProperty
    DynamicContent
    CloseElement
    APPEND PROGRAM UPDATE PROGRAM
    UpdateDynamicContent

    View Slide

  69. StaticAttr
    OpenElement
    GetVariable
    GetProperty
    DynamicContent
    CloseElement
    APPEND PROGRAM UPDATE PROGRAM
    UpdateDynamicContent

    LESS WORK

    View Slide

  70. Demo

    View Slide

  71. import RudyRufer from './RudyRufer';
    import TheSunHasGotHisHatOn from './
    TheSunHasGotHisHatOn';
    import Roenno from './Roenno';
    import LisaSchulte from './LisaSchulte';
    import ChasinWildTrains from './ChasinWildTrains';
    import TommyWinship from './TommyWinship';
    import Parepepeotes from './Parepepeotes';
    export default class extends React.Component {
    componentDidMount() {
    let counter = 0;
    setInterval(() => {
    this.setState({ counter: ++counter });
    }, 0);
    }
    render() {
    let { state } = this;
    let { counter } = state || {};
    return
    { counter }








    import RudyRufer from './RudyRufer';
    import TheSunHasGotHisHatOn from './
    TheSunHasGotHisHatOn';
    import Roenno from './Roenno';
    import LisaSchulte from './LisaSchulte';
    import ChasinWildTrains from './ChasinWildTrains';
    import TommyWinship from './TommyWinship';
    import Parepepeotes from './Parepepeotes';
    export default class extends Component {
    @tracked counter = 0;
    didInsertElement() {
    setInterval(() => {
    this.counter++;
    }, 0);
    }
    static template = `
    {{counter}}












    REACT GLIMMER

    View Slide

  72. import RudyRufer from './RudyRufer';
    import TheSunHasGotHisHatOn from './
    TheSunHasGotHisHatOn';
    import Roenno from './Roenno';
    import LisaSchulte from './LisaSchulte';
    import ChasinWildTrains from './ChasinWildTrains';
    import TommyWinship from './TommyWinship';
    import Parepepeotes from './Parepepeotes';
    export default class extends React.Component {
    componentDidMount() {
    let counter = 0;
    setInterval(() => {
    this.setState({ counter: ++counter });
    }, 0);
    }
    render() {
    let { state } = this;
    let { counter } = state || {};
    return
    { counter }








    import RudyRufer from './RudyRufer';
    import TheSunHasGotHisHatOn from './
    TheSunHasGotHisHatOn';
    import Roenno from './Roenno';
    import LisaSchulte from './LisaSchulte';
    import ChasinWildTrains from './ChasinWildTrains';
    import TommyWinship from './TommyWinship';
    import Parepepeotes from './Parepepeotes';
    export default class extends Component {
    @tracked counter = 0;
    didInsertElement() {
    setInterval(() => {
    this.counter++;
    }, 0);
    }
    static template = `
    {{counter}}












    REACT GLIMMER

    View Slide

  73. import RudyRufer from './RudyRufer';
    import TheSunHasGotHisHatOn from './
    TheSunHasGotHisHatOn';
    import Roenno from './Roenno';
    import LisaSchulte from './LisaSchulte';
    import ChasinWildTrains from './ChasinWildTrains';
    import TommyWinship from './TommyWinship';
    import Parepepeotes from './Parepepeotes';
    export default class extends React.Component {
    componentDidMount() {
    let counter = 0;
    setInterval(() => {
    this.setState({ counter: ++counter });
    }, 0);
    }
    render() {
    let { state } = this;
    let { counter } = state || {};
    return
    { counter }








    import RudyRufer from './RudyRufer';
    import TheSunHasGotHisHatOn from './
    TheSunHasGotHisHatOn';
    import Roenno from './Roenno';
    import LisaSchulte from './LisaSchulte';
    import ChasinWildTrains from './ChasinWildTrains';
    import TommyWinship from './TommyWinship';
    import Parepepeotes from './Parepepeotes';
    export default class extends Component {
    @tracked counter = 0;
    didInsertElement() {
    setInterval(() => {
    this.counter++;
    }, 0);
    }
    static template = `
    {{counter}}












    REACT GLIMMER

    View Slide

  74. import RudyRufer from './RudyRufer';
    import TheSunHasGotHisHatOn from './
    TheSunHasGotHisHatOn';
    import Roenno from './Roenno';
    import LisaSchulte from './LisaSchulte';
    import ChasinWildTrains from './ChasinWildTrains';
    import TommyWinship from './TommyWinship';
    import Parepepeotes from './Parepepeotes';
    export default class extends React.Component {
    componentDidMount() {
    let counter = 0;
    setInterval(() => {
    this.setState({ counter: ++counter });
    }, 0);
    }
    render() {
    let { state } = this;
    let { counter } = state || {};
    return
    { counter }








    import RudyRufer from './RudyRufer';
    import TheSunHasGotHisHatOn from './
    TheSunHasGotHisHatOn';
    import Roenno from './Roenno';
    import LisaSchulte from './LisaSchulte';
    import ChasinWildTrains from './ChasinWildTrains';
    import TommyWinship from './TommyWinship';
    import Parepepeotes from './Parepepeotes';
    export default class extends Component {
    @tracked counter = 0;
    didInsertElement() {
    setInterval(() => {
    this.counter++;
    }, 0);
    }
    static template = `
    {{counter}}












    REACT GLIMMER

    View Slide

  75. View Slide

  76. View Slide

  77. 2 Optimized Updates
    • With Virtual DOM, sluggish update performance can
    sneak up on you
    • Generating optimized bytecode during initial render
    keeps updates fast

    View Slide

  78. 1
    3
    2
    Instant Templates
    60fps Incremental Rendering
    Optimized Updates

    View Slide

  79. 3 60fps Incremental Rendering

    View Slide

  80. e Frame Frame Frame Frame F

    View Slide

  81. s 16ms 16ms 16ms 16ms 1

    View Slide

  82. s 16ms 16ms 16ms 16ms 1
    1000ms ÷ 60 = 16.6
    ̅ms

    View Slide

  83. s 70ms 1

    View Slide

  84. s 70ms 1

    View Slide

  85. 1000ms+

    View Slide

  86. 1000ms+
    WHITE
    RECTANGLE
    OF DOOM

    View Slide

  87. OpenElement
    FlushElement
    Text
    CloseElement
    GetVariable
    GetProperty
    DynamicContent
    SYNCHRONOUS

    View Slide

  88. OpenElement
    FlushElement
    requestIdleCallback
    16ms
    Text
    CloseElement
    GetVariable
    requestIdleCallback
    16ms
    GetProperty
    DynamicContent
    requestIdleCallback
    16ms

    View Slide

  89. OpenElement
    FlushElement
    requestIdleCallback
    16ms
    Text
    CloseElement
    GetVariable
    requestIdleCallback
    16ms
    GetProperty
    DynamicContent
    requestIdleCallback
    16ms

    View Slide

  90. Demo

    View Slide

  91. View Slide

  92. View Slide

  93. render(iterator: TemplateIterator): void {
    // Iterate the template iterator, executing the compiled template program
    // until there are no more instructions left to execute.
    let result;
    do {
    result = iterator.next();
    } while (!result.done);
    this.result = result.value;
    }

    View Slide

  94. render(iterator: TemplateIterator): Promise {
    return new Promise((resolve) => {
    let timeout = this.timeout;
    let tick = (deadline: Deadline) => {
    let iteratorResult: IteratorResult;
    do {
    iteratorResult = iterator.next();
    } while (!iteratorResult.done && deadline.timeRemaining() > 1);
    if (iteratorResult.done) {
    this.result = iteratorResult.value;
    return resolve();
    }
    requestIdleCallback(tick, { timeout });
    };
    requestIdleCallback(tick, { timeout });
    });
    }

    View Slide

  95. View Slide

  96. View Slide

  97. • Initial render can take multiple seconds on low-end phones
    • Raw optimization has reached diminishing returns—we
    need to break up the work.
    • VM architecture make this almost too easy.
    • Works with rehydration, too!
    2 60fps Incremental Rendering

    View Slide

  98. 1
    3
    2
    Instant Templates
    60fps Incremental Rendering
    Optimized Updates

    View Slide

  99. Future
    ๏ Implement VM in Web Assembly
    ๏ Execute programs in Web Worker

    View Slide

  100. https://try.glimmerjs.com

    View Slide

  101. https://try.glimmerjs.com

    View Slide

  102. View Slide

  103. Thank you!
    @tomdale

    View Slide