[1] http://www.stefankrause.net/wp/?p=64 [2] http://www.ibm.com/developerworks/library/j-jtp09275/
That said, JVMs do not actually stack allocate anything. They do a smarter optimisation called scalar replacement. The object is effectively decomposed into local variables that are then subject to further optimisation, for instance, completely deleting a field that isn't used.
Value types will be added to the JVM eventually in the Valhalla project. Go fans may note here that Go has value types, but this is a dodge - the bulk of the work being done so far in Valhalla is a major upgrade of the support for generics, because the Java (and .NET) teams believe that value types without generic specialisation is a fairly useless feature. If they didn't do that you could have MyValueType[] as an array, but not a List<MyValueType> or Map<String, MyValueType> which would make it fairly useless. Go gets around this problem by simply not letting users define their own generic data structures and baking a few simple ones into the language itself. This is hardly a solution.
https://www.cs.virginia.edu/kim/publicity/pldi09tutorials/me...
Also that they went VM instead of AOT like those languages main implementations.
Oh well, at least they are now on the roadmap for Java 10, 30 years later.
So the golang pool is good for the case where you have GC heavy but non-latency sensitive operations, but not the more general performance sensitive problems.
The experience of every JIT developer is that dynamic type checks do matter a lot in hot paths.
If that branch is mispredicted, we're talking about 12-20 cycles. Ok, I assume it's a range check and thus (nearly) always not taken. So if it's in hot path, it'll always be correctly predicted. Modern CPUs will most likely fuse cmp+jae into one micro-op, so predicted-not-taken + mov will take 2 cycles (+latency).
"cmp/jae/cmp/jne/mov" will of course be fused into 3 micro-ops. But don't you mean "cmp/jae/cmp/je/mov"? I'm assuming second compare is a NULL check (or at least that instructions are ordered that way second branch is practically never taken). I think that also takes 2 cycles (both branches execute on same clock cycle + mov), but not sure how fused predicted-not-takens behave.
L3 miss for that mov, well... might well be 200 cycles.
The first compare is a bounds check against the array backing the pool, and the second compare is against the type field on the interface, not a null check. Golang interfaces are "fat pointers" with two words: a data pointer and a vtable pointer. So the first cmp is against a register, while the second cmp is against memory, data dependent on the register index. The address of the cmp has to be at least checked to determine if it faults, so I would think at least some part of it would have to be serialized after the first branch, making it slower than the version without the type guard.
Well, I didn't profile that case. Who knows what will really happen. Modern x86 processors are hard to understand.
> ... while the second cmp is against memory, data dependent on the register index
Hmm... that sounds like something that would dominate the cost? Memory access and data dependency. Ouch.
Also of course in that case, second compare+branch can't be fused, because cmp has a memory operand.
So your had rolled one will not have the typing overhead we are discussing, but it will have 2 much worse issues.
1. sync.Pool's have thread local storage, something your own pools will not have.
2. sync.Pool's are GC aware; meaning if the allocator is having trouble it can drain "free" pool objects to gain memory. Your custom pool will not have this integration.
I have a feeling that the performance you gained not type-checking you will loose by not having #1.[edit] As pcwalton points out. My whole argument is actually null and void due to type erasure...doh.
Both of these can dramatically reduce GC pressure too.