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

Javascript & HTML5 optimisation

Marcin Kulik
November 29, 2012

Javascript & HTML5 optimisation

My Future of Web Apps Prague 2012 conf presentation.

"A journey through Javascript and HTML(5) optimizations by example of ascii.io web based terminal session player. When we talk about animations on HTML pages we usually mean one-shot CSS transitions or jQuery based DOM manipulations that move elements around. There are cases however, where the page is not the subject but only the medium for a highly dynamic content element which is the real subject. HTML5 games or painting apps are good examples. If you join me I'll show you actual, real-life usage of modern browser technologies like Canvas, requestAnimationFrame or WebWorkers that made ascii.io player fast and responsive. You'll learn techniques that will make highly dynamic elements on your own sites smooth and sleek."

Marcin Kulik

November 29, 2012
Tweet

More Decks by Marcin Kulik

Other Decks in Programming

Transcript

  1. I share at github & twitter as I share at

    github & twitter as sickill sickill
  2. You may know me from: You may know me from:

    ascii.io, racksh, git-dude, vim-pasta, bitpocket, coloration, ascii.io, racksh, git-dude, vim-pasta, bitpocket, coloration, stderred... stderred... https://github.com/sickill https://github.com/sickill
  3. Write some code Write some code $ rails generate resource

    Logo $ vim -O app/models/logo.rb spec/models/logo_spec.rb
  4. Tests still running, I'll just look at facebook.com for a

    sec... Tests still running, I'll just look at facebook.com for a sec...
  5. Push changes to a repository Push changes to a repository

    $ git add . $ git commit -m "I must have been drunk." $ git push
  6. # end of .bashrc if [ -z "$RECORDING" ]; then

    export RECORDING=1 ASCII_IO_API_URL=http://localhost:3000 exec asciiio -y fi Thx @roy & @_solnic_ ! Thx @roy & @_solnic_ !
  7. Player loop Player loop function scheduleFrame() { var frame =

    getNextFrame(n++); var data, delay = frame[0], frame[1]; setTimeout(function() { processFrame(data) }, delay); } function processFrame(data) { var changes = interpret(data); render(changes); scheduleFrame(); } scheduleFrame();
  8. What do terminal apps do to a What do terminal

    apps do to a terminal? terminal? insert, cut and overwrite text change fg/bg color for next printing operation jump the cursor to any line/column and do above things
  9. Buffer data model Buffer data model totalLines = 4 totalColumns

    = 10 lines = [line0, line1, line2, line3] lineX = [cell0, cell1, ... cell9] cellX = [character, brush]
  10. Example buffer content Example buffer content lines = [ [

    ["f", b1], ["o", b2], ["w", b1], ["a", b1] ], # first line [ .... ] # second line ] cell = lines[0][1] cell[0] # => "o" cell[1] # => b2
  11. "Changes" object "Changes" object { 0: [ ["f", b1], ["o",

    b1], ["w", b1], ["a", b2] ], 4: [ ["X", b3], ["X", b3], ["X", b3], ["X", b3] ] }
  12. Rendering Rendering function render(changes) { for (var n in changes)

    { renderLine(n, changes[n]); } } function renderLine(n, line) { ... }
  13. Resulting DOM structure Resulting DOM structure <span class="line"> <span class="fg1">f</span>

    <span class="fg1">o</span> <span class="fg1">w</span> <span class="fg2">a</span> </span> ... <span class="line"> <span class="bg3">X</span> <span class="bg3">X</span> <span class="bg3">X</span> <span class="bg3">X</span> </span>
  14. Resulting DOM structure Resulting DOM structure <span class="line"> <span class="fg1">fow</span>

    <span class="fg2">a</span> </span> ... <span class="line"> <span class="bg3">XXXX</span> </span>
  15. Optimisation result Optimisation result On average 80% less span elements

    for each line On average 80% less span elements for each line ~3.5x faster line rendering ~3.5x faster line rendering
  16. Getting line element (100,000 times) Getting line element (100,000 times)

    line = this.$('.line:eq(' + n + ')')[0] // sloppy dev version // 16.0 sec
  17. Getting line element (100,000 times) Getting line element (100,000 times)

    line = this.$('.line:eq(' + n + ')')[0] // sloppy dev version // 16.0 sec line = this.$('.line').eq(n)[0] // 3.5 sec
  18. Getting line element (100,000 times) Getting line element (100,000 times)

    line = this.$('.line:eq(' + n + ')')[0] // sloppy dev version // 16.0 sec line = this.$('.line').eq(n)[0] // 3.5 sec line = this.$('.line')[n] // 2.4 sec
  19. Getting line element (100,000 times) Getting line element (100,000 times)

    line = this.$('.line:eq(' + n + ')')[0] // sloppy dev version // 16.0 sec line = this.$('.line').eq(n)[0] // 3.5 sec line = this.$('.line')[n] // 2.4 sec this.lines = this.$('.line') // set earlier line = this.lines[n] // 0.15 sec
  20. Updating line element (10,000 times) Updating line element (10,000 times)

    line = $('span.line:first') line.replaceWith( '<span class="line"><span class="fg4">foo</span> * 30</span>' ) // 3.7 sec
  21. Updating line element (10,000 times) Updating line element (10,000 times)

    line = $('span.line:first') line.replaceWith( '<span class="line"><span class="fg4">foo</span> * 30</span>' ) // 3.7 sec line[0].innerHTML = '<span class="fg4">foo</span> * 30' // 8.5 sec
  22. Updating line element (10,000 times) Updating line element (10,000 times)

    line = $('span.line:first') line.replaceWith( '<span class="line"><span class="fg4">foo</span> * 30</span>' ) // 3.7 sec line[0].innerHTML = '<span class="fg4">foo</span> * 30' // 8.5 sec line[0].innerHTML = '<span><span class="fg4">foo</span> * 30</span>' // 3.0 sec (winner!)
  23. Final DOM structure Final DOM structure <span class="line"> <span> <!--

    <=== added --> <span class="fg1">fow</span> <span class="fg2">a</span> </span> </span> ... <span class="line"> <span> <!-- <=== added --> <span class="bg3">XXXX</span> </span> </span>
  24. Optimisation result Optimisation result renderLine before: 6.7 sec renderLine before:

    6.7 sec renderLine after: 4.4 sec renderLine after: 4.4 sec ~35% faster line rendering ~35% faster line rendering
  25. ascii.io player supports multiple ascii.io player supports multiple renderers renderers

    <pre> , with "copy & paste" 2d <canvas> , faster, no "copy & paste" WebGL based is in plans
  26. calling renderLine 10,000 times calling renderLine 10,000 times renderLine(0, [['foo',

    brush], ['bar', brush], ['baaaz', brush]]) <pre>: 1.4 sec <canvas>: 0.9 sec
  27. Optimisation result Optimisation result Additional 35% faster line rendering Additional

    35% faster line rendering * but no copy/paste functionality * but no copy/paste functionality
  28. Usual animation code: Usual animation code: function draw() { //

    Drawing code goes here } setInterval(draw, 100); Or: Or: function draw() { setTimeout(draw, 100); // Drawing code goes here } draw();
  29. The problem The problem You You tell the browser when

    to paint tell the browser when to paint
  30. Why is this a problem? Why is this a problem?

    Browsers have rendering loop for updating view ...that is optimized to be in sync with screen refresh rate ...and optimized to update all changed page elements in single batch
  31. Why is this a problem? Why is this a problem?

    By telling the browser when to render ...you don't care what else is happening in the browser ...you cause additional CPU wake-ups ...you're making browser's UI less responsive
  32. requestAnimationFrame to the rescue requestAnimationFrame to the rescue Native API

    for running any type of animation in the browser Proposed by Mozilla Adopted and improved by WebKit team
  33. When to render? When to render? The browser just knows

    better. The browser just knows better.
  34. Then Then function draw() { setTimeout(draw, 100); // Drawing code

    goes here } draw(); Now Now function draw() { requestAnimationFrame(draw); // Drawing code goes here } draw();
  35. If you have a painting loop If you have a

    painting loop Only collect the changes to be painted Paint in requestAnimationFrame callback
  36. Then Then function scheduleFrame() { ... setTimeout(function() { processFrame(data) },

    delay); } function processFrame(data) { var changes = interpret(data); render(changes); scheduleFrame(); } scheduleFrame();
  37. Now Now function scheduleFrame() { ... setTimeout(function() { processFrame(data) },

    delay); } function processFrame(data) { var changes = interpret(data); this.changes = mergeChanges(this.changes, changes); scheduleFrame(); } scheduleFrame();
  38. Then Then function render(changes) { for (var n in changes)

    { renderLine(n, changes[n]); } } Now Now function render() { requestAnimationFrame(render); for (var n in this.changes) { renderLine(n, this.changes[n]); } this.changes = {}; } render();
  39. Optimisation result Optimisation result Smoother, jitter-free animation Smoother, jitter-free animation

    Browser UI as responsive as with no animation Browser UI as responsive as with no animation
  40. What are Web Workers? What are Web Workers? scripts that

    run in background thread window and document not available setTimeout, setInterval and XHR available used for computationally expensive tasks
  41. Page script: Page script: var worker = new Worker('my_task.js'); worker.onmessage

    = function(event) { console.log("Worker said : " + event.data); }; worker.postMessage('ali'); my_task.js my_task.js self.postMessage("I'm working before postMessage('ali')."); self.onmessage = function(event) { self.postMessage('Hi '+ event.data); };
  42. Example data stream to parse Example data stream to parse

    data = "Hello FOWA!\nSome colors: \x1b[31mred, \x1b[1mbold"
  43. Parsing happens in browser's UI thread Parsing happens in browser's

    UI thread # UI thread render() render() processFrame() . interpret() . scheduleFrame() render() render() processFrame() . interpret() . scheduleFrame() render() render()
  44. Parsing happens in Worker thread Parsing happens in Worker thread

    # UI thread # Worker thread render() processFrame() render() . changes = interpret() mergeChanges(changes) <= . postMessage(changes) render() . scheduleFrame() render() processFrame() render() . changes = interpret() mergeChanges(changes) <= . postMessage(changes) render() . scheduleFrame() render() processFrame() render() . changes = interpret() mergeChanges(changes) <= . postMessage(changes) render() . scheduleFrame()