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