I would have expected the compiler to allowed to change:
if len(l.shared) > 0 { # the racy check for non-emptiness
l.Lock()
last := len(l.shared) - 1
to
tmp := len(l.shared)
if tmp > 0 { # the racy check for non-emptiness
l.Lock()
last := tmp - 1
I'd be interested if this were a legal optimisation. Were it so, then you
could get illegal behaviour.
Maybe?
Chris
On Friday, 16 February 2018 14:57:59 UTC, Carlo Alberto Ferraris wrote:
>
> Also, keep in mind, "being there nothing in the pool" is the common state
> of pools immediately after every GC.
>
> On Friday, February 16, 2018 at 12:05:27 PM UTC+9, Kevin Malachowski wrote:
>>
>> If there is likely nothing in the Pool, then maybe it's better to not use
>> one at all. Can you compare the internal workload with an implementation
>> where all callers just call the `New` function directly? What's the purpose
>> of using a pooled memory if there's often nothing in the pool?
>>
>> On Wednesday, February 14, 2018 at 3:56:34 PM UTC-8, Carlo Alberto
>> Ferraris wrote:
>>>
>>> In an attempt to reduce the time pprof says is spent in sync.Pool
>>> (internal workloads, sorry) I modified Get and getSlow to skip locking the
>>> per-P shared pools if the pools are likely to be empty. This yields
>>> promising results, but I'm not sure the approach is sound since the check I
>>> do is inherently racy.
>>>
>>> As a (artificial and contrived) benchmark, I'm using this:
>>>
>>> func BenchmarkPoolUnderflow(b *testing.B) {
>>> var p Pool
>>> b.RunParallel(func(pb *testing.PB) {
>>> for pb.Next() {
>>> p.Put(1)
>>> p.Get()
>>> p.Get()
>>> }
>>> })
>>> }
>>>
>>> This is meant to simulate a pool in which more or objects are Get() than
>>> are Put() (it wouldn't make much sense to simulate a pool in which we only
>>> get, so to keep things simple I opted for a 2:1 ratio)
>>>
>>> The change I applied to Get and getSlow is the following. Starting from
>>> the current pattern of:
>>>
>>> l := ... # per-P poolLocal
>>> l.Lock()
>>> last := len(l.shared) - 1
>>> if last >= 0 {
>>> x = l.shared[last]
>>> l.shared = l.shared[:last]
>>> }
>>> l.Unlock()
>>>
>>> I add a check (not protected by the mutex, that is the expensive op
>>> we're trying to skip if it's not necessary) to see if the pool is likely to
>>> be non-empty:
>>>
>>> l := ... # per-P poolLocal
>>> if len(l.shared) > 0 { # the racy check for non-emptiness
>>> l.Lock()
>>> last := len(l.shared) - 1
>>> if last >= 0 {
>>> x = l.shared[last]
>>> l.shared = l.shared[:last]
>>> }
>>> l.Unlock()
>>> }
>>>
>>> I know I should not call this a benign race, but in this case I don't
>>> see how this can lead to problems. If the racy check gets it right, then
>>> it's almost a net win. If if it gets it wrong, either we do what we do now
>>> (i.e. we lock, just to find an empty pool), or we skip an otherwise
>>> non-empty pool - thereby failing to immediately return an otherwise
>>> reusable object (note that 1. there is a per-P shared pool for each P, so
>>> I'd estimate the chances of this happening on all of them to be pretty low
>>> and 2. the Pool documentation explicitly say that Get is allowed to treat
>>> the pool as empty). Also note that the race detector is already explicitly
>>> disabled in all sync.Pool methods.
>>>
>>> The reason I'm asking is to understand whether my reasoning is sound
>>> and, regardless, if anybody has suggestions about how to do this in a
>>> better way.
>>>
>>> The current results (of the approach above, plus some refactoring to
>>> recover some lost performance on the other benchmarks) on my laptop are the
>>> following:
>>>
>>> name old time/op new time/op delta
>>> Pool-4 14.5ns ± 3% 14.2ns ± 2% -1.64% (p=0.023 n=9+10)
>>> PoolOverflow-4 1.99µs ±12% 1.78µs ± 1% -10.62% (p=0.000 n=10+8)
>>> PoolUnderflow-4 152ns ± 6% 30ns ± 1% -80.00% (p=0.000 n=10+8)
>>>
>>> (the first two benchmarks are already part of sync.Pool, the last one is
>>> the one I described above)
>>>
>>> Any feedback is welcome. If this is deemed safe I'm going to submit a CL.
>>>
>>> Carlo
>>>
>>>
>>>
--
You received this message because you are subscribed to the Google Groups
"golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
For more options, visit https://groups.google.com/d/optout.