1 of 55

Wrap up:�High Performance JavaScript

Sho Miyamoto (@shqld)

2 of 55

Who I am

Sho Miyamoto (@shqld)

  • Web Developer
    • At a certain NewsPaper Company
    • {Edge, Server, Client}-side Engineer
    • JavaScript Enthusiast
    • (Web)Performance Enthusiast
      • CDN, Node, Loading, Rendering, ...

2

3 of 55

Agenda

Goal: Wrap-up JavaScript performance aspects explaining platform-specific things.

  • Layers
    • 🙆‍♂️ Runtime-
    • 🙆‍♂️ Engine-
    • 🙅‍♂️ Product-
  • Optimization
  • Profiling

3

4 of 55

Introduction

4

5 of 55

JavaScript Layers

Product�(application, library, ...)

Runtime�(Node.js, browsers, ...)

Engine�(V8, SpiderMonkey, …)

5

6 of 55

JavaScript Implementations

  • Server-side:
    • Node.js(V8), deno(V8)
    • QuickJS, ...
  • Client-side:
    • Browsers
      • Chrome(V8), FireFox(SpiderMonkey), Safari(JavaScriptCore), IE(Chakra), Flow(SpiderMonkey), ...
    • ReactNative
      • iOS(JavaScriptCore), Android(V8, hermes)
    • Electron(V8), ...

(*) : Embedded engine

6

7 of 55

Runtime &

Engine

7

8 of 55

Runtimes & Engines

Engines

  • V8
  • SpiderMonkey
  • JavaScriptCore
  • Chakra
  • Hermes
  • JerryScript
  • ...

Runtimes

  • Browsers
  • Node.js
  • deno
  • ReactNative
  • Electron
  • ...

8

9 of 55

Runtime vs. Engine

Engine

  • parses, compiles, executes
  • JIT, VM, ...

Runtime

  • has an embedded engine inside
  • runtime platform for JS
  • has each own Global Object�(window, process, …)
  • can override own EventLoop
  • ...

9

10 of 55

Levels of optimization

10

11 of 55

Levels of optimization

Product-level

  • memoization, cache, algorithm, …

Runtime-level

  • async operation, code fetching, …

Engine-level

  • IC, SMI/HeapNumber, Packed/Holey Array, …

Be aware of the level of what you are doing for performance

11

12 of 55

Levels of optimization (2)

Impacts:

Product >>>>>> Runtime >>>>>> Engine

12

13 of 55

Micro-optimization

“Don’t focus on manual micro-optimizations,

instead write readable code

and let JS engines do their job!”

https://twitter.com/mathias/status/1171664472035004423

JavaScript is fast enough

13

14 of 55

Optimization

14

15 of 55

Server-side runtime

Node.js

Goal: Make each operation fast

🙅‍♂️ Out of scope

  • Options (UV_THREADPOOL_SIZE, ...)

15

16 of 55

Server-side runtimes

  • Server-side
    • Node.js, deno
    • QuickJS
    • ...

16

17 of 55

I/O

I/O (Network, FileSystem, …) operation tends to be bottlenecks for web applications.

Let’s focus on this here.

17

18 of 55

Node.js

  • Single-threaded
  • Async non-blocking IO
  • Embedding V8 as the engine

18

19 of 55

Node.js: Async Non-blocking I/O

Powered by libuv (which is tokio in deno)

  • Every I/O operation is asynchronous
    • Network, Socket, FileSystem, …
  • Also able to opt-out to sync ones
    • fs.readFileSync, …

19

20 of 55

What’s EventLoop

Basically, just a while loop

while (true) {

// various operations

if (isEnd) break;

}

20

21 of 55

EventLoop Flow

(Node.js)

┌───────────────────────────┐

┌─>│ timers

│ └─────────────┬─────────────┘

│ ┌─────────────┴─────────────┐

│ │ pending callbacks

│ └─────────────┬─────────────┘

│ ┌─────────────┴─────────────┐

