Your Browser’s UI thread is overloaded, underpaid, and begging your help

GapLo.Tech

Overworked

60 fps

16.67 ms

Virual DOM Reconciliation

Rendering

Painting

System

0ms

5ms

10ms

15ms

20ms

Why 99% of the Browsers’ UI threads are overworked?

State Management

Ideal

Virual DOM Reconciliation

Rendering

Painting

System

Why 99% of the Browsers’ UI threads are overworked?

State Management

Actual

Remote API Response Massage

0ms

5ms

10ms

15ms

20ms

Virual DOM Reconciliation

Rendering

Painting

System

Why 99% of the Browsers’ UI threads are overworked?

State Management

Actual

Remote API Response Massage

0ms

5ms

10ms

15ms

20ms

Tracking Analytic Event

Virual DOM Reconciliation

Rendering

Painting

System

Why 99% of the Browsers’ UI threads are overworked?

State Management

Actual

Remote API Response Massage

0ms

5ms

10ms

15ms

20ms

Tracking Analytic Event

Persisting Data and Creating in-Memory Store

Why 99% of the Browsers’ UI threads are overworked?

Actual

0ms

5ms

10ms

15ms

20ms

Drop frame

UI Thread's Performance is Unpredictable

Why 99% of the Browsers’ UI threads are overworked?

Actual - Intel i9 9900k

0ms

5ms

10ms

15ms

20ms

Drop frame

Why 99% of the Browsers’ UI threads are overworked?

Actual - SnapDragon 632 (2018)

104ms

< 10 fps !

Drop frame

"Web App is poor"

Underpaid

Judged by Comparing
other platform's UI Development

let nameLabel = UILabel()

DispatchQueue.global(qos: .background).async {
  getRemoteUser(id: 1) { data in 
    DispatchQueue.main.async {
      nameLabel.text = data.username
    }
  }
}

Why the UI thread is underpaid?

iOS Development

val nameText = TextView()

GlobalScope.launch(Dispatchers.IO) {
  val data = getRemoteUser(id = 1) // suspend function
  GlobalScope.launch(Dispatchers.Main) {
    nameText.text = data.username
  }
}

Why the UI thread is underpaid?

Android Development

const nameElem = document.getElementById('name')

// wait and process on main(UI) thread
const data = await getRemoteUser(1)

nameElem.innerHTML = data.username

Why the UI thread is underpaid?

Web Development

60Hz
90Hz
120Hz

16.67ms
11.11ms
8.33ms

Why the UI Thread is underpaid?

Actual - iPad Pro (60Hz)

0ms

5ms

10ms

15ms

20ms

Drop frame

Why the UI Thread is underpaid?

Actual - iPad Pro (120Hz)

0ms

5ms

10ms

15ms

20ms

Drop frame

SOS Signal

via Console

via Profiler

Live Demo

Rescure

We are not writing

a 120Hz game!

Nokia 3.4, Released on September 2020

SnapDragon 460

- Equiv. to iPhone 5s
(7 year ago)

- Equiv. to Nexus 5X
(5 year ago)

Single Core Performance of  2020 Budget Phone

writing a smooth web app for everyone!

Off-Main-Thread Architecture

View

Action

Store

Dispatchers

Action

Flux

Main Thread 

View

Action

Store

Dispatchers

Action

Flux (Simple)

Web Worker

Main Thread 

Worker Thread 

View

Action

Store

Dispatchers

Action

Flux (Aggresive)

Web Worker

Main Thread 

Worker Thread 

Web Worker Challenages

  • No Shared Memory
  • No DOM Access
  • No USB, Media, WebRTC, Bluetooth
  • Isolated Scope, Difficult to Import libraries
  • Completely Message Driven
// app.js
const nameElem = document.getElementById('name')

// wait and process on main(UI) thread
const data = await getRemoteUser(1)

nameElem.innerHTML = data.username
// app.js
const nameElem = document.getElementById('name')
const worker = new Worker('./worker.js')

worker.postMessage({ topic: 'getRemoteUser', data: { id: 1 }})

worker.addEventListener('message', event => {
  nameElem.innerHTML = event.data.username
})

// worker.js
async function getRemoteUser(id) {
  return fetch(..)
}

addEventListener('message', async event => {
  switch(event.data.topic) {
    case 'getRemoteUser':
      const data = await getRemoteUser(event.data)
      postMessage(data)
    default:
      throw new Error('not supported message')
  }
})

Introducing

Comlink Loader (Webpack)

Turning Message-driven to RPC

import { getRemoteUser } from './userApi.worker.singleton.js'

const nameElem = document.getElementById('name')

// now process on worker thread!
const data = await getRemoteUser(1)

nameElem.innerHTML = data.username


// userApi.worker.singleton.js
export async function getRemoteUser(id) {
  return fetch(..)
}

Live Demo

Thanks!

Made with Slides.com