[14744] | 1 | // Licensed to the .NET Foundation under one or more agreements.
|
---|
| 2 | // The .NET Foundation licenses this file to you under the MIT license.
|
---|
| 3 | // See the LICENSE file in the project root for more information.
|
---|
| 4 |
|
---|
| 5 | using System;
|
---|
| 6 |
|
---|
| 7 | namespace TestPooling.Pool {
|
---|
| 8 | using System.Threading;
|
---|
| 9 |
|
---|
| 10 | /// <summary>
|
---|
| 11 | /// Generic implementation of object pooling pattern with predefined pool size limit. The main
|
---|
| 12 | /// purpose is that limited number of frequently used objects can be kept in the pool for
|
---|
| 13 | /// further recycling.
|
---|
| 14 | ///
|
---|
| 15 | /// Notes:
|
---|
| 16 | /// 1) it is not the goal to keep all returned objects. Pool is not meant for storage. If there
|
---|
| 17 | /// is no space in the pool, extra returned objects will be dropped.
|
---|
| 18 | ///
|
---|
| 19 | /// 2) it is implied that if object was obtained from a pool, the caller will return it back in
|
---|
| 20 | /// a relatively short time. Keeping checked out objects for long durations is ok, but
|
---|
| 21 | /// reduces usefulness of pooling. Just new up your own.
|
---|
| 22 | ///
|
---|
| 23 | /// Not returning objects to the pool in not detrimental to the pool's work, but is a bad practice.
|
---|
| 24 | /// Rationale:
|
---|
| 25 | /// If there is no intent for reusing the object, do not use pool - just use "new".
|
---|
| 26 | /// </summary>
|
---|
| 27 | public sealed class ObjectPool<T> where T : class {
|
---|
| 28 | private struct Element {
|
---|
| 29 | internal T Value;
|
---|
| 30 | }
|
---|
| 31 |
|
---|
| 32 | // storage for the pool objects.
|
---|
| 33 | private readonly Element[] _items;
|
---|
| 34 |
|
---|
| 35 | // factory is stored for the lifetime of the pool. We will call this only when pool needs to
|
---|
| 36 | // expand. compared to "new T()", Func gives more flexibility to implementers and faster
|
---|
| 37 | // than "new T()".
|
---|
| 38 | private readonly Func<T> _factory;
|
---|
| 39 |
|
---|
| 40 |
|
---|
| 41 | internal ObjectPool(Func<T> factory)
|
---|
| 42 | : this(factory, Environment.ProcessorCount * 2) { }
|
---|
| 43 |
|
---|
| 44 | internal ObjectPool(Func<T> factory, int size) {
|
---|
| 45 | _factory = factory;
|
---|
| 46 | _items = new Element[size];
|
---|
| 47 | }
|
---|
| 48 |
|
---|
| 49 | private T CreateInstance() {
|
---|
| 50 | var inst = _factory();
|
---|
| 51 | return inst;
|
---|
| 52 | }
|
---|
| 53 |
|
---|
| 54 | /// <summary>
|
---|
| 55 | /// Produces an instance.
|
---|
| 56 | /// </summary>
|
---|
| 57 | /// <remarks>
|
---|
| 58 | /// Search strategy is a simple linear probing which is chosen for it cache-friendliness.
|
---|
| 59 | /// Note that Free will try to store recycled objects close to the start thus statistically
|
---|
| 60 | /// reducing how far we will typically search.
|
---|
| 61 | /// </remarks>
|
---|
| 62 | internal T Allocate() {
|
---|
| 63 | var items = _items;
|
---|
| 64 | T inst;
|
---|
| 65 |
|
---|
| 66 | for (int i = 0; i < items.Length; i++) {
|
---|
| 67 | // Note that the read is optimistically not synchronized. That is intentional.
|
---|
| 68 | // We will interlock only when we have a candidate. in a worst case we may miss some
|
---|
| 69 | // recently returned objects. Not a big deal.
|
---|
| 70 | inst = items[i].Value;
|
---|
| 71 | if (inst != null) {
|
---|
| 72 | if (inst == Interlocked.CompareExchange(ref items[i].Value, null, inst)) {
|
---|
| 73 | goto gotInstance;
|
---|
| 74 | }
|
---|
| 75 | }
|
---|
| 76 | }
|
---|
| 77 |
|
---|
| 78 | inst = CreateInstance();
|
---|
| 79 | gotInstance:
|
---|
| 80 |
|
---|
| 81 | return inst;
|
---|
| 82 | }
|
---|
| 83 |
|
---|
| 84 | /// <summary>
|
---|
| 85 | /// Returns objects to the pool.
|
---|
| 86 | /// </summary>
|
---|
| 87 | /// <remarks>
|
---|
| 88 | /// Search strategy is a simple linear probing which is chosen for it cache-friendliness.
|
---|
| 89 | /// Note that Free will try to store recycled objects close to the start thus statistically
|
---|
| 90 | /// reducing how far we will typically search in Allocate.
|
---|
| 91 | /// </remarks>
|
---|
| 92 | internal void Free(T obj) {
|
---|
| 93 | var items = _items;
|
---|
| 94 | for (int i = 0; i < items.Length; i++) {
|
---|
| 95 | if (items[i].Value == null) {
|
---|
| 96 | // Intentionally not using interlocked here.
|
---|
| 97 | // In a worst case scenario two objects may be stored into same slot.
|
---|
| 98 | // It is very unlikely to happen and will only mean that one of the objects will get collected.
|
---|
| 99 | items[i].Value = obj;
|
---|
| 100 | break;
|
---|
| 101 | }
|
---|
| 102 | }
|
---|
| 103 | }
|
---|
| 104 | }
|
---|
| 105 | }
|
---|