A Well Known but Forgotten Trick: Object Pooling
highscalability.com
highscalability.com
What killed it off was generational collection. Compacting generational collectors make allocation very cheap, and they make maintenance of short-lived objects very cheap. As a result, it's reliably cheaper to allocate an object and throw it away than it is to attempt to reuse it.
So it's surprising and interesting to read that the author of this article clearly thinks that there are still benefits to object pooling. Since Alex Petrov is a very sharp guy, i have to take him at his word. But it's a shame he didn't include benchmarks to compare it to idiomatic use of a modern GC.
I'm no Java hater, but that sounds like the wrong place to use Java in the first place
Of course, this totally depends on who might be touching the codebase...
The JVM is being used in Mt. Everest-sized workloads. Object pooling is a useful tool when architecting something huge and performance sensitive.
It's fairly moot for hard real-time programs though, as those typically completely eschew dynamic allocation (malloc can have unpredictable time too).
This happened because it has an event dispatcher where each event has a bunch of associated name/value keypairs. Even though most of the names are fixed ("SourceIP", "SourceProfile", "SessionUuid", etc.) the event system ends up strdup'ing all of them, each time. With GC we could simply ignore this. All the constant string names would just end up in a high gen, and the dynamic stuff would get cleaned in gen0, no additional code. (As-is, I'm looking at a fairly heavy rewrite, affecting thousands of callsites.)
Gen0 or young GC still involves a safepoint, a few trips to kernel scheduler, trashes the cpu instruction and data caches, possibly causes other promotions to tenured space (with knock on effects later), etc. It's no panacea when those tens/hundreds of millis are important.
I was just pointing out that GC can "help", by reducing complexity and enabling a team that otherwise might get mired in details to deliver something OK.
I can't really agree with that statement. One way to get to lower latency is to avoid using locks and rely on lock free algorithms.
Many of those are much easier to implement if you can rely on a GC, because the GC solves the problem that you can have objects that are still referenced in some thread, but that aren't reachable from the lock-free datastructure anymore. There are ways around this, e.g. using RCU or hazard pointers, but mostly it's easier with a GC.
Similarly, it makes it easier to amortise costs across multiple allocations/deallocations.
GC does have a bad rep in the hard real-time world, because in the worst case scenario, a poorly timed GC creates all kinds of trouble, which is why I mentioned that it helps if the allocator/deallocator is aware of hard real-time commits.
This only works if you enter a critical section with sufficient free heap. You could have just malloc()ed that space ahead of time if you weren't using a GC, so I don't see an improvement, just a convenience.
> Similarly, it makes it easier to amortise costs across multiple allocations/deallocations.
Amortizing costs is often the opposite of what you want to do to minimize latency; with hard real-time you care more about the worst-case than the average-case, and amortizing only helps the average-case (often at the expense of the worst-case)
> GC does have a bad rep in the hard real-time world, because in the worst case scenario, a poorly timed GC creates all kinds of trouble, which is why I mentioned that it helps if the allocator/deallocator is aware of hard real-time commits.
Yes, and GC can be made fully compatible with hard real-time systems; any incremental GC can be made fixed-cost with very little effort. It's somewhat moot since most hard real-time systems also want to never run out of heap, and the easiest way to do that is to never heap allocate after initialization, so most hard real-time systems don't use malloc() either.
Perhaps I am wrong and the technique is less familiar than I had thought?.
Woah, woah. No, object pooling is not for that case and definitely doesn't help with that. If you've re-used the object for something else and you've still got a reference to it - and you're updating it - you've got some seriously nasty bugs and randomly broken code.
Object pooling is not a 'must'. You need object pooling when you have a real time app and your total create/delete bandwidth exceeds the garbage collectors ability to process it in the 'early generation', forcing eventual 'stop the world' GC.
https://en.wikipedia.org/wiki/Escape_analysis http://www.burnison.ca/articles/escape-analysis-stack-alloca...
It would be nice to be able to control it though. (value objects?)
Secondly, beyond trivial examples, sufficient inlining needs to occur for compiler to have full horizon, and that can fail for a variety of reasons, even with run to run variance of exact same code shape.
As you say, there's no control and you're fully at the mercy of the compiler's mood that day. This is well into Sufficiently Smart Compiler land ...
The bottom line is that anyone for whom those allocations can be problematic take manual measures to ensure they don't happen and do not rely on EA.
Value types should help for a few reasons, however they appear to be somewhat limited (e.g. immutable, cannot be passed by ref).
This was a fun thing I remember writing: https://github.com/newobj/ffwd/blob/master/Src/Core/Std/Allo... When returned to the pool, each object used the first 4 bytes of itself to point to the next free one in a list, so there was no additional overhead.
Having tens of thousands of agents constantly being garbage invalidated was the use case.
(Not really sure it's forgotten, either; it's used regularly for heavy objects, or when speed is absolutely critical.)
which is cheaper, stack object allocation/creation or object pooling, in languages like C/C++/Rust?