Why is my mobile app slow???
I have been part of many hybrid verses native mobile app performance discussions over the last 6 months. In my experience as being a developer of native mobile apps (iOS/Android & WP) in the past and lately in the hybrid space (Cordova with Kendo or Ionic) I’m slowly learning that the client side implementation doesn’t have a lot to do with performance of a mobile app in the ‘grand scheme of things’. What can affect performance of either platform, and in my option matters more than your client side technology, are your services.
In my experience the easiest and usually the most important performance gain you can make is optimizing your services and your client to talk to those services. If your used to developing web sites, internal/corporate apps and the like chances are you’ve never thought of API calls as expensive, “Dude I’m using WebAPI and JSON, I’m set”.
Below we are going to talk about your mobile app to your mobile services. Lets assume that your mobile services and everything behind them are well tuned and performant.
Below are some key points I’ve noticed when mobile apps talk to remote services.
Data Size
1 ANSI character = 8 bits, a 3G connection transfers of at least 200 kbit/s which means to transfer 1 ANSI character takes just under 1/2 a millisecond, not including TCP/Network overhead, latency, etc. It doesn’t sound like much, 1/2 a millisecond really, but it adds up. How much data are you sending over the wire, when you don’t need to? When looking at some of our JSON payloads we found that that in the worst case scenarios we could be adding 1/2 a second in data that didn’t need to be on the wire.
Take a look at this data model, pretty standard in the .Net space. ActionTypes and UserStateTypes are simple Integer enumerations.
public class PersonnelStatusResult { public string UserId { get; set; } public string Name { get; set; } public ActionTypes ActionType { get; set; } public DateTime ActionTimestamp { get; set; } public string DestinationId { get; set; } public string DestinationName { get; set; } public UserStateTypes StateType { get; set; } public DateTime StateTimestamp { get; set; } }
So how does this look on the wire:
{ "UserId": "f3a48991-3e5e-4f4c-ab3d-5e7a43a428e3", "Name": "Shawn Jackson", "ActionType": "StandingBy", "ActionTimestamp": "2014-11-26T14:46:16.517", "DestinationId": null, "DestinationName": null, "StateType": "Unavailable", "StateTimestamp": "2014-12-01T12:24:52.047" }
As you can see the JSON serializer used the String values of our enumeration, this instead of transmitting integer values we have long strings. Also for this request I’m sending back the users name, this is something that will rarely change, and probably shouldn’t be send with every request. Also although the names for the parameters are nice it cost of 4 milliseconds of 3G wire time to transmit them.
I’ve recoded the service to send less data on every request. Destination Names (which rarely change) along with users name’s are now loaded up and cached locally on the device.
public class PersonnelStatusResult { public string Uid { get; set; } public int Atp { get; set; } public DateTime Atm { get; set; } public string Did { get; set; } public int Ste { get; set; } public DateTime Stm { get; set; } }
Now lets see what the new data looks like over the wire.
{ "Uid":"db27e74b-8f00-43c7-bce4-fd72a2c8d28b", "Atp":0, "Atm":"2014-11-26T14:46:16.517", "Did":null, "Ste":2, "Stm":"2014-12-01T12:24:52.047" }
Much more concise and very little wasted data. The delta between the first request and the 2nd one over a 3G connection is 5 milliseconds. The above data is in a list, with 100 items in the list I’ve saved 1/2 a second in unneeded transmission time.
The serialization mechanism used is also very important. You should not be using something verbose like XML/SOAP to communicate with your apps, instead utilize JSON or Protobuf. For any serialization mechanism you need to think about the serialization/deserialization speed but you still need to think about the syntax of it on the wire.
Data Types
As seen in the example above I’m using Guid’s for user’s id value. Each Guid will cost me almost 2 milliseconds of 3G network time. Although I cannot change that data type now, I need to ensure that all data that goes across the wire is as compact as possible, i cialisviagras.net.e. utilizing Integers for Id’s instead of Guids.
Network
Never assume your users are on a great WiFi connection, LTE or 4G. There are A LOT of 3G connections out there and congestion in populated areas could easily kill your data transfer speeds of better networks. Just because your lucky enough to be on a 4G network doesn’t mean it’s fast, you need to think about latency, see the table below.
Generation | Data rate | Latency 2G | 100–400 Kbit/s | 300–1000 ms 3G | 0.5–5 Mbit/s | 100–500 ms 4G | 1–50 Mbit/s | < 100 ms
Even on a 4G network your latency could be 1/10’th of a second. This is without any other issues like, retries timeouts, dropped packets, etc.
Overhead
TCP overhead matters. Every time you make a data call from your mobile app to an external service that goes out over the wire. Your TCP header is normally 20 Bytes but can be up to 60 depending on what options are being set. Then your IPv4 header is another 20 Bytes. That means for every connection you have 40Bytes of overhead occurring in the best case scenario. That means that 2 milliseconds are spent with every call over a 3G connection with TCP/IP overhead.
Conclusion
So below I have the following tips for service performance for mobile apps.
- Plan for the worst case but realistic network connection scenario. In the areas that my app works you can see Edge connections which are typically limited to 135 kbit/s. If you plan for the worst your app will perform just fine at better connections and speeds more typically seen.
- Don’t assume you’re going to be fast just because your on a 4G/LTE connection. If your doing connection based logic just determining the connection type isn’t enough. Latency, cell strength, congestion, etc can make a 4G or LTE connection just as slow as a 3G one.
- DRY (Don’t Repeat Yourself) and Cache your data on the device. Keep list calls and redundant data to a minimum.
- If you haven’t changed it in the last 3 months, chances are you wont! Don’t store configuration data on the server only to serve it up to the device every time. Keep it on the device or cache it.
- Don’t transmit or contact the server unless you have to. Every time you ‘leave process’ it’s a slow operation. Don’t do it unless you have it.
- For lists, utilize 1 call to bring back all the list data, then use that data to populate detail views. Unless the detail data is grossly larger then the data for the list it’s worth it to not make a detail view call.
So if you currently have a slow mobile app take a look at your services first and ensure they are as performant as possible before you go re-writing your app.