Synchronization Changes in 0.5.2


Synchronization Changes in 0.5.2

This information primarily applies only to dynamic plots;  static (unchanging) plots need not concern themselves with synchronization topics. When it comes to new development, the #1 concern for Androidplot is data integrity.  What is meant by "data integrity" is that when you look at a plot, the data you see is accurate.  Most commonly this is noticed as the accuracy of the values displayed on plot ticks relative the the contours of a plot.  A more subtle aspect is the relationship between each series of data that a plot draws.  For example if you are plotting jogging statistics for yourself and a friend over a period of time and you want to compare performance, you'll want to see that data properly aligned; if the relationship gets shifted to the left or right the information conveyed by the plot can drastically change. Let's take a brief look at how Androidplot works.  Androidplot allows multiple plots to be displayed on a single screen.  Each plot is composed of a number of series.  Plots can share some, all or none of the same series data.  Furthermore, each plot can be configured to do it's rendering in a background thread, introducing the possibility of concurrent read access to a series. Now let's focus on Series for a second.  At the end of the day, all Series is, is an interface that defines methods that a plot can use to retrieve data.  As a plot renders it's self it calls these methods to iterate over the series data, usually at least once per item stored in the series.  In terms of cpu time, this process is pretty slow and slow operations are vulnerable to race conditions if synchronization is not properly implemented. Imagine a plot dynamically displaying data coming from an accelerometer.  For the sake of this particular thought experiment let's also assume that there is a background thread retrieving samples from the accelerometer.  Each loop of the thread removes the oldest value from the beginning (removeFirst) of a SimpleXYSeries instance, appends the newly read value to the end (addLast) and invokes redraw() on the plot.  Now let's add some performance numbers to our experiment.  Pretend that our sensor thread is running at a frequency of 100hz and our plot redraw frequency is ~33hz.  This means that during the time it takes our plot to redraw once, the sensor has spit out 3 new values which have been added to SimpleXYSeries while 3 old values have been removed. Let's imagine that we invoke redraw() while our SimpleXYSeries contains the following y-vals: {1, 1, 2, 1, 1, 1, 2, 1, 1}  During this time the new values {3, 2, 4} are added to the end and the first 3 values are removed {1, 1, 2} leaving us with: {1, 1, 1, 2, 1, 1, 3, 2, 4 But because of the timing of each thread the race condition caused the plot to render the first 6 original values with the 3 new ending values at the end: {1, 1, 2, 1, 1, 1, 3, 2, 4} Not only is this not what the data looked like when we invoked render but it's not even what the data EVER looked like; it's just garbage.  This is especially dangerous because the random patterns produced in this manner appear realistic due to the fact that the pattern is composed of reasonable values as opposed to unexpectedly high or low values. Prior to 0.5.1, Androidplot provided a blocking version of redraw() which would be effectively used to avoid this scenario.  It came with a pretty hefty performance hit however and forced rendering to be done on the UI thread - something that should be avoided whenever possible and so in 0.5.1 we finally removed the blocking nature of redraw().  This effectively made it the end developer's duty to understand and properly implement synchronization.  The synchronization changes in 0.5.2 are intended to remove this requirement for the vast majority of use cases while providing a much simplified solution for those who require finer grain control. The big change we've made in 0.5.2 is to modify Plot to attempt to lock each series for reading before continuing with rendering.  Once a lock has been obtained plot will go ahead with the render.  It's important to use read/write locks so that multiple plots can render simultaneously while holding their respective read locks.  (Remember that more than one plot may be visible and a series may belong to more than one plot.)  To accomplish this we've modified Plot.addSeries(...) and Plot.removeSeries(...) to inspect the Series argument to see whether if it is an instance of PlotListener.  If it is, the series is added as a listener to the plot and it will begin to receive onBeforeDraw(...) and onAfterDraw(...) callbacks.  Concrete Series implementations concerned with synchronization should invoke locking and unlocking functionality from within these methods.  We've updated SimpleXYSeries with exactly this functionality.  Use it as a reference for your own implementations.  So to recap, in order to synchronize your custom Series implementations do the following:
  1. Modify your Series to implement PlotListener
  2. Add a read/write lock to your Series implementation.  We strongly suggest using ReentrantReadWriteLock.
  3. Obtain a read lock when your Plot's onBeforeDraw(...) method is called.
  4. Release a read lock when your Plot's onAfterDraw(...) method is called.
  5. Enclose any logic in your Series implementation that adds, removes or in any way modifies the data of that series between a write lock and a write unlock.
  6. Use SimpleXYSeries as a reference if you need an example of how to do any of the above. 

Leave a comment

Your email address will not be published. Required fields are marked *