Slide 1

Slide 1 text

Javascript and HTML(5) Javascript and HTML(5) Optimisation Optimisation ascii.io player case study Marcin Kulik Marcin Kulik

Slide 2

Slide 2 text

Marcin Kulik Marcin Kulik

Slide 3

Slide 3 text

I work at I work at

Slide 4

Slide 4 text

I share at github & twitter as I share at github & twitter as sickill sickill

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

Developer's workflow Developer's workflow

Slide 7

Slide 7 text

Write some code Write some code $ rails generate resource Logo $ vim -O app/models/logo.rb spec/models/logo_spec.rb

Slide 8

Slide 8 text

Run tests Run tests $ rake ...........

Slide 9

Slide 9 text

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...

Slide 10

Slide 10 text

Block facebook.com Block facebook.com $ echo "127.0.0.1 facebook.com" | sudo tee -a /etc/hosts

Slide 11

Slide 11 text

Push changes to a repository Push changes to a repository $ git add . $ git commit -m "I must have been drunk." $ git push

Slide 12

Slide 12 text

What do these actions have in What do these actions have in common? common?

Slide 13

Slide 13 text

They all happen in a terminal They all happen in a terminal

Slide 14

Slide 14 text

Screencasts Screencasts

Slide 15

Slide 15 text

No content

Slide 16

Slide 16 text

Quick Quick history history lesson lesson

Slide 17

Slide 17 text

No content

Slide 18

Slide 18 text

No content

Slide 19

Slide 19 text

VT100 VT100 Released in 1978 Foundation for terminals we use

Slide 20

Slide 20 text

ascii.io ascii.io

Slide 21

Slide 21 text

No content

Slide 22

Slide 22 text

Example use cases Example use cases

Slide 23

Slide 23 text

Tutorials Tutorials Rails blog app in 15 minutes? ;) Vim git shell tricks

Slide 24

Slide 24 text

Bug reporting Bug reporting app starts leaking memory app starts eating CPU

Slide 25

Slide 25 text

Auto-recording of shell sessions Auto-recording of shell sessions

Slide 26

Slide 26 text

# 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_ !

Slide 27

Slide 27 text

The player The player

Slide 28

Slide 28 text

HTML5 based VT100/102 HTML5 based VT100/102 implementation implementation Multiple renderers:
 and

Fast & responsive
Tested with Jasmine


Slide 29

Slide 29 text

How the player works? How the player works?

Slide 30

Slide 30 text

View / Markup View / Markup

...

Slide 31

Slide 31 text

No content

Slide 32

Slide 32 text

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();

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

Buffer data model Buffer data model totalLines = 4 totalColumns = 10 lines = [line0, line1, line2, line3] lineX = [cell0, cell1, ... cell9] cellX = [character, brush]

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

"Changes" object "Changes" object { 0: [ ["f", b1], ["o", b1], ["w", b1], ["a", b2] ], 4: [ ["X", b3], ["X", b3], ["X", b3], ["X", b3] ] }

Slide 37

Slide 37 text

Rendering Rendering function render(changes) { for (var n in changes) { renderLine(n, changes[n]); } } function renderLine(n, line) { ... }

Slide 38

Slide 38 text

Resulting DOM structure Resulting DOM structure f o w a ... X X X X

Slide 39

Slide 39 text

... and what it looks like ... and what it looks like fowa XXXX

Slide 40

Slide 40 text

1. Limit number of DOM 1. Limit number of DOM elements elements

Slide 41

Slide 41 text

Improved "changes" object Improved "changes" object { 1: [ ["fow", b1], ["a", b2] ], 5: [ ["XXXX", b3] ] }

Slide 42

Slide 42 text

Resulting DOM structure Resulting DOM structure fow a ... XXXX

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

2. Know jQuery/DOM 2. Know jQuery/DOM perf characteristics perf characteristics

Slide 45

Slide 45 text

renderLine() renderLine()

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

Updating line element (10,000 times) Updating line element (10,000 times) line = $('span.line:first') line.replaceWith( 'foo * 30' ) // 3.7 sec

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

Updating line element (10,000 times) Updating line element (10,000 times) line = $('span.line:first') line.replaceWith( 'foo * 30' ) // 3.7 sec line[0].innerHTML = 'foo * 30' // 8.5 sec line[0].innerHTML = 'foo * 30' // 3.0 sec (winner!)

Slide 53

Slide 53 text

Final DOM structure Final DOM structure fow a ... XXXX

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

3. Use 3. Use

Slide 56

Slide 56 text

ascii.io player supports multiple ascii.io player supports multiple renderers renderers
 , with "copy & paste"
2d  , faster, no "copy & paste"
WebGL based is in plans


Slide 57

Slide 57 text

 vs 
 vs 


Slide 58

Slide 58 text

calling renderLine 10,000 times calling renderLine 10,000 times renderLine(0, [['foo', brush], ['bar', brush], ['baaaz', brush]])
: 1.4 sec
: 0.9 sec


Slide 59

Slide 59 text

Optimisation result Optimisation result Additional 35% faster line rendering Additional 35% faster line rendering * but no copy/paste functionality * but no copy/paste functionality

Slide 60

Slide 60 text

ascii.io default renderer:
ascii.io default renderer: 
http://ascii.io/a/3?renderer=canvas
http://ascii.io/a/3?renderer=canvas


Slide 61

Slide 61 text

4. Use rAF 4. Use rAF requestAnimationFrame requestAnimationFrame "The secret to silky smooth JavaScript animation"

Slide 62

Slide 62 text

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();

Slide 63

Slide 63 text

The problem The problem You You tell the browser when to paint tell the browser when to paint

Slide 64

Slide 64 text

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

Slide 65

Slide 65 text

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

Slide 66

Slide 66 text

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

Slide 67

Slide 67 text

When to render? When to render? The browser just knows better. The browser just knows better.

Slide 68

Slide 68 text

Then Then function draw() { setTimeout(draw, 100); // Drawing code goes here } draw(); Now Now function draw() { requestAnimationFrame(draw); // Drawing code goes here } draw();

Slide 69

Slide 69 text

If you have a painting loop If you have a painting loop Only collect the changes to be painted Paint in requestAnimationFrame callback

Slide 70

Slide 70 text

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

Slide 71

Slide 71 text

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

Slide 72

Slide 72 text

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();

Slide 73

Slide 73 text

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

Slide 74

Slide 74 text

5. Use Web Workers 5. Use Web Workers

Slide 75

Slide 75 text

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

Slide 76

Slide 76 text

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); };

Slide 77

Slide 77 text

Web Workers in ascii.io Web Workers in ascii.io

Slide 78

Slide 78 text

function processFrame(data) { var changes = interpret(data); this.changes = mergeChanges(this.changes, changes); scheduleFrame(); }

Slide 79

Slide 79 text

Example data stream to parse Example data stream to parse data = "Hello FOWA!\nSome colors: \x1b[31mred, \x1b[1mbold"

Slide 80

Slide 80 text

Escape sequence for making text red Escape sequence for making text red \x1b[31m

Slide 81

Slide 81 text

Highly dynamic terminal apps Highly dynamic terminal apps

Slide 82

Slide 82 text

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()

Slide 83

Slide 83 text

The UI thread is what user "feels" The UI thread is what user "feels"

Slide 84

Slide 84 text

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()

Slide 85

Slide 85 text

Optimisation result Optimisation result Even more responsive browser UI Even more responsive browser UI

Slide 86

Slide 86 text

ascii.io source code ascii.io source code https://github.com/sickill/ascii.io https://github.com/sickill/ascii.io

Slide 87

Slide 87 text

Thanks! Thanks! Questions please Questions please @sickill @sickill