Two of the most persistent issues that I have to constantly address with Purple Robot are its performance and resource footprint. After over a year, I've continued to refine and modify the code to squeeze out more responsiveness and battery life. In the current Purple Robot code, I just committed some code that increased battery life 176%, decreased average CPU load by 65%, at the cost of increasing the average memory footprint from 31.7MB to 36.4MB (as measured on a Samsung Galaxy Nexus running Android 4.3).

How did I do it?

I've known for some time that Purple Robot's major performance bottleneck has been the original implementation of the code that handles sensor data, from its original collection to transmission to the server. Basically, this process unfolds as follows:

  1. A sensor generates a reading and broadcasts the reading (and other data) to the rest of the system using an Android broadcast.
  2. The HttpUploadPlugin receives the broadcast and generates a JSON object that it holds in memory. When a sufficient number of sensor broadcasts are collected, Purple Robot bundles them up into a single JSON object and writes the aggregate to disk.
  3. On another thread, Purple Robot periodically wakes up and determines if there is new data to transmit. (One or more files will be in a predetermined location.)
  4. If there's new data to upload, Purple Robot reads the JSON files and extracts a sufficient number of JSON objects to construct a "payload". The size of the ideal payload is either determined adaptively (from data collected from prior transmissions) or set in the app preferences.
  5. Purple Robot transmits the payload to the server and repeats the process. If no more data is available for upload, Purple Robot allows the current thread to complete and restarts the process at some time in the future (either determined adaptively or set in the preferences).

This has been a useful approach that minimizes the opportunity for data loss and attempts to scale the uploader activity with the user's available connection quality.

However, when I profiled Purple Robot to try and determine what various performance bottlenecks were, I found that the bulk of the samples that I collected were string manipulations related to JSON serialization and deserialization. With that in mind, I simplified the data collection and upload pipeline:

  1. A sensor generates a reading and broadcasts the reading (and other data) to the rest of the system using an Android broadcast.
  2. The new upload plugin receives the broadcast and converts the bundle to JSON. This JSON is added to an array already being created. If the expanded array exceeds a set size (256kB in this case), it's written to disk as a JSON string.
  3. On another thread, Purple Robot periodically wakes up and determines if there is new data to transmit. (One or more files will be in a predetermined location.) If there's new data to upload, Purple Robot reads the JSON payload directly from disk and uploads it to the server.
  4. Purple Robot transmits the payload to the server and repeats the process. If no more data is available for upload, Purple Robot allows the current thread to complete and restarts the process at some time in the future (either determined adaptively or set in the preferences).

This process is similar to the original one. However, it differs in that the sensor data is translated into a JSON string only once. That string is included verbatim in the upload document that's posted to the server. Data that once went through three JSON conversions now only goes through one. The other major change is that the JSON is processed using a streaming method rather than the popular in-memory tree method.

The tree-based method (using the org.json classes) reads a full JSON document in memory and operations such as reads and updates are done in-memory. When the manipulations are complete, the full in-memory structure is written out to disk. The streaming method uses an alternative set of classes that construct a JSON document on-disk through a series of events. At no time is the JSON full tree in memory. This is directly analogous to the differences between DOM and SAX parsers for XML.

Android introduced streaming JSON classes in Honeycomb. These allowed me to create a new streaming JSON uploader for newer devices. A third-party Java library called Jackson also provides very similar API that allows the streaming approach to be used on pre-3.0 devices. As of today, Purple Robot includes uploaders that use both classes for the purpose of a comparison.

So, how do these uploaders compare to each other?

Uploader Battery Drain
(seconds per %)
Avg. CPU Load Avg. Memory Usage
(PSS)
HttpUploadPlugin 211s 0.74 31.6MB
StreamingJSONUploadPlugin 345s 0.64 38.7MB
StreamingJacksonUploadPlugin 584s 0.25 36.4MB

These measures were collected on a Samsung Galaxy Nexus running Android 4.3. The accelerometer, magnetic field, and gyroscope probes were set to their highest frequencies (> 100Hz), and the quantities above were collected over a 30 minute period.

I was surprised to see that the original approach remained the most memory efficient (31.6MB vs. 38.7MB & 36.4MB). This may be explained by my (and Jackson's) use of buffered output classes that keep content in memory longer in order to facilitate more efficient writes (write a a large amount at once instead of many small writes). On the battery front, the streaming approaches dramatically decreased the battery usage. Using the tree-based approach, we could expect a runtime around 5.86 hours using this configuration. Using the Honeycomb classes, runtime grows to 9.58 hours, and the Jackson classes boost the runtime up to 16.22 hours. We see an expected decrease in CPU load, with the use of the Jackson classes providing the largest gains. (Note that these figures will vary based on other applications running on the device. A marathon session of Angry Birds will still drain your battery just as quickly.)

Since the Jackson-based uploader not only provides the largest performance gains, but also provides backward compatibility to pre-3.0 devices, this will become the new upload approach after some more testing. The results I measured were in line with other tests of JSON read/write performance.

I'm quite pleased with these results and look forward to deploying these enhancements in an upcoming version of Purple Robot.