-
Notifications
You must be signed in to change notification settings - Fork 4.9k
#1991 - ObjectPool approach #3702
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
// Copyright (c) Microsoft. All rights reserved. | ||
// Licensed under the MIT license. See LICENSE file in the project root for full license information. | ||
|
||
using System.Threading; | ||
|
||
namespace System.IO.Compression.Internal | ||
{ | ||
/// <summary> | ||
/// Generic implementation of object pooling pattern with predefined pool size limit. The main | ||
/// purpose is that limited number of frequently used objects can be kept in the pool for | ||
/// further recycling. | ||
/// | ||
/// Notes: | ||
/// 1) it is not the goal to keep all returned objects. Pool is not meant for storage. If there | ||
/// is no space in the pool, extra returned objects will be dropped. | ||
/// | ||
/// 2) it is implied that if object was obtained from a pool, the caller will return it back in | ||
/// a relatively short time. Keeping checked out objects for long durations is ok, but | ||
/// reduces usefulness of pooling. Just new up your own. | ||
/// | ||
/// Not returning objects to the pool in not detrimental to the pool's work, but is a bad practice. | ||
/// Rationale: | ||
/// If there is no intent for reusing the object, do not use pool - just use "new". | ||
/// </summary> | ||
internal sealed class ObjectPool<T> where T : class | ||
{ | ||
private struct Element | ||
{ | ||
internal T Value; | ||
} | ||
|
||
// storage for the pool objects. | ||
private readonly Element[] _items; | ||
|
||
// factory is stored for the lifetime of the pool. We will call this only when pool needs to | ||
// expand. compared to "new T()", Func gives more flexibility to implementers and faster | ||
// than "new T()". | ||
private readonly Func<T> _factory; | ||
|
||
|
||
internal ObjectPool(Func<T> factory) | ||
: this(factory, Environment.ProcessorCount * 2) | ||
{ } | ||
|
||
internal ObjectPool(Func<T> factory, int size) | ||
{ | ||
_factory = factory; | ||
_items = new Element[size]; | ||
} | ||
|
||
private T CreateInstance() | ||
{ | ||
var inst = _factory(); | ||
return inst; | ||
} | ||
|
||
/// <summary> | ||
/// Produces an instance. | ||
/// </summary> | ||
/// <remarks> | ||
/// Search strategy is a simple linear probing which is chosen for it cache-friendliness. | ||
/// Note that Free will try to store recycled objects close to the start thus statistically | ||
/// reducing how far we will typically search. | ||
/// </remarks> | ||
internal T Allocate() | ||
{ | ||
var items = _items; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ❗ coding style violation: rule # 10 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @Maxwe11 I actually took this verbatim from: https://github.com/dotnet/corefx/blob/master/src/System.Reflection.Metadata/src/System/Reflection/Internal/Utilities/ObjectPool%601.cs I was signing the CLA and then opening a new ticket to make ObjectPool available throught all CoreFX to avoid redundant code-copying. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In that case I'd rather apply There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Apparently the first part didn't went through. 😄 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No it didnt. Sent an email to the foundation requesting support. Not even using the Access documents option worked. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @stephentoub I didn't get any response from either the ticket I opened to DocuSign and the email I sent to the DotNet Foundation that is specified in the email. Do you know who I could contact to be able to sign the CLA? Because it is not working, tried in Chrome and Edge, no luck. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @richlander, @terrajobst, can you help @redknightlois? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @redknightlois, I spoke with @martinwoodward at the foundation, and he's concerned because he didn't see any email from you nor any issues with signing. He asked that you resend your email and include him directly: martin@dotnetfoundation.org. Sorry for the hassle, and thanks for persisting. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. Forwarded the original email to Martin. |
||
T inst; | ||
|
||
for (int i = 0; i < items.Length; i++) | ||
{ | ||
// Note that the read is optimistically not synchronized. That is intentional. | ||
// We will interlock only when we have a candidate. in a worst case we may miss some | ||
// recently returned objects. Not a big deal. | ||
inst = items[i].Value; | ||
if (inst != null) | ||
{ | ||
if (inst == Interlocked.CompareExchange(ref items[i].Value, null, inst)) | ||
{ | ||
goto gotInstance; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. IMO It would be better to use boolean flag and eliminate usage of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We are competing with new/GC here on the basis that we know the scenario and can do better. BTW, Roslyn has a slightly newer version that has special treatment for situations when a single instance is sufficient in most cases, which is relatively common. It basically adds a singleton stash in addition to an array. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am fairly acquainted with the Roslyn ObjectPool, it is the general consensus to use that one instead on CoreFX? It feels a bit too broad a change for me to do on my first PR :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. All the leak tracking goo is unnecessary. You can get same data from any profiler if needed. The only interesting difference there is that additional single item cache. |
||
} | ||
} | ||
} | ||
|
||
inst = CreateInstance(); | ||
gotInstance: | ||
|
||
return inst; | ||
} | ||
|
||
/// <summary> | ||
/// Returns objects to the pool. | ||
/// </summary> | ||
/// <remarks> | ||
/// Search strategy is a simple linear probing which is chosen for it cache-friendliness. | ||
/// Note that Free will try to store recycled objects close to the start thus statistically | ||
/// reducing how far we will typically search in Allocate. | ||
/// </remarks> | ||
internal void Free(T obj) | ||
{ | ||
var items = _items; | ||
for (int i = 0; i < items.Length; i++) | ||
{ | ||
if (items[i].Value == null) | ||
{ | ||
// Intentionally not using interlocked here. | ||
// In a worst case scenario two objects may be stored into same slot. | ||
// It is very unlikely to happen and will only mean that one of the objects will get collected. | ||
items[i].Value = obj; | ||
break; | ||
} | ||
} | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not familiar with the deflater codebase, but quite familiar with this pool. One thing to watch out when using ObjectPool, especially if it is static, is to not return back large objects.
If _buffer may expand, make sure you are not returning really big ones.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It does not, they are all the same size. 32Kb and 8Kb (all below the LOH threshold).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pool limits the number of retained objects, but you also need to ensure a limit for the size of stored objects. Otherwise the cache is unbounded - also known as memory leak.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I selected the size to ensure that a max of 1MB of memory is consumed, as arrays are all of the same size (and will never change, _buffer is readonly just in case) and we can know that only 32 are going to be retained.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good then.