Performances: hunting inefficiency

Looking at in the nightlies apk gives a lot of insights on what we are doing wrong in the app in terms of the resulting size of the javascript our codebase is producing and that needs to be loaded before the user can interact with the app.

First of all kudos to @roman for all his work on getting advanced compilation working, as well as modules in vanilla cljs (though I would have prefered shadow :stuck_out_tongue_winking_eye:) and answering all my questions and inquiries

  1. The bad stuff
  • [TODO] Slurp macro

Using the slurp macro is bad, whatever you slurp ends up being copied as is as a string in the minified js produced by the compiler, even with advanced optimization.

SVGs alone are 1 meg, but they are advanced compile so the final size is smaller, though that isn’t necessarily good news either, because that means they need to be evaled when starting the app, which is tedious for slow devices Replace by PNG

  • [TODO] html, js and css code

Either slurped (the worst) or required at some point to be loaded in a webview:

  • webviews that slurp 140k of minified web3 and 25k of non minified code of ours for status API [UNSLURP/MINIFY] → This should slightly improve response time as well when opening a dapp
  • extensions are using an html file, 4k that is plain text in the final bundle, js and css are just required [UNSLURP/MINIFY]
  • [TODO] List of strings and anything than could be turned into a json and is in the code instead

For instance

  • gfycat.animals 25k
  • gfycat.adjectives 18k
  • signing-phrase.dictionaries.en 6k
    could be turned into jsons and modularized

We also have actual jsons that are slurped, like the node configs,this is also bad

  • [TODO] Dead code that isn’t advanced compiled

The old accouts.status is unused, but because it is required and defined in a reg-fx it isn’t advanced compile, I didn’t follow the code but for sure it is not used at all, the statuses are 4k of strings + the dead code that follows.

  • [PARTIALLY DONE] look at the advanced compile bundle and check for useless stuff that could be removed or modularized
  1. The stuff we should deal with sooner rather than later

So the previous chapter was about the bad, stuff that should definitely not be in v1. Now comes the stuff that could definitely make a small difference too:

  • [TODO] upgrading to react-navigation 3 and minimize wrapping of view

Currently we have a lot of wrapping going on for navigation. It seems like this has been improved in react-navigation 3, for instance one problem was safe-area and most bugs have been fixed. Plus when skimming through the changes I recall it simplifies caching of view, though that might be only for drawers and rn2 was already doing it?

  • [TODO] clean up the chat components UI

There is a lot of overhead here, wrappers wrapping wrappers, a much more straightforward component hierarchy would both reduce the code size and improve performances.

POC PR for experiments: [POC] simplify chat components for perf by yenda · Pull Request #8364 · status-im/status-mobile · GitHub
A PR applying one of these changes

  • [TODO] use local state in components

Whenever dispatching an event in a component, think about whether the whole app has to know about it or not.
If not, does it need to survive the unmounting of the screen? If not, then it’s definitely a candidate for local state

  1. Stuff I didn’t mention

This is just from analyzing the bundle and component hierarchy. I could also say again that in terms of js thread, everything is completely overshadowed by the huge strains of receiving messages from the inbox. So big kudos to status-go team @igor @adam for working on the chat API that will help with that. Kudos to @dmitrys as well for helping me out with the wallet API.


Additional notes:

we were wondering with @roman about the influence of bundle size compared to number of instructions, seems like size has less influence than actual code:
so finally, regarding bundle size: 9.5M without cljs obfuscation is loaded in 5.8s, ~4M bundle with obfuscation takes 5.12s. So i can say that most likely the number of instructions in the code is more important than the size of the file. The code is actually same in both versions, it is just minimized better in one of cases

1 Like

plateform specific code

We are using platform/ios? platform/android? and platform/desktop? in the code base, this could be replaced by macros to only compile the code for the plateform it is running on

ui/screens/desktop could be removed entirely from the mobile compilation


If I replace components.svg/slurp-svg with js/require the size of is 2835KB (wallet module branch). With components.svg/slurp-svg it is 2911KB. So that is 76KB, not sure how you got 1MB there.

goog/i18n adds 269KB

Moving all goog.i18n mentions to a separate module makes 9.17% smaller (2644KB vs 2911KB) and startup 4.1% faster. The loading of the module even on a slow device is quite fast (90ms), assuming its size is ~216KB. Probably it is a bit smaller and faster because I rewrote it a bit so that compilation warning doesn’t appear anymore, and thus it was optimized better during compilation.

I mean this warning

[2019-06-01T06:57:46.639Z] Jun 01, 2019 6:57:43 AM println
[2019-06-01T06:57:46.639Z] WARNING: /home/jenkins/workspace/status-react_prs_android_PR-8332/target/android-prod/status_im/utils/datetime.js:40: WARNING - incomplete alias created for namespace goog.i18n, possibly due to await/yield transpilation.
[2019-06-01T06:57:46.639Z] See for more details.
[2019-06-01T06:57:46.639Z] var loc = (function (){var G__23425 = goog.i18n;
[2019-06-01T06:57:46.639Z]                                       ^^^^^^^^^
06-05 08:25:32.863 30156 30193 D ReactNativeJS: DEBUG [] - :load-goog.i18n-module
06-05 08:25:32.953 30156 30193 D ReactNativeJS: DEBUG [] - :goog.i18n-module-loaded

Will file PR later today.


Performance updates:

  • realm upgrade now makes remote debugging much more meaningful, in emulator press Alt-M to open the developper menu and enable it, then in chrome you can use the developer tools, such as profiling to see what is running on the JS thread. Please do it to backup claim on perfs in js thread! From what I see this is really not a bottleneck except for the reception of loads of messages from mailbox. It looks like we need to do more work on improving our components, the chat ui components are the bottleneck when navigating to a chat from what I can see
  • realm upgrade should also improve perfs in general for encrypted db
  • @roman turned translations and i18n into modules, saving startup time
  • I made a PR that greatly improves rendering of markup
  • I edited OP to add what is DONE/TODO

I think right now considering the migration of message handling to status-go api, the real drag on performance is on rendering according to the profiles I could generate when opening chats, but I’d be curious to see some generated by low end devices if possible:

Profile-20190606T003427.json - Google Drive!

Above is a screenshot of the js thread for the 50ms it takes to navigate to a chat (but end up on white screen)

I would like to understand what is the app doing during the almost 1 sec it takes to finish loading the chat, considering it looks like js thread does nothing most of the time:

That being said, easy todo for very small perf improvement:

  • replace pr-str and edn/read by core/serialize and core/deserialize in data-store namespaces for a small query speedup on the entities that use it

most likely app is rendering the ui, it doesn’t happen on js thread

That is my guess as well but I don’t understand why it is so slow. I’m sure there is some trickery we could leverage here to make it render much faster. I mean it can render messages as fast as I scroll after that so something is wrong here


There most definitely is.

@anna asked me to create some GHIs so here is my wish list for when I return from vacations:
Slurp macro:


Web3 and minified html5

Patform specific code exclusion at compile time

Upgrade to react-navigation 3

I think you can use a native (Android Studio or Xcode) profile to see what exactly is happening on the non-JS threads at that time.