MyFeed Performance Rework

TL;DR

After using MyFeed RSS app for about a year or so, I thought i'd take a look to see what kind of performance impact it has on my devices. To do this I measured the network and CPU to see if there were any clear problems and easy adjustments that could be made to improve.

As a bit of background MyFeed is a progressive web app RSS reader, which can be installed on most devices, operating systems and browsers. It runs *almost* completely browser side, with a small CORS proxy powered by Cloudflare Workers. The app itself uses plain ES6 JavaScript and the lit-html library to provide reactive DOM updates. Plain ES6, no babel transpile step, is possible due to supporting only modern browsers (everything but IE) and allows the app to be super lightweight. Read more about how and why I built it here.

I used Chrome Dev Tools to take network and CPU performance readings of the feed page. As this is the view that has the main functionality of the app I wanted to see the impact of this.

Starting with the network, things already look pretty good, a total of 62.9kb of JavaScript to run the app, and only 30.8kb transferred (compressed) over the wire. For comparison create-react-app generates 133kb of JavaScript before any application code!

Looking closer at the network log revealed that localforage, the library I'm using to interact with IndexedDB took around 28kb of the total 62.9kb of JavaScript.

Showing JavaScript network logs of the MyFeed app

I found a nice alternative called idb by Jake Archibald. Weighing in at around 2kb, it provides a nice Promise based wrapper around the native IndexedDB APIs. I would like to interact directly with native IndexedDB APIs but they're so cumbersome I would probably end up making my own wrapper around them. Switching out localforage made a big difference to total JS size, but the next step was looking into CPU performance.

Dev tools has a nice feature in the performance tab where you can slowdown your CPU when taking performance measurements. I opted to turn this setting to 6x CPU slowdown to simulate low end devices. After taking a performance recording I could drill down through the call stack to understand which functions took the longest to run.

Showing CPU usage by JavaScript functions

generateFeed() is the function used to create the final HTML to be injected into the page, drilling down you can see the n.fromNow() function takes quite a long time to run. This comes from the DayJS library and is responsible for creating the relative time for each article e.g "1 hour ago", "5 days ago" etc. As the app only has to support modern browsers, this meant that I could use what's included in modern browsers such as the Intl library, which provides international time formatting, as well as other things.

Showing CPU usage by JavaScript functions after the changes

I created a thin wrapper function getRelativeTime() to generate the time format and the results were pretty amazing. It now takes only 1.3ms to generate the relative time, down from 59.4ms with DayJS, a huge win for CPU time. Plus it meant I could remove the additional network request for the DayJS library. Another win for application load time!

Showing JavaScript network logs after the changes

In summary looking at what the app uses and switching out libraries for others that will fit the use case has allowed for large reductions in network requests and CPU time.

Network

Before: 62.8kb total JS (30.8kb over the wire)

After: 29.1kb total JS (19.9kb over the wire)

CPU Performance

Before: 65.8ms to run generateFeed()

After: 5.7ms to run generateFeed()

I will continue to look at different parts of the app to see what can be swapped out / tweaked. The next step will be to look at the feed fetching performance, and see what increases can be made there, such as concurrent fetching via Promise.all(), as well as performance tweaks and timeouts added to the CORS proxy.

Stay tuned!