Friday, July 3, 2015

Balancing Performance with Code Readability : Case Study (Android, Java)




Image result for code complexity
Google has a number of blog posts on how to write code for Android performance. They make recommendations that conflict with normal Java standards such as avoiding enumerated types and using counter-type  loops over their collection types. They make good arguments for comparative performance improvements for each, but I think it's good to step back and evaluate Google's motivations before blindly applying all their recommendations.


  • Motivation 1: Bad apps make Android look bad

    From Google's perspective, a few misbehaving apps can make the entire ecosystem look bad when compared to the highly controlled ecosystem of Apple. This is especially true for apps that are bad citizens (e.g.- consume more resources than needed for longer than needed, services that run longer than needed, activation of power-hungry resources like GPS hardware for longer than needed). Other recommendations are performance-oriented. There is little motivation for them to recommend well-written code over code that grabs every nanosecond of performance. They aren't the ones supporting the apps, after all.


  • Motivation 2: Wide range of hardware capabilities

    Often times developers will test only on a high-speed device over WiFi or 4G and not understand the implications of how their app will run on older or less expensive hardware or over slow Edge networks in China or elsewhere. With the advent of smart devices like watches, the limitations can be even more severe, especially for  battery life.

While I strongly endorse writing programs that are good citizens, other recommendations to grab every nanosecond of performance I often find as not always necessary. I also have the luxury of architecting, designing, and developing apps for specific high-end Android devices, which makes motivation 2 less relevant. Even if this were not the case, I still prefer to write first for readability and, only when proven necessary, refactor for performance.

An example of where I recently found this  useful was in creating code whose function is coordinating an external set of contacts with the Android native contact database. There were a number of fields that were to be synchronized and the list of exactly which would be synchronized was subject to change. I found code in another application that someone wrote to do something similar as a reference. The author of this code created a string projection  where each ordinal was referenced by number into that projection. Insertions or deletions of values in this projection would change the meaning of each ordinal, making the code very brittle.

I introduced an enumerated type for storing the elements of the Android database string projection and relied on the ordinal position for later retrieving columns from this projection. The structure was thus "self-aligned" making for easy removal and addition of elements to the projection. The enum looked something like this:


Access to the columns, could then use the ordinal of the enumerated values like so:
instead of using "magic numbers" which rely on the order of the elements in the projection (in this case it would have been "1").

The new self-aligned approach is both much more readable and less susceptible to bugs as the code changes and there was no noticeable performance hit.


3 comments:

  1. Hopefully, you'd wrap the cursor so you don't have a method letting you call:

    wrappedCursor.get(StringProjection.GIVEN_NAME)

    also, if you are going to use an enum this way, you should use this enum to build the query, so you know the indices will be correct - essentially, this class has a dependency on the query, so you really only want to use these indices with queries that match that signature. There are ways to enforce this to make it less likely that you will misuse the enums in places where it isn't appropriate, or to avoid somebody adding a column to the query and messing up your indices.

    ReplyDelete
    Replies
    1. The purpose of the statement assigning PROJECTION above is that I then use it in the query. This is key to its usage or the indices would be incorrect, as you state. This is all package protected and used in a single query so there is little opportunity for misuse.

      Delete
  2. Recently I saw a quantification of just how memory-hungry enums are on Android: http://goo.gl/38z0nw . Fortunately, the upside is that we use ProGuard which alleviates the problem.

    ReplyDelete