│ │ idle, prepare

│ └─────────────┬─────────────┘ ┌───────────────┐

│ ┌─────────────┴─────────────┐ │ incoming: │

│ │ poll │<─────┤ connections, │

│ └─────────────┬─────────────┘ │ data, etc. │

│ ┌─────────────┴─────────────┐ └───────────────┘

│ │ check

│ └─────────────┬─────────────┘

│ ┌─────────────┴─────────────┐

└──┤ close callbacks

└───────────────────────────┘

21

setTimeout

I/O operations

22 of 55

Sync I/O operation blocking

Sync (blocking) operations: can block other sync/async operations for a long time i.e. can pause eventloop

This could be matter (typically on servers)

22

23 of 55

Avoid sync operations

Hint: avoid sync I/O operations

  • many standard module methods named like -Sync
  • readFile >>> readFileSync

But… still sync long-tasks (not I/O) are inevitable somewhere.

23

24 of 55

EventLoop Flow (2)

┌───────────────────────────┐

┌─>│ timers

│ └─────────────┬─────────────┘

│ ┌─────────────┴─────────────┐

│ │ pending callbacks

│ └─────────────┬─────────────┘

│ ┌─────────────┴─────────────┐

│ │ idle, prepare

│ └─────────────┬─────────────┘ ┌───────────────┐

│ ┌─────────────┴─────────────┐ │ incoming: │

│ │ poll │<─────┤ connections, │

│ └─────────────┬─────────────┘ │ data, etc. │

│ ┌─────────────┴─────────────┐ └───────────────┘

│ │ check

│ └─────────────┬─────────────┘

│ ┌─────────────┴─────────────┐

└──┤ close callbacks

└───────────────────────────┘

24

LongTask!!�

Pausing loop...

25 of 55

Scheduling sync operations

See a sample: Async handling for sync operations on server-side, leveraging EventLoop (Sho Miyamoto)

Hint: schedule sync operations with timers to avoid blocking

25

26 of 55

Node.js: Stream API

  • Effective API for streaming data
  • Operations look like piping stdin between commands

e.g.

  • http.{ClientRequest, IncomingMessage}
  • fs.{ReadStream, WriteStream}
  • process.{stdin, stdout, stderr}, ...

26

27 of 55

Without Stream

Imagine you read a large file and compress it dynamically and serve it.

27

Source: File

Binary/String

serve

transform

28 of 55

Without Stream

http

.createServer((req, res) => {

res.setHeader('Content-Type', 'text/plain')

res.setHeader('Content-Encoding', 'gzip')

fs.readFile('hello.txt', (err, file) => {

zlib.gzip(file, (err, data) => {

res.end(data, () => {

console.log('done')

})

})

})

})

.listen(3000)

  • nested
  • less memory-efficient

28

29 of 55

With Stream

Imagine you read a large file and compress it dynamically and serve it.

Note that reading, transforming and serving run at the same time.

29

Source: File

Buffer: Stream

serve

transform

read

30 of 55

With Stream

http

.createServer((req, res) => {

res.setHeader('Content-Type', 'text/plain')

res.setHeader('Content-Encoding', 'gzip')

fs.createReadStream('hello.txt')

.pipe(zlib.createGzip())

.pipe(res)

.addListener('close', () => {

console.log('done')

})

})

.listen(3000)

  • no nest
  • memory-efficient
  • progressively rendering on browser
  • declarative

30

31 of 55

Node.js: Stream API

React.renderToNodeStream

ReactDOMServer.renderToNodeStream(element).pipe(res)

Hint: Use Stream for memory-intensive operations or sending large data where possible

31

32 of 55

Client-side runtime

Chrome

Goal: make page loading and navigation fast

🙅‍♂️ Out of scope

  • Rendering & Parsing HTML
  • General web performance

32

33 of 55

Client-side runtimes

  • Client-side:
    • Browsers
      • Chrome, FireFox, Safari, IE, Flow, ...
    • Electron
    • React-Native
      • iOS, Android

33

34 of 55

Parse & Compile

Parsing & compiling are heaviest in JavaScript execution

34

35 of 55

Parse & Compile

35

36 of 55

Chrome

  • Chrome: a product
  • Chromium: a project & its software�
  • Rendering Engine: blink
  • JavaScript Engine: V8

  • Also has its own EventLoop system

36

37 of 55

Code Caching

Before execution, Chrome(V8) parses and compiles scripts, which can take long time

By Code Caching, parsing and compiling can be skipped

37

38 of 55

Code Caching

Caching scripts to skip parsing & compiling

  • Scripts that are executed twice in 72 hours
  • Scripts of Service Worker that are executed twice in 72 hours
  • Scripts stored in Cache Storage via Service Worker in the first execution

Hint: Cache scripts in CacheStorage via ServiceWorker as possible

38

39 of 55

ESModules on browsers

  • Modern browsers now support ESModules natively
  • import/export
  • Dynamic imports, Code Splitting
  • Differential Loading

39

40 of 55

Preloading scripts

<link rel="preload" href="index.mjs">

<link rel="modulepreload" href="index.mjs">

40

41 of 55

Preloading scripts

<link rel="modulepreload" href="index.mjs">

`modulepreload` is optimized for modules

modulepreload’ed scripts are parsed and compiled as soon as fetching is done, like Code Caching

Hint: when you use `type modules`, adding `link modulepreload` would have effect

41

42 of 55

idling (idlization*)

  • Idle Until Urgent: Scheduling code execution

requestIdleCallback, cancelIdleCallback

cf: https://github.com/GoogleChromeLabs/idlize

Hint: Avoid long-tasks blocking main thread for better FID & TTI unless it’s needed

42

43 of 55

Lazy parsing

It’s not always every single part of scripts are parsed and evaluated.

For some performance purposes, Chrome parses only immediately needed parts (Lazy parsing)

  • iife, pife

43

44 of 55

Engine

V8

Goal: Reduce the overhead of each process across the board

🙅‍♂️ Out of scope

  • Hacks (...)

44

45 of 55

Engines

  • Server-side:
    • Node.js(V8), deno(V8)
    • QuickJS, ...
  • Client-side:
    • Browsers
      • Chrome(V8), FireFox(SpiderMonkey), Safari(JavaScriptCore), ...
    • ReactNative
      • iOS(JavaScriptCore), Android(V8, hermes)
    • Electron(V8), ...

(*) : Embedded Engine

45

46 of 55

V8

  • The most used JavaScript (& Wasm) engine
    • typically Chrome and Node.js

46

47 of 55

V8: Pipeline

Many engines have similar systems.

47

48 of 55

V8: Internal Representation (examples)

Type: (See v8/src/builtins/base.tq for details)

  • Object => Smi | HeapObject
  • Number => Smi | HeapNumber

Values:

  • Smi: 31-bit signed integer
  • HeapNumber: other numbers

48

49 of 55

V8: Hidden Class

49

const obj = {}

obj.x = 1; obj.y = 2;

50 of 55

V8: Hidden Class

Not only Shape but also Representation matter

50

51 of 55

V8: Inline Cache

Caching for property access & method calls based on HiddenClass

Hint: Always keep objects’ shape and types as possible

51

52 of 55

V8: Element types

  • In-object properties
  • Fast properties
  • Slow properties

52

53 of 55

V8: Element kinds

  • Packed/Holey...

53

54 of 55

Takeaways

- Avoid sync I/O operations�- Schedule sync operations with timers to avoid blocking�- Use Stream for memory-intensive operations or sending large data where possible�- Cache scripts in CacheStorage via ServiceWorker as possible�- When you use `type modules`, adding `link modulepreload` would have effect�- Avoid long-tasks blocking main thread unless it’s needed�- Always keep objects’ shape and types as possible

54

55 of 55

Thank you.

55