RenderScript / TimingLogger

Blurring Images – Part 3

In the previous article we took a look at wiring up our blur method so that it was appended to the layout phase to ensure that it was called only following a layout change, and not in onDraw(). So why shouldn’t we call it in onDraw()? In this article we’ll perform some benchmarking which will help us to understand why.

part1There’s a really useful benchmarking utility class TimingLogger (which we have looked at before on Styling Android) which is really good for measuring performance and finding bottlenecks. Log is useful for debugging bit is not particularly well suited to performance monitoring because each log operation requires string formatting and I/O operations which can be quite heavy and potentially distort of results. TimingLogger is a little different because we initially create a TimingLogger object before entering the section of code that we want to measure, while the code that we’re measuring is running we call the addSplit() method of TimingLogger to create the log points that were interested in. The addSplit() method is extremely lightweight and does not actually output anything. To get some output we need to call the dumpToLog() method, which we do after we have completed the coffee that we’re measuring. The two heavy operations of TimingLogger object creation and formatting and writing to logcat are performed either side of the code being measured, and we only make the lightweight addSplit() calls during the critical section this reducing the chance of heavy logging operations skewing our performance results.

Let’s add some TimingLogger goodness to our blur method:

private static final String TAG = "Blurring";
private void blur(Bitmap bkg, View view, float radius) {
    TimingLogger tl = new TimingLogger(TAG, "blur");

    Bitmap overlay = Bitmap.createBitmap(
        view.getMeasuredWidth(), 
        view.getMeasuredHeight(), 
        Bitmap.Config.ARGB_8888);
    tl.addSplit("Bitmap.createBitmap()");
 
    Canvas canvas = new Canvas(overlay);
    tl.addSplit("new Canvas()");
 
    canvas.drawBitmap(bkg, -view.getLeft(), 
        -view.getTop(), null);
    tl.addSplit("canvas.drawBitmap()");
 
    RenderScript rs = RenderScript.create(this);
    tl.addSplit("RenderScript.create()");
 
    Allocation overlayAlloc = Allocation.createFromBitmap(
        rs, overlay);
    tl.addSplit("Allocation.createFromBitmap()");

    ScriptIntrinsicBlur blur = ScriptIntrinsicBlur.create(
        rs, overlayAlloc.getElement());
    tl.addSplit("ScriptIntrinsicBlur.create()");

    blur.setInput(overlayAlloc);
    tl.addSplit("blur.setInput()");

    blur.setRadius(radius);
    tl.addSplit("blur.setRadius()");
 
    blur.forEach(overlayAlloc);
    tl.addSplit("blur.forEach()");
 
    overlayAlloc.copyTo(overlay);
    tl.addSplit("overlayAlloc.copyTo()");
 
    view.setBackground(new BitmapDrawable(
        getResources(), overlay));
    tl.addSplit("view.setBackground()");

    rs.destroy();
    tl.addSplit("rs.destroy()");
    tl.dumpToLog();
}

If we run this we won’t actually get any output. The reason for this is one of the idiosyncrasies of TimingLogger – it is set to only output at Verbose logging level so we need to change the logging level for the particular tag that we’re using. We can do this using a simple adb command:

adb shell setprop log.tag.Blurring VERBOSE

Next we run our app again, and we can see the output from TimingLogger (This was run on a Nexus 5 running KitKat 4.4.2):

blur: begin
blur:      0 ms, Bitmap.createBitmap()
blur:      0 ms, new Canvas()
blur:      1 ms, canvas.drawBitmap()
blur:      4 ms, RenderScript.create()
blur:      11 ms, Allocation.createFromBitmap()
blur:      0 ms, ScriptIntrinsicBlur.create()
blur:      0 ms, blur.setInput()
blur:      0 ms, blur.setRadius()
blur:      0 ms, blur.forEach()
blur:      26 ms, overlayAlloc.copyTo()
blur:      0 ms, view.setBackground()
blur:      5 ms, rs.destroy()
blur: end, 47 ms

The total time here is 47ms which may not seen that long, but this is pretty much a best case scenario as the RenderScript compute operations will be executed on the Adreno 330 GPU which is part of the Snapdragon 800 SoC powering the Nexus 5. The real problem with this is that if we added this to onDraw() it is going to kill the framerate of any animations that we may need to run, including scrolling ListViews etc. The reason for this is that the blur operation alone will limit the frame rate to 20 frames per second (fps) because the blur operation for each frame will take 47ms. And that is before we take in to account the calculations for the animation, and the actual drawing operation – so the actual frame rate is likely to be much lower. If we then factor in lower spec hardware then it is going to be worse still.

Most video games aim for between 30-60 fps, so we’re not going to get smooth animations if we perform our blur in onDraw(). We certainly can’t in its current form, but later in this series we’ll look at how feasible it is to optimise our blur to perform better when animating.

Now that we can measure the performance of our blur, in the next article we’ll take a look at doing a blur in pure Java to see how it compares to RenderScript in terms of performance.

The source code for this article is available here.

© 2014, Mark Allison. All rights reserved.

Copyright © 2014 Styling Android. All Rights Reserved.
Information about how to reuse or republish this work may be available at http://blog.stylingandroid.com/license-information.

Leave a Reply

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

This site uses Akismet to reduce spam. Learn how your comment data is processed.