-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Reduce string related allocations in Test262File.FromStream
- Loading branch information
Showing
3 changed files
with
241 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
#nullable disable | ||
|
||
using System.Diagnostics; | ||
|
||
namespace Test262Harness; | ||
|
||
/// <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 | ||
{ | ||
[DebuggerDisplay("{Value,nq}")] | ||
private struct Element | ||
{ | ||
internal T Value; | ||
} | ||
|
||
/// <remarks> | ||
/// Not using System.Func{T} because this file is linked into the (debugger) Formatter, | ||
/// which does not have that type (since it compiles against .NET 2.0). | ||
/// </remarks> | ||
internal delegate T Factory(); | ||
|
||
// Storage for the pool objects. The first item is stored in a dedicated field because we | ||
// expect to be able to satisfy most requests from it. | ||
private T _firstItem; | ||
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 Factory _factory; | ||
|
||
internal ObjectPool(Factory factory) | ||
: this(factory, Environment.ProcessorCount * 2) | ||
{ } | ||
|
||
internal ObjectPool(Factory factory, int size) | ||
{ | ||
Debug.Assert(size >= 1); | ||
_factory = factory; | ||
_items = new Element[size - 1]; | ||
} | ||
|
||
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() | ||
{ | ||
// PERF: Examine the first element. If that fails, AllocateSlow will look at the remaining elements. | ||
// Note that the initial 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. | ||
T inst = _firstItem; | ||
if (inst == null || inst != Interlocked.CompareExchange(ref _firstItem, null, inst)) | ||
{ | ||
inst = AllocateSlow(); | ||
} | ||
|
||
return inst; | ||
} | ||
|
||
private T AllocateSlow() | ||
{ | ||
var items = _items; | ||
|
||
for (var i = 0; i < items.Length; i++) | ||
{ | ||
// Note that the initial 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. | ||
var inst = items[i].Value; | ||
if (inst != null) | ||
{ | ||
if (inst == Interlocked.CompareExchange(ref items[i].Value, null, inst)) | ||
{ | ||
return inst; | ||
} | ||
} | ||
} | ||
|
||
return CreateInstance(); | ||
} | ||
|
||
/// <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) | ||
{ | ||
if (_firstItem == 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. | ||
_firstItem = obj; | ||
} | ||
else | ||
{ | ||
FreeSlow(obj); | ||
} | ||
} | ||
|
||
private void FreeSlow(T obj) | ||
{ | ||
var items = _items; | ||
for (var 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; | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
#nullable disable | ||
|
||
using System.Diagnostics; | ||
using System.Text; | ||
|
||
namespace Test262Harness; | ||
|
||
/// <summary> | ||
/// The usage is: | ||
/// var inst = StringBuilderPool.GetInstance(); | ||
/// var sb = inst.builder; | ||
/// ... Do Stuff... | ||
/// ... sb.ToString() ... | ||
/// inst.Free(); | ||
/// </summary> | ||
internal sealed class StringBuilderPool : IDisposable | ||
{ | ||
private const int DefaultPoolCapacity = 40 * 1024; | ||
private readonly int _defaultCapacity; | ||
|
||
// global pool | ||
private static readonly ObjectPool<StringBuilderPool> s_poolInstance = CreatePool(); | ||
|
||
public readonly StringBuilder Builder; | ||
private readonly ObjectPool<StringBuilderPool> _pool; | ||
|
||
private StringBuilderPool(ObjectPool<StringBuilderPool> pool, int defaultCapacity) | ||
{ | ||
Debug.Assert(pool != null); | ||
_defaultCapacity = defaultCapacity; | ||
Builder = new StringBuilder(defaultCapacity); | ||
_pool = pool; | ||
} | ||
|
||
public int Length => Builder.Length; | ||
|
||
/// <summary> | ||
/// If someone need to create a private pool | ||
/// </summary> | ||
internal static ObjectPool<StringBuilderPool> CreatePool(int size = 100, int capacity = DefaultPoolCapacity) | ||
{ | ||
ObjectPool<StringBuilderPool> pool = null; | ||
pool = new ObjectPool<StringBuilderPool>(() => new StringBuilderPool(pool, capacity), size); | ||
return pool; | ||
} | ||
|
||
/// <summary> | ||
/// Returns a StringBuilder from the default pool. | ||
/// </summary> | ||
public static StringBuilderPool GetInstance() | ||
{ | ||
var builder = s_poolInstance.Allocate(); | ||
Debug.Assert(builder.Builder.Length == 0); | ||
return builder; | ||
} | ||
|
||
public override string ToString() | ||
{ | ||
return Builder.ToString(); | ||
} | ||
|
||
public void Dispose() | ||
{ | ||
var builder = Builder; | ||
|
||
// Do not store builders that are too large. | ||
|
||
if (builder.Capacity == _defaultCapacity) | ||
{ | ||
builder.Clear(); | ||
_pool.Free(this); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters