I admittedly do this rather frequently, even for the most basic and generic containers, though my domain is rather intense in terms of data structure requirements.
The thing in my case is that while these generalized structures are extremely efficient at what they do in ways where I'd be a fool to try to match them fulfilling the same requirements, their generality usually offers plenty of room for a more efficient solution if you tailor that solution to the specific nuances and requirements of the problem at hand.
General vs. Tailored Solutions
For example, I would be a complete fool to try to beat or even match C++'s
std::vector (similar to
ArrayList in Java) in terms of its general purpose as a variable-length, random-access sequence which is designed to store anywhere from 0 to a gazillion elements. But I can beat it without breaking a sweat (as should anyone else) in use cases where the common case scenario involves frequently constructing a sequence that has no more than 32 elements, for example, with a straightforward implementation of a generic container that only has a couple of dozen lines of code using a small buffer optimization which avoids using the heap in those common cases.
Similar thing with a general-purpose memory allocator. It'd be very foolish in my opinion to try to beat
malloc in C with my own implementation designed to satisfy variable-length allocation requests while juggling all kinds of different concerns, and I'd pit myself against the world's finest who devote so much time and attention to just this one problem if I tried. But if all I need is to allocate N element of the same type (same chunk size) in a single thread, then the problem is exponentially simpler to solve, and I can easily beat
malloc if I design a solution just to solve this one very specific type of need.
So I've often found a lot of use for slightly more nuanced, but still somewhat general (enough to find a bit of reuse) solutions like these which are more tailored to the specific problem at hand. And it's not like you can implement a competitive, production-quality path tracer or realtime physics engine using
std::hash_map in place of a spatial index, e.g, let alone using a third party implementation, so my domain inevitably calls for rolling your own data structures at times since that's what gives your software the competitive advantage (or lack thereof if we fail) and unique performance characteristics*.
In these areas it's not always about "better" or "worse"; different solutions offer different performance strengths and weaknesses, and some of what makes your solution competitive and stand out isn't always about being outright better at everything but being better at some. For example, some of our competitors have very efficient GPU implementations of standard path tracing (as opposed to BDPT or other techniques) which allows them to process a ridiculous number of rays/sec on beefy GPUs. However, they suffer at times with tricky lighting scenarios and can still take hours to converge to a noise-free image in those cases. Ours is more of a hybrid system and data structure and lacks that brute force power, but can converge on those tricky lighting setups a lot faster which tends to make us popular for architectural visualization, while theirs converges a lot faster than ours with more straightforward lighting.
A Practical Example
Just as a practical example I found many on SO struggling to implement their spatial indexes (quadtrees, octrees, BVHs, grids, spatial hashes, etc) efficiently, and one of the biggest gotchas I always see is the temptation to store a generic container from their standard library with each node. That just ends up becoming too explosive in memory to reach for such a generalized solution in areas like a single node/cell of such a structure.
Their structures often had difficulty pulling off even 30 FPS with just a few thousand agents. Whereas I ended up handling half a million on a single thread at over 100 FPS (over 50 million insertions+removals+search queries into the structure per second with about a third of the time spent drawing the frame, not doing collision queries) with a single-thread solution I whipped up in 2 hours that I deliberately tried not to optimize much (the code was intended to teach other people and be as simple and as straightforward as possible):
And this is not intended as a boast and while I have some experience doing these types of things (along with a very fancy and expensive profiler), the first and biggest key (but a substantial one that anybody seeking similar results can apply) to achieving such a solution is to move away from the idea of storing generalized containers in every single node. We have to sometimes move away from such a generalized mindset to solving the problem and, instead of just reaching into the standard toolbox in some demanding cases like these, start really thinking about how to arrange the data in memory and access it efficiently in ways you can't if you can't represent this data at a finer level of nuance any further than what standard containers provide. And exercising this mindset might still allow some use of a generalized container at the tree level but not at the level of a single node in a spatial index.
One of the things I learned the hard way is not to generalize these more tailored and nuanced solutions too much (otherwise the amount of hand-rolled code we'd have to write and test can multiply a great deal in exchange for very little). I used to try to generalize them to the extent of offering identical interfaces to the standard library to try to maximize their reusability and familiarity, and while I still try to do that when it doesn't require going out of my way at all, I no longer do that when it does. That just ended up requiring so much time upfront to try to generalize a solution whose performance advantages over what's more generalized came from not being so generalized, only to find all the bells and whistles to meet such standard compliance were hardly, if ever, used. So it was like trying to balance two conflicting requirements at once and also a huge time sink. So these days I don't bother trying to generalize these "tailored" solutions too much; I'll make them conform to standard and familiar interfaces when that's almost effortless. Otherwise I won't and accept that these solutions are somewhat narrower in their applicability.
With that aside, I still lean on the generalized containers built into the language whenever possible (there's a massive benefit to doing that which multiplies for teams since the team members are already familiar with those standard containers), and often implement my first draft implementations using them for new and unfamiliar problems I encounter (though I've built up a bit of a library of solutions for very familiar ones). Only if I start seeing hotspots there in hindsight in profiling sessions and, balancing that out with the need to ship, do I consider reaching for anything else.