5

So, I was reading this: http://www.ibm.com/developerworks/java/library/j-jtp09275/index.html which says, "Public service announcement: Object pooling is now a serious performance loss for all but the most heavyweight of objects, and even then it is tricky to get right without introducing concurrency bottlenecks," and took it at face value. The article talks about generational GC, deallocation, thread-local allocation and escape analysis.

However, I just had a little voice in my head ask me, "But is this true of the garbage collector implementation in Android?" and I don't know the answer. I wouldn't even know how to go about finding the answer.

I remember having the GC run less often in my android apps when I implemented pooling for small objects that were used often, though. Not sure if that means a faster app.. Also, GC ran more often without pooling (according to logcat), so I assume Android's implementation of the GC loses to pooling.. But this assumption has very little backing because I didn't notice any significant performance difference with or without pooling.

So.. Anyone here know if pooling is more efficient than Android's GC for small objects used often?

Justin AnyhowStep
  • 1,130
  • 3
  • 12
  • 19
  • It is worth noting that "now" is from an article dated as 2005 – SJuan76 Jun 13 '13 at 22:51
  • It's not really an even comparison between handheld client software where you can place clear constraints on resource needs, vs application server software that needs to support an arbitrarily large number of threads/nodes competing for the pooled resource. – Affe Jun 13 '13 at 23:00
  • @SJuan76 Quite true. What's your experience with the GC, though? I've only ever made small, casual games for Android and never got to a point where I needed optimized code but I figured it'd be good to know if I should pool or not. – Justin AnyhowStep Jun 13 '13 at 23:02
  • @Affe I don't quite get your point. Although, if I needed to support multiple threads, I'd give them each their own pool.. I'm just asking about Android's GC, not comparing client and server resource needs.. – Justin AnyhowStep Jun 13 '13 at 23:05
  • It isn't as it if I have a strong opinion (did little as Android developer); I mostly warned about the date because when I first read your OP it felt like it was a new issue and (for the sake of information) that detail is important. I would say that you should avoid pooling, pooling means that you increase memory usage to decrease execution time. Since I do not expect a mobile app to create heavy objects, I would go without it. And remember that premature optimization is the origin of 89% of all evil. So, unless there is a clear reason, no pooling. – SJuan76 Jun 13 '13 at 23:06
  • Yeah, I agree with you about premature optimizations but I asked because it gives me an uneasy feeling to see so many allocations without knowing if they go on the heap or stack or the mechanics behind these allocations and deallocations. It's good to just get it working and care about optimizations later if it ever becomes an issue but I just had to know how Android's GC handles it. A curiosity or something. – Justin AnyhowStep Jun 13 '13 at 23:17
  • Check out the Message class for a good example of something Google deemed pool-worthy and their implementation of it. https://developer.android.com/reference/android/os/Message.html https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/core/java/android/os/Message.java – Krylez Jun 13 '13 at 23:22
  • @Krylez I've never used the Message class so I had to read up a little on what it was about. Looks like it's used by apps to talk to other apps(?). I read that Android's GC suspends program execution when running from here: http://stackoverflow.com/questions/4818869/technical-details-of-android-garbage-collector Maybe they didn't want GC to fire and cause a delay between inter-process communication? But Tim Bender, below, said that it fires anyway (just less often) and could possibly take longer to go through those objects.. I am so confused now =/ – Justin AnyhowStep Jun 13 '13 at 23:45
  • Handlers (and Messages) are used to communicate across threads within an app, so they're used _everywhere_ in the core OS. You generally don't need to implement pooling for your own objects, but there are appropriate cases. – Krylez Jun 13 '13 at 23:51

2 Answers2

5

Anyone here know if pooling is more efficient than Android's GC for small objects used often?

That depends on how you measure "efficient", "small", and "often".

Object pooling is used in several places within Android itself, such as:

  • the whole Adapter framework for AdapterView (ListView and kin) is designed around object pools, this time for relatively heavyweight objects (e.g., a ListView row can easily be tens of KB)

  • SensorEvent objects are recycled, this time for lightweight objects used potentially dozens of times per second

  • AttributeSet objects are recycled, as part of View inflation

and so on.

Some of that was based on early versions of Dalvik in early versions of Android, when we were aiming at under 100MHz CPUs with a purely interpreted language and a fairly naïve GC engine.

However, even today, object pooling has one big advantage beyond immediate performance: heap fragmentation.

Java's GC engine is a compacting garbage collector, meaning that contiguous blocks of free heap space are combined into much larger blocks. Dalvik's GC engine is a non-compacting garbage collector, meaning that a block that you allocate will never become part of a larger block. This is where many developers get screwed with bitmap management -- the OutOfMemoryError they get is not because the heap is out of room, but that the heap has no block big enough for the desired allocation, due to heap fragmentation.

Object pools avoid heap fragmentation, simply by preventing the pooled objects from getting garbage collected again and not allocating new objects for the pool very often (only if the pool needs to grow due to too much simultaneous use).

Game developers have long used object pooling in Android, stemming from back when Android's garbage collection was non-concurrent, "stopping the world" when GC was conducted. Now, most Android devices uses a concurrent garbage collector, which eases the pain here some.

So, object pooling is definitely still a relevant technique. Mostly, though, I'd view it as something to employ as a reaction to a detected problem (e.g., Traceview showing too much time in GC, Traceview showing too much time in object constructors, MAT showing that you have plenty of heap but you get OutOfMemoryErrors). The exception would be game development -- game developers probably have their own heuristics for when pooling is still needed with modern Android devices.

CommonsWare
  • 986,068
  • 189
  • 2,389
  • 2,491
  • Awesome answer, thanks! Do I assume pooling is more important for larger objects, then? Since larger objects require more contiguous memory, pooling them at the start and then letting smaller objects get allocated and deallocated all over the place should be fine, right? I mean, all the allocation and deallocation will fragment the heap but if all that's ever allocated is small objects, this fragmentation would have to be really bad to not allow even small objects to be allocated (I hope that logic's sound). – Justin AnyhowStep Jun 13 '13 at 23:57
  • @JustinAnyhowStep Objects get copied around and get compacted to reduce fragmentation. Object pools are more useful if you want to avoid pressure on the GC and have less collections, if that is not a concern they can add complexity to your code you don't need. If in doubt, I would stick with what you believe is the simplest option. – Peter Lawrey Jun 14 '13 at 00:33
  • *"Object pools avoid heap fragmentation ..."* - That depends on how you populate the object pool. For instance, if the pool objects are created lazily, or if non-pool objects created later get semi-permanently "attached" to objects in the pool, you've potentially got fragmentation again. The only *real* cure for fragmentation is for Android to implement a compacting garbage collector. – Stephen C Jun 14 '13 at 04:29
  • @StephenC: "The only real cure for fragmentation is for Android to implement a compacting garbage collector" -- no question. – CommonsWare Jun 14 '13 at 10:32
  • @JustinAnyhowStep: "Since larger objects require more contiguous memory, pooling them at the start and then letting smaller objects get allocated and deallocated all over the place should be fine, right?" -- larger *allocations* require more contiguous space. An "object" is usually made up of references to other objects. So, a row in a `ListView` is expensive because the sum of all its widgets' and containers' sizes is large, but pooling here is done more for CPU (e.g., avoid jank while scrolling). For heap fragmentation, the biggest thing is reusing bitmaps, as bitmaps are large blocks. – CommonsWare Jun 14 '13 at 10:35
2

There is a fallacy in your reasoning. The GC running more frequently does not indicate some sort of diminished performance. Those more frequent GC runs could also be much faster and shorter lived than the less frequent ones that have to muddle through the object pool.

That said, I did some research and here are some thoughts... A couple years ago, my mobile phone had a single core. Running the GC meant switching from the activity to the GC thread. Even with concurrent GC and multiple cores (modern devices have 2-5 afaik), there could be slight pauses.

Pre-allocating everything that the user might need for the next sequence of interactions is suggested as a good idea for games. Essentially following the mantra of real-time applications which are less worried about overall performance as compared to having consistent measurable performance during the User Experience portion of the application.

http://developer.android.com/training/articles/perf-tips.html

Tim Bender
  • 20,112
  • 2
  • 49
  • 58
  • I never thought about it that way, I wish I'd remembered to look at those 'ms' values that logcat was telling me about. – Justin AnyhowStep Jun 13 '13 at 23:13
  • No, no, no. Not sarcasm. I really didn't think about it that way =/ I was always told that GC equals bad so I didn't think to scrutinize that. No, I don't have the logs; I never saved them. – Justin AnyhowStep Jun 13 '13 at 23:35
  • Thanks for the help! I think I'll leave my code as it is until I encounter hiccups, I guess. I'm worried because I'm using a Vector2 class for math all over my code base and changing it later would take more time than just dealing with the issue now. The last time I tried to use a pool of Vector2 objects, the code was barely readable because I had to always pre-allocate Vector2 instances before using them for vector math (which was used often) and I had to deallocate them afterwards; this added a lot of code that added no 'meaning' and clouded the real intent of the code (in my opinion). – Justin AnyhowStep Jun 14 '13 at 07:12