Application Performance and UX

tldr: get application entry points and screen rendering under or as close to 250ms, with a goal of 100ms rendering from user interaction.

Preamble

We have a goal of the application feeling production ready by the end of Q2, so Status can support the go to market strategies of Keycard, Teller Network and the SNT Utilities.

The keyword here is feeling. We’re a consumer facing application and how a user feels, how you feel when interacting with the application, matters.

It’d be amazing if we can raise this in our collective consciousness and ensure this is continually a priority moving forward.

Timing is Everything

Status faces substantial issues in rendering screens and loading in information, below I present 4 real world examples that demonstrates some of the worst offenders from my personal experience.

When it comes to protocols and UX, Status should be delay-tolerant and ‘offline first’, decoupling the retrieval of new information from the display of information the client already is aware of, and what it knows is incoming.

When it comes to rendering screens and entering the application, latency between user input or interaction should be kept to a minimum, it seems humans are capable of identifying visual stimulus changes around 50ms, while cognitive processing for reaction to visual stimulus happens around 250ms.

While user’s upper threshold for waiting for new information is about 10 seconds.
Unfortunately we are a far cry away from any of these user expectations.

I strongly recommend everyone reads the links below, there’s plenty of recommendations. To keep things convenient for the reader here’s some quotes to demonstrate impact:

[Google found that] the page with 10 results took 0.4 seconds to generate. The page with 30 results took 0.9 seconds. Half a second delay caused a 20% drop in traffic. Half a second delay killed user satisfaction.

In A/B tests, [Amazon] tried delaying the page in increments of 100 milliseconds and found that even very small delays would result in substantial and costly drops in revenue.

The 100 ms threshold was established over 30 yrs ago. See:

Card, S. K., Robertson, G. G., and Mackinlay, J. D. (1991). The information visualizer: An information workspace. Proc. ACM CHI’91 Conf. (New Orleans, LA, 28 April-2 May), 181-188.

Miller, R. B. (1968). Response time in man-computer conversational transactions. Proc. AFIPS Fall Joint Computer Conference Vol. 33, 267-277.

Myers, B. A. (1985). The importance of percent-done progress indicators for computer-human interfaces. Proc. ACM CHI’85 Conf. (San Francisco, CA, 14-18 April), 11-17.

Real-world Examples

The below scenarios are a daily occurrence for me. This is my real-world experience, I made an attempt to preserve some privacy by blurring some UI elements, however it may not be the case that I covered it entirely and apologize in advance.

When it comes to feeling, when using Status I observe within myself strong emotions of frustration and find Status tedious to use, which is further compounded with message reliability issues and little meaningful feedback on message delivery. I probably wouldn’t use Status if I wasn’t working on it, nor do I expect others to.

Cold Boot & Initial Sync

First Impressions matter, opening Status is the most important entry point to get right, this is still quite slow (regression?), It takes 13 seconds from opening Status to rendering the initial chat list. The rest of the video is syncing and does not complete, this time the syncing took over 15 minutes with auto-mailserver selection.

App-Entry from Push Notifications

The next important entry point into the application is from push notifications.

First-time

It takes 13 seconds to render the wrong UI elements, an additional 7 seconds to provide an error (which has persisted for some months and doesn’t seem to impact the display of chat history, as I get this frequently). I left the application recording for 15 minutes without ever seeing the message.

Second-time

40 mins apart from the previous recording I attempted to view a new push notification.

Here it takes 9 seconds to render the wrong UI elements, an additional 8 seconds to show offline state and error. 27 seconds to show the message, and not the chat history.

Here we should be displaying the chat history and a placeholder for the new incoming message.

Slow-Rendering of Chat History

From opening a chat from chat list, it takes about 2 seconds to render the most recent page of chat messages.

Application Switching

Not recorded, but when switching the application from background to foreground on Android there is a consistent syncing period and the application is slow, even when there is no meaningful change to application state.

Bonus Points

  • Notice the sync bar animation jitter, while I don’t know how it’s implemented, could this animation be offloaded to the UI thread somehow? Are there more instances of this?

  • Redraws. @roman already did some excellent work on this and proved that redraws matter, the list scrolling performance has improved substantially. There are still many red patches in the application and it would be nice to minimise this further if possible.

  • the 9+ icons are essentially meaningless to me

  • i often never receive push notifications for new messages

Accountability & Responsibilities

It would be great if UX, QA and Core swarms, in that order, took on the responsibility to be accountable for ensuring that these issues and others like it are permanently resolved before the end of Q2.

I strongly believe that improving this will make Status a much more enjoyable experience and help users be more forgiving in our other shortcomings.

Thank-you

14 Likes

Switching between chats on desktop (mac) and iOS has felt delayed compared with other messengers I have grown used to. On desktop clicking on to another room or conversation takes nearly 1 second from click to updated view.

On iOS it’s also nearly 1 second before the conversation is visible. I noticed on something like whats app the conversation is already rendered when the animation is sliding in, while on Status the window is blank during the animation. Practically one can’t really read much while it’s sliding in, but it does make it seem faster if the conversation is rendered in the window sliding in. I also think the slide in animation speed on Status is slower than on messengers like whats app.

I think it was a smart move by whats app to make the slide in animation slightly faster than the iOS default messages app because it makes it feel like whats app is faster than the default messages app.

2 Likes

This is some painful stuff to watch. For reference here, some factors I think we should investigate:

Engineering

  • Network and connectivity management (fetching of information, time outs, etc.)
  • Memory management
    (I put my beloved Fairphone to rest as I kept struggling with memory issues and could no longer even get past the loading screen)
  • GUI performance (optimizing framerates)
  • Input responsiveness (Are we optimally using React Native touchables)

Design

  • GUI performance (loading states, empty states, user controlled fetching)
  • Input responsiveness (specifying component states of all tap and touch events)

I’ll set up a kick-off call on how each of these factors and others might impact the issues @jarradhope described and have been reported elsewhere.

Meanwhile, I’m in the dark when it comes to the engineering side, not my area of expertise. If anyone has strong suspicions on where we can improve. Please ping me.

Can’t find a recent reference, but am pretty sure system performance has a proven effect on human performance, comfort and satisfaction through heart rate increase. So agreed, this is a big deal.

3 Likes

With regards to

I ran across these in https://github.com/status-im/status-react/pull/7663 and noticed this in another PR in progress Fix endless spinner on Fetch messages by rasom · Pull Request #7973 · status-im/status-mobile · GitHub

This has been in the back of my mind recently, and I do not exactly know how to improve it yet, but leaving this here as a side note.

1 Like

This is fairly shocking. I’m having much better load times on iOS, though if I haven’t synced in some time it’s much slower. But when I open a conversation, for instance, it takes less than 1 second.

I do have issues with chats syncing out of order, which is disorienting when switching devices.

the 9+ icons are essentially meaningless to me

The issue with 9+ icons for unread messages is on the front-end Core UI backlog and meant to be tackled in Q2 as part of the mark all as read feature, which would allow users to pick up where they left off in a public channel and then jump to bottom. The problematic behaviour for me there is that the 9+ badges do not clear properly once a conversation has been viewed. Is that also what you’re referring to @jarradhope?

2 Likes

Thanks for the detailed report, use cases and numbers. Thought I’d quickly partially replicate it as another data point:

Parameters

Phone: iPhone XS Max 10 / Honor 8 / Samsung Galaxy S8
Version: Recent (iPhone) / latest stable (Android)

Parameters: Phone / Chats / Last sync / Test case

Method: Ad hoc, counting seconds

Cold Boot & Initial Sync

iPhone XS Max (~50 chats; last sync: few minutes ago)

Cold boot: 3s to initial chat list
Initial sync: ~3.5s sec (just synced a few seconds ago)

Huawei Honor 8 (3 popular public chats with no history; first sync)

Cold boot: 6s

Initial sync: 30s+ (network bound? I could interact with chat earlier though)

App-Entry from Push Notifications

Didn’t test

Slow-Rendering of Chat History

iPhone XS Max (~50 chats; last sync: few minutes ago)
<0.5s (need to measure precisely for better granularity)

Huawei Honor 8 (3 popular public chats with 24h history; synced)

<~2s

Application Switching

iPhone XS Max (~50 chats; last sync: few minutes ago)

Essentially instant

Huawei Honor 8 (3 popular public chats with 24h history; synced)

Almost instant

Caveats and improvements

Things I didn’t do that it’d be great to see more data points on:

  • Android phone didn’t recover old account with lots of state
  • Didn’t test push notification flow
  • Didn’t test with other Android device (i.e. Samsung Galaxy S8) or older iPhone (i.e. 6)
  • Ad hoc measurement, so to get finer touch need to measure either with stopwatch/frame/profiling tool.

tldr

Essentially replicated on old Android device, cold boot time especially is ridiculous. General sluggishness on Android. More data points from e.g. QA cc would be useful.

Would love to see a plan from Core on how we plan to attack these more in detail, starting with low hanging fruits and biggest problems.

3 Likes

so the device I run these examples is a Huawei M5 8inch Tablet (yes I know tablets aren’t supported its just an android phone with a larger screen calm yo self)

It has a HiSilicon Kirin 960 processor, 4GB memory and a Mali-G71 MP8 GPU, state of the art in 2016. I`m not sure how useful the old/new distinction is considering our ui complexity, processor should be fine for decryption) and every other app performs wonderfully on it.

I have ~120 chats, and 10 dapps. and 100mbps download speed.

3 Likes

I think we will start with the cold startup and mostly focus on Android, looking at the numbers.

Also this slowness of syncing looks very suspicious, but it might be mitigated by switching protocol parsing to status-go (but that needs to be confirmed).

And I guess we will start with replicating the conditions, making a test account with around ~120 chats and similar specs like Huawei M5. I think we have a bot that can make a decent history for these chatrooms.

2 Likes

Here is a quick investigation

As you can see on the picture, we have one js thread and when a user presses a button, we:

  1. register re-frame event in a queue
  2. run all events in a queue
  3. calculate ALL active subscription functions (currently ~100!!)
  4. calculate all active render functions
  5. parse hiccup convert to js

Low hanging fruits

  • inspect all subs on performance
  • make more efficient subscriptions tree
  • find out why we calculate not visible render functions and subscriptions (i believe it’s because of react-navigation)
  • inspect events, optimize or split

Hight hanging

  • move events and subs in separate js thread, because of re-frame arch it should be doable, so in main thread will be only user action events
  • find out how realm impacts performance, because we need to convert message data many times, and save it encrypted in realm, i believe it would be much more efficient if we could store messages with status-go

Subsctiptions
Currently, we react on any change in app-db, and there maybe subscriptions with calculations, so we need to improve our subscriptions graph,
first node must be map only with keys which impact subscriptions, next, one subscription for each key in this map, and next subscriptions related to these keys

Events
re-frame has a solution for events. it suggests to split calculations in small functions, and we used this before, but for some reason, we decided to use our own solution with fx/merge where we merge all small event functions in a big one, and this may lead to performance issues, for example, if you’ve received a lot of messages you need to filter them, sort etc, save in realm, all in one function, maybe we could find a compromise here and split it for example for such events like messages

Screens rendering
Currently, we calculate all screens even if they are not displayed, maybe we could find a way when we could keep them in react but don’t calculate subscriptions and render functions for them

PS: 5 steps above happen for each event, for example when you type a text in the input field, for each char we calculate all subscriptions and views renderers
image

11 Likes

I am not sure about this, subscriptions that take other subscriptions as input should only render if those subscriptions have changed afaik

that would be a nice effort, similar to events, I would be in favor of putting all subscriptions to first level keys in app-db in one file and use them as input for other more complex subscriptions.
such an effort would also include diagnosing poor design of app-db with top level keys that should rather be nested to reduce amount of recomputations

I disagree with splitting events, more on this in response to your other paragraph.

while I agree with this, the simplest and more efficient solution might actually end up being the current effort to move chat protocols to status-go. if status-go stores messages and returns them per chat and in order with a comprehensive API we could avoid the most expensive computations on status-react side.

already commented above, I think we are thinking roughly about the same thing

strongly disagree with that statement, fx/merge is about

  • composability
  • transactional events which cancels out if an error occurs instead of partially changing app state as it would happen with a small chain of events, they also avoid unnecessary recomputation of subscriptions.
  • the fx/merge actually often do little work and are mostly about returning a map of effects, which are going to do the actual work. for instance saving in realm is done outside of the event.

Keep in mind that there is overhead to dispatching events, events dispatch within another event only run next tick, but those dispatched at the same time all render during the same tick. After events are computed there is a check to app-db. fx/merged is saving a lot of time by merging changes to realm as well.

Now sorting and filtering the messages especially in big chats is a big deal and not really related to fx/merge, it will take 99% of the computation time regardless of how much fx/defn are merged within the event. So the actual effort should rather be put into finding a solution to that problem, either by offloading this work to another thread (but it has its own complications), using status-go for that, or putting more effort into alternative datastructures such as the ordered persistent hashmap which could be leveraged for more caching and less sorting.

(parenthesis re-frisk really doesn’t like custom datastructures and my repl is often wrecked by millions of lines of exceptions such as

                       java.util.concurrent.ThreadPoolExecutor$Worker.run  ThreadPoolExecutor.java:  624
                        java.util.concurrent.ThreadPoolExecutor.runWorker  ThreadPoolExecutor.java: 1149
                                                                      ...                               
                 clojure.core.async.impl.channels.ManyToManyChannel/fn/fn             channels.clj:   95
                                         clojure.core.async/do-alts/fn/fn                async.clj:  252
                                          clojure.core.async/ioc-alts!/fn                async.clj:  383
             clojure.core.async.impl.ioc-macros/run-state-machine-wrapped           ioc_macros.clj:  977
                     clojure.core.async.impl.ioc-macros/run-state-machine           ioc_macros.clj:  973
              taoensso.sente/-start-chsk-router!/fn/state-machine--auto--               sente.cljc: 1529
           taoensso.sente/-start-chsk-router!/fn/state-machine--auto--/fn               sente.cljc: 1529
                                    taoensso.sente/-start-chsk-router!/fn               sente.cljc: 1527
taoensso.sente/-start-chsk-router!/fn/state-machine--auto--/fn/inst-17988               sente.cljc: 1541
                                  re-frisk-sidecar.core/event-msg-handler                 core.clj:   70
                                                                      ...                               
                                       re-frisk-sidecar.core/eval18744/fn                 core.clj:  111
                                                       clojure.core/swap!                 core.clj: 2345
                                                                      ...                               
                                                     re-frisk.delta/apply               delta.cljc:   94
                                                 re-frisk.delta/apply-map               delta.cljc:   84
                                         re-frisk.delta/apply-map-changes               delta.cljc:   80
                                                      clojure.core/reduce                 core.clj: 6748
                                              clojure.core.protocols/fn/G            protocols.clj:   13
                                                clojure.core.protocols/fn            protocols.clj:   75
                                       clojure.core.protocols/iter-reduce            protocols.clj:   49
                                      re-frisk.delta/apply-map-changes/fn               delta.cljc:   80
                                                                      ...                               
                                                   clojure.core/update-in                 core.clj: 6092
                                                   clojure.core/update-in                 core.clj: 6106
                                                clojure.core/update-in/up                 core.clj: 6105
                                                       clojure.core/apply                 core.clj:  659
                                                                      ...                               
                                   re-frisk.delta/apply-map-changes/fn/fn               delta.cljc:   80
                                                     re-frisk.delta/apply               delta.cljc:   94
                                                 re-frisk.delta/apply-map               delta.cljc:   84
                                         re-frisk.delta/apply-map-changes               delta.cljc:   80
                                                      clojure.core/reduce                 core.clj: 6748
                                              clojure.core.protocols/fn/G            protocols.clj:   13
                                                clojure.core.protocols/fn            protocols.clj:   75
                                       clojure.core.protocols/iter-reduce            protocols.clj:   49
                                      re-frisk.delta/apply-map-changes/fn               delta.cljc:   80
                                                                      ...                               
                                                   clojure.core/update-in                 core.clj: 6092
                                                   clojure.core/update-in                 core.clj: 6106
                                                clojure.core/update-in/up                 core.clj: 6105
                                                       clojure.core/apply                 core.clj:  659
                                                                      ...                               
                                   re-frisk.delta/apply-map-changes/fn/fn               delta.cljc:   80
                                                     re-frisk.delta/apply               delta.cljc:   94
                                                 re-frisk.delta/apply-map               delta.cljc:   84
                                         re-frisk.delta/apply-map-changes               delta.cljc:   80
                                                      clojure.core/reduce                 core.clj: 6748
                                              clojure.core.protocols/fn/G            protocols.clj:   13
                                                clojure.core.protocols/fn            protocols.clj:   75
                                       clojure.core.protocols/iter-reduce            protocols.clj:   49
                                      re-frisk.delta/apply-map-changes/fn               delta.cljc:   80
                                                                      ...                               
                                                   clojure.core/update-in                 core.clj: 6092
                                                   clojure.core/update-in                 core.clj: 6106
                                                clojure.core/update-in/up                 core.clj: 6105
                                                       clojure.core/apply                 core.clj:  659
                                                                      ...                               
                                   re-frisk.delta/apply-map-changes/fn/fn               delta.cljc:   80
                                                     re-frisk.delta/apply               delta.cljc:   94
                                                 re-frisk.delta/apply-map               delta.cljc:   85
                                                                      ...                               
                                                       clojure.core/merge                 core.clj: 3033
                                                       clojure.core/merge                 core.clj: 3040
                                                     clojure.core/reduce1                 core.clj:  926
                                                     clojure.core/reduce1                 core.clj:  936
                                                    clojure.core/merge/fn                 core.clj: 3041
                                                        clojure.core/conj                 core.clj:   85
                                                                      ...                               
java.lang.IllegalArgumentException: Don't know how to create ISeq from: com.cognitect.transit.impl.TaggedValueImpl

I think relying more on local state especially for input fields would already save us a lot of subscriptions and events, we can pass a validation function for those fields that require some validation, it doesn’t have to be done through an event

Overall I would be very much in favor of having only one subscription per screen, which is composed of subscriptions with a focus on minimizing recomputations

2 Likes

thanks Eric for your answer

yes, i wrote it not exactly how i meant, but anyway i tested it with 10x, and it’s from ~60 to ~100 subscriptions by one event are run when many screens are active, because of react-navigation when you leave the screen, subscriptions remain active for it

that’s true, but maybe we could find a way how to run them in a separate tick, just as a low hanging temporary solution, for example split them and dispatch-later

i’ll try to find a time once again to look at that

that would be great!

1 Like

We went through the excruciatingly difficult decision to ditch React Native and move to Swift and Kotlin code bases.

I cannot tell you how much I despise React Native. You cannot even construct a performant table view with 100 cells.

I wish you all the best as you try to speed up Status.

5 Likes

Are they actually computed or just active? Also just like events some subs are fairly quick to compute. For instance if you just patch together a bunch of other subs it’s just going to be a few assoc

I’m wondering if this is more an issue in how react-native is being used?

As someone who has mostly used react-dom, I know this can be an issue where rendering cells for all available data will lead to poor performance over just rendering the cells that come into view. There are solutions to deal with this issue like http://www.reactvirtualized.com/

Have you tried optimizing react’s rendering before migrating away?

We should really get some data on the performance impact of subscriptions vs input params in child components

Here is a comparison I would like to try to verify some assumptions about subscriptions costs:

  • one huge chat subscription which includes the list of all messages down to author related informations and have all the data flowing down to child components (almost what we have now, our current implementation is more hybrid but would do the trick for now)
  • one minimalist chat subscription with a vector of message ids, each message component subscribing to a parametrized subscription that gets the data for the message, author component itself uses its own parametrized subscription.

Difficulty: find the proper way to measure perfs, rendering vs subscription computations

Assumptions:

  • In the first solution we might save a lot of subscriptions but initial subscription might be expensive. Changes in inputs might trigger lots of recomputations. Parents components would rerender when the child changes but this might actually be negligeable.
  • In the second solution there will be a lot of subscriptions but they might not be so expensive and rarely recalculated, also the subscriptions would only be computed when the messages are actually rendered so it might save lots of computations in big chats especially when scrolling is up, where the first solution recomputes everything.
  • having lots of subscriptions takes space but can save lots of time especially if they are rarely recomputed

The chat screen is definitely the place to experiment as it is were the most expensive operations occurs. While I would expect the one big subscription model to be best for other screens, I think in that case the small parametrized subscriptions might be better.

1 Like

This is what happens when user presses wallet tab , times maybe incorrect because it’s develop build and we have overhead for tracing tools, but this trace is for an empty account, for different conditions times might be different, for example if account has a lot of chat, messages , tokens etc, subscriptions calculation may take much more time

:arrow_forward: :event :navigate-to :wallet-stack 65.0 ms
:arrow_forward: :event/handler :wallet-stack 1.0 ms
:arrow_forward: :event/do-fx 44.0 ms
:arrow_forward: :sub/run :…ad-messages-number 0.0 ms
:arrow_forward: :sub/run :…/pending-requests 0.0 ms
:arrow_forward: :sub/run :get :accounts/login 0.0 ms
:arrow_forward: :sub/run :…/settings 1.0 ms
:arrow_forward: :sub/run :get :…/advanced? 0.0 ms
:arrow_forward: :sub/run :get :chats/loading? 0.0 ms
:arrow_forward: :sub/run :…settings/currency 1.0 ms
:arrow_forward: :sub/run :search/filter 0.0 ms
:arrow_forward: :sub/run :wallet 0.0 ms
:arrow_forward: :sub/run :…subs/contacts 0.0 ms
:arrow_forward: :sub/run :…chat.subs/chats 1.0 ms
:arrow_forward: :sub/run :account/account 0.0 ms
:arrow_forward: :sub/run :chain-sync-state 0.0 ms
:arrow_forward: :sub/run :latest-block-number 0.0 ms
:arrow_forward: :sub/run :…-network-uses-rpc? 0.0 ms
:arrow_forward: :sub/run :wallet/all-tokens 0.0 ms
:arrow_forward: :sub/run :…twork-initialized? 0.0 ms
:arrow_forward: :sub/run :prices 0.0 ms
:arrow_forward: :sub/run :peers-count 0.0 ms
:arrow_forward: :sub/run :dimensions/window 1.0 ms
:arrow_forward: :sub/run :mailserver/state 0.0 ms
:arrow_forward: :sub/run :…/request-error? 0.0 ms
:arrow_forward: :sub/run :get :view-id 0.0 ms
:arrow_forward: :sub/run :get :network/type 0.0 ms
:arrow_forward: :sub/run :get :…/editing? 0.0 ms
:arrow_forward: :sub/run :bottom-sheet 0.0 ms
:arrow_forward: :sub/run :get :account/account 0.0 ms
:arrow_forward: :sub/run :get :…/profile 0.0 ms
:arrow_forward: :sub/run :network-status 0.0 ms
:arrow_forward: :sub/run :get :…/profile 0.0 ms
:arrow_forward: :sub/run :sync-state 0.0 ms
:arrow_forward: :event :…ui/pull-to-refresh 72.0 ms
:arrow_forward: :event/handler 71.0 ms
:arrow_forward: :event/do-fx 0.0 ms
:arrow_forward: :event :update-wallet 20.0 ms
:arrow_forward: :event/handler 6.0 ms
:arrow_forward: :event/do-fx 7.0 ms
:arrow_forward: :event :…/on-will-focus :wallet 7.0 ms
:arrow_forward: :event/handler :wallet 1.0 ms
:arrow_forward: :event/do-fx 0.0 ms
:arrow_forward: :sub/run :…/pending-requests 0.0 ms
:arrow_forward: :sub/run :get :accounts/login 1.0 ms
:arrow_forward: :sub/run :…/settings 0.0 ms
:arrow_forward: :sub/run :…ad-messages-number 0.0 ms
:arrow_forward: :sub/run :get :…/advanced? 0.0 ms
:arrow_forward: :sub/run :get :chats/loading? 0.0 ms
:arrow_forward: :sub/run :…settings/currency 0.0 ms
:arrow_forward: :sub/run :search/filter 1.0 ms
:arrow_forward: :sub/run :wallet 0.0 ms
:arrow_forward: :sub/run :…subs/contacts 0.0 ms
:arrow_forward: :sub/run :…chat.subs/chats 0.0 ms
:arrow_forward: :sub/run :account/account 0.0 ms
:arrow_forward: :sub/run :chain-sync-state 0.0 ms
:arrow_forward: :sub/run :latest-block-number 0.0 ms
:arrow_forward: :sub/run :…-network-uses-rpc? 0.0 ms
:arrow_forward: :sub/run :wallet/all-tokens 0.0 ms
:arrow_forward: :sub/run :…twork-initialized? 0.0 ms
:arrow_forward: :sub/run :prices 0.0 ms
:arrow_forward: :sub/run :peers-count 0.0 ms
:arrow_forward: :sub/run :dimensions/window 1.0 ms
:arrow_forward: :sub/run :mailserver/state 0.0 ms
:arrow_forward: :sub/run :…/request-error? 0.0 ms
:arrow_forward: :sub/run :get :view-id 0.0 ms
:arrow_forward: :sub/run :get :network/type 0.0 ms
:arrow_forward: :sub/run :get :…/editing? 1.0 ms
:arrow_forward: :sub/run :bottom-sheet 0.0 ms
:arrow_forward: :sub/run :get :account/account 0.0 ms
:arrow_forward: :sub/run :get :…/profile 0.0 ms
:arrow_forward: :sub/run :network-status 0.0 ms
:arrow_forward: :sub/run :get :…/profile 1.0 ms
:arrow_forward: :sub/run :sync-state 0.0 ms
:arrow_forward: :event :…ate-prices-success {[…], […]} 3.0 ms
:arrow_forward: :event/handler {:SNT {:…171.85}}} 0.0 ms
:arrow_forward: :event/do-fx 1.0 ms
:arrow_forward: :sub/run :…/pending-requests 1.0 ms
:arrow_forward: :sub/run :get :accounts/login 0.0 ms
:arrow_forward: :sub/run :…/settings 0.0 ms
:arrow_forward: :sub/run :…ad-messages-number 0.0 ms
:arrow_forward: :sub/run :get :…/advanced? 0.0 ms
:arrow_forward: :sub/run :get :chats/loading? 0.0 ms
:arrow_forward: :sub/run :…settings/currency 0.0 ms
:arrow_forward: :sub/run :search/filter 0.0 ms
:arrow_forward: :sub/run :wallet 0.0 ms
:arrow_forward: :sub/run :…subs/contacts 0.0 ms
:arrow_forward: :sub/run :…chat.subs/chats 0.0 ms
:arrow_forward: :sub/run :account/account 0.0 ms
:arrow_forward: :sub/run :chain-sync-state 0.0 ms
:arrow_forward: :sub/run :latest-block-number 0.0 ms
:arrow_forward: :sub/run :…-network-uses-rpc? 0.0 ms
:arrow_forward: :sub/run :wallet/all-tokens 0.0 ms
:arrow_forward: :sub/run :…twork-initialized? 0.0 ms
:arrow_forward: :sub/run :prices 0.0 ms
:arrow_forward: :sub/run :peers-count 1.0 ms
:arrow_forward: :sub/run :dimensions/window 0.0 ms
:arrow_forward: :sub/run :mailserver/state 0.0 ms
:arrow_forward: :sub/run :…/request-error? 0.0 ms
:arrow_forward: :sub/run :get :view-id 0.0 ms
:arrow_forward: :sub/run :get :network/type 0.0 ms
:arrow_forward: :sub/run :get :…/editing? 0.0 ms
:arrow_forward: :sub/run :bottom-sheet 0.0 ms
:arrow_forward: :sub/run :get :account/account 0.0 ms
:arrow_forward: :sub/run :get :…/profile 0.0 ms
:arrow_forward: :sub/run :network-status 0.0 ms
:arrow_forward: :sub/run :get :…/profile 0.0 ms
:arrow_forward: :sub/run :sync-state 0.0 ms
:sub/run :portfolio-value 1.0 ms
2 Likes

This is really helpful! Given the pull-to-refresh I would assume this relates to requesting fiat value. @andmironov can you look into a loading state for the wallet fiat amounts or wallet as a whole? That way we can seperate delay in rendering from delay in navigation.

2 Likes

I’m pretty sure FlatList in RN renders only the items it needs to render, and I feel like the chat view is the least of our problems right now. At least I didn’t notice that when I was profiling.

The problem I saw is the clogged JS thread, mostly when a lot of messages arrive. In a native case you just offload this asynchronously to a background thread and deal with the results, but there isn’t an option like that for pure JS implementations.

Even if you make every JS call super efficient, there still will be a threshold at which the JS thread will be clogged. But it needs to be free, because it also dispatches all the events from touches, etc.

So, one of the ideas of fixing that part is to move all the processing of messages to status-go and to only return necessary pre-processed messages window (maybe 100 at a time) that we show. This way we will keep all the message processing, persisting, etc to the background.

There’s already an initiative on that by @adamb, and I’m planning to take it over when he is on vacation.

Btw, @flexsurfer, @yenda I want to discuss that with you :smiley:

4 Likes

Just had call with @anna @igor @andrey to discuss how Core UI should take performance into account as we build new screens and features.

Two main things came out of that:

  1. Per Igor’s post, moving some things to status-go will potentially affect new feature implementation. The Core UI team can build accordingly with @andrey’s leadership.
  2. Determining performance metrics will help the test team enforce relevant acceptance criteria.
    A bottoms-up approach to this is to define a set of critical flows, break them out into steps (e.g. request made, awaiting response, response received), measure how long they take under various conditions, and repeat after improvements. We can then set benchmarks and compare performance across builds. Anna will take the lead on defining critical flows and metrics with Igor.

Unedited call notes

4 Likes

You could use web workers in JS for background threads. GitHub - devfd/react-native-workers: Background services and web workers for react-native is an example library I just found but there maybe something better. Before going down the path of multi process I would confirm the main thread is indeed the issue and the way its calls are being made are correct (IO bound calls should not be blocking) as multi proc comes with it’s own complexity. WatermelonDB (GitHub - Nozbe/WatermelonDB: 🍉 Reactive & asynchronous database for powerful React and React Native apps ⚡️) is an example of using workers in JS specifically for localDB reads and writes to take pressure off the main thread.

3 Likes