Some days ago cyrilmottier posted a blog post about ListView's tip and tricks.
In one of his tips he mentions the following:
CharArrayBuffer: Developers are regularly using thegetString()
method on theCursor
. Unfortunately, it implies creatingString
objects. These objects are likely to be garbaged once the user starts scrolling. In order to prevent object creation and consequently garbage collection freezes, it is possible to use aCharArrayBuffer
that consists on copying data from theCursor
to a raw array ofchar
that will be directly used by ourTextView
s. Instead of having to create aString
object, we will hence reuse aCharArrayBuffer
.
My first thought was: Oh my! I am using getString() everywhere! We had some performance issues in the past with a Gallery so it was a great place to start fixing the code.
Yesterday evening I modified the adapter's code to use CharArrayBuffer instead of getString(). When I tested the app it felt slower than before and very blocky. I went to sleep.
Traceview:
Today evening I decided starting from the scratch, but instead of using my app I decided to create a toy app. I stole some code from @vogella and start testing.
You can download the app from this link.
My first test was with the traceview tool:
In the screenshot you see two instances of the traceview tool analyzing the bindView method.
Over the top, you can see
withoutFix.trace (the one using the cursor.getString() method) and below the withFix.trace (which is using the cursor.copyStringToBuffer).Another thing I took some time trying to understand is this:
Conclusions so far:
* Using cursor.copyStringToBuffer() seems to run the bindView method faster than cursor.getString().
* cursor.getString() is faster than cursor.copyStringToBuffer()
The difference is not much so I wouldn't take care of keeping the fix. The code looks clearer with the getString() method.
MAT:
But wait, let's give it a try to the Memory Analyzer (MAT).
Note: After hearing @dubroy's talk at the googleIO 2011 I wanted to do something like this :)
@dubroy also wrote an article for the Android Developers blog called "Memory Analysis for Android".
Objects #0 and Shallow Heap #0 are for the cursor.getString() version and
Objects #1 and Shallow Heap #1 are for the cursor.copyStringToBuffer().
Not much to say here.
* The getString() versions has less String objects.
* The copyStringToBuffer uses more heap. (I use three new CharArrayBuffer(128))
Final conclusion:
I am not an expert using this tools but, from what I have tested, I couldn't notice a performance improvement when using cursor.copyStringToBuffer().
What do you guys think?
UPDATE:
I had the following conversation with cyrilmottier over twitter:
cyrilmottier: The actual benefit is you will reduce garbage collection to a minimum. This is less useful in Android 2.3 or higher.
2.3 introduced a generational GC. Hence, CharArrayBuffer is probably more useful on old Android versions.
me: It makes sense. I will keep the getString() calls, knowing that I have the buffer if I need to improve memory usage.
Sounds like a good analysis. As it had been said, pre-mature optimization is the root of all evil... :-)
ReplyDeleteIn order to share my point of view, I prefer posting it here.
ReplyDeleteThe actual benefit of this optimization is too minimize garbage collection which may block the UI. In Android 2.3 a generational GC was introduced and made this optimization less necessary. However if you are targeting old Android you may use CharArrauBuffers to prevent UI from being freezed.
As Lars Vogel said it may be considered as a "pre-mature optimization". Personally I think this technique is very simple to use and can benefit a lot to users (especially in long ListView like you can find in a 'Contact' or 'Music' application. That's why I prefer using it in my applications ...
Cyril:
ReplyDeleteThe problem I noticed is that when I replaced every getString() call with the copyStringToBuffer() my AdapterView started to go slower.
So in android >2.3 it might not be a benefit and endup doing more work just to run slower!