-
Notifications
You must be signed in to change notification settings - Fork 470
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Avoid using ConcurrentDictionary pools due to allocation overhead in …
…Clear Addresses 650MB allocations observed in https://dev.azure.com/devdiv/DevDiv/_workitems/edit/1815692.
- Loading branch information
Showing
7 changed files
with
228 additions
and
26 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
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
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
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
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
89 changes: 89 additions & 0 deletions
89
src/Utilities/Compiler/PooledObjects/TemporaryDictionary`2.cs
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,89 @@ | ||
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. | ||
|
||
using System.Collections.Generic; | ||
using System.Diagnostics.CodeAnalysis; | ||
using System.Threading; | ||
|
||
namespace Analyzer.Utilities.PooledObjects | ||
{ | ||
[SuppressMessage("Performance", "CA1815:Override equals and operator equals on value types", Justification = "Not used in this context")] | ||
[SuppressMessage("Naming", "CA1711:Identifiers should not have incorrect suffix", Justification = "The 'Dictionary' suffix is intentional")] | ||
internal struct TemporaryDictionary<TKey, TValue> | ||
where TKey : notnull | ||
{ | ||
#pragma warning disable CS0649 // Field 'TemporaryDictionary<TKey, TValue>.Empty' is never assigned to, and will always have its default value | ||
public static readonly TemporaryDictionary<TKey, TValue> Empty; | ||
#pragma warning restore CS0649 // Field 'TemporaryDictionary<TKey, TValue>.Empty' is never assigned to, and will always have its default value | ||
|
||
/// <summary> | ||
/// An empty dictionary used for creating non-null enumerators when no items have been added to the dictionary. | ||
/// </summary> | ||
private static readonly Dictionary<TKey, TValue> EmptyDictionary = new(); | ||
|
||
// 🐇 PERF: use PooledDictionary<TKey, TValue> instead of PooledConcurrentDictionary<TKey, TValue> due to | ||
// allocation overhead in clearing the set for returning it to the pool. | ||
private PooledDictionary<TKey, TValue>? _storage; | ||
|
||
public readonly Enumerable NonConcurrentEnumerable | ||
=> new(_storage ?? EmptyDictionary); | ||
|
||
public void Free(CancellationToken cancellationToken) | ||
{ | ||
Interlocked.Exchange(ref _storage, null)?.Free(cancellationToken); | ||
} | ||
|
||
private PooledDictionary<TKey, TValue> GetOrCreateStorage(CancellationToken cancellationToken) | ||
{ | ||
if (_storage is not { } storage) | ||
{ | ||
var newStorage = PooledDictionary<TKey, TValue>.GetInstance(); | ||
storage = Interlocked.CompareExchange(ref _storage, newStorage, null) ?? newStorage; | ||
if (storage != newStorage) | ||
{ | ||
// Another thread initialized the value. Make sure to release the unused object. | ||
newStorage.Free(cancellationToken); | ||
} | ||
} | ||
|
||
return storage; | ||
} | ||
|
||
internal void Add(TKey key, TValue value, CancellationToken cancellationToken) | ||
{ | ||
var storage = GetOrCreateStorage(cancellationToken); | ||
lock (storage) | ||
{ | ||
storage.Add(key, value); | ||
} | ||
} | ||
|
||
public readonly struct Enumerable | ||
{ | ||
private readonly Dictionary<TKey, TValue> _dictionary; | ||
|
||
public Enumerable(Dictionary<TKey, TValue> dictionary) | ||
{ | ||
_dictionary = dictionary; | ||
} | ||
|
||
public Enumerator GetEnumerator() | ||
=> new(_dictionary.GetEnumerator()); | ||
} | ||
|
||
public struct Enumerator | ||
{ | ||
private Dictionary<TKey, TValue>.Enumerator _enumerator; | ||
|
||
public Enumerator(Dictionary<TKey, TValue>.Enumerator enumerator) | ||
{ | ||
_enumerator = enumerator; | ||
} | ||
|
||
public bool MoveNext() | ||
=> _enumerator.MoveNext(); | ||
|
||
public KeyValuePair<TKey, TValue> Current | ||
=> _enumerator.Current; | ||
} | ||
} | ||
} |
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,111 @@ | ||
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. | ||
|
||
using System.Collections.Generic; | ||
using System.Diagnostics.CodeAnalysis; | ||
using System.Threading; | ||
|
||
namespace Analyzer.Utilities.PooledObjects | ||
{ | ||
[SuppressMessage("Performance", "CA1815:Override equals and operator equals on value types", Justification = "Not used in this context")] | ||
internal struct TemporarySet<T> | ||
{ | ||
#pragma warning disable CS0649 // Field 'TemporarySet<T>.Empty' is never assigned to, and will always have its default value | ||
public static readonly TemporarySet<T> Empty; | ||
#pragma warning restore CS0649 // Field 'TemporarySet<T>.Empty' is never assigned to, and will always have its default value | ||
|
||
/// <summary> | ||
/// An empty set used for creating non-null enumerators when no items have been added to the set. | ||
/// </summary> | ||
private static readonly HashSet<T> EmptyHashSet = new(); | ||
|
||
// 🐇 PERF: use PooledHashSet<T> instead of PooledConcurrentSet<T> due to allocation overhead in | ||
// clearing the set for returning it to the pool. | ||
private PooledHashSet<T>? _storage; | ||
|
||
public readonly Enumerable NonConcurrentEnumerable | ||
=> new(_storage ?? EmptyHashSet); | ||
|
||
public void Free(CancellationToken cancellationToken) | ||
{ | ||
Interlocked.Exchange(ref _storage, null)?.Free(cancellationToken); | ||
} | ||
|
||
private PooledHashSet<T> GetOrCreateStorage(CancellationToken cancellationToken) | ||
{ | ||
if (_storage is not { } storage) | ||
{ | ||
var newStorage = PooledHashSet<T>.GetInstance(); | ||
storage = Interlocked.CompareExchange(ref _storage, newStorage, null) ?? newStorage; | ||
if (storage != newStorage) | ||
{ | ||
// Another thread initialized the value. Make sure to release the unused object. | ||
newStorage.Free(cancellationToken); | ||
} | ||
} | ||
|
||
return storage; | ||
} | ||
|
||
public bool Add(T item, CancellationToken cancellationToken) | ||
{ | ||
var storage = GetOrCreateStorage(cancellationToken); | ||
lock (storage) | ||
{ | ||
return storage.Add(item); | ||
} | ||
} | ||
|
||
public readonly bool Contains(T item) | ||
{ | ||
if (_storage is not { } storage) | ||
return false; | ||
|
||
lock (storage) | ||
{ | ||
return storage.Contains(item); | ||
} | ||
} | ||
|
||
public readonly bool Contains_NonConcurrent(T item) | ||
{ | ||
if (_storage is not { } storage) | ||
return false; | ||
|
||
return storage.Contains(item); | ||
} | ||
|
||
public readonly Enumerator GetEnumerator_NonConcurrent() | ||
{ | ||
return new Enumerator((_storage ?? EmptyHashSet).GetEnumerator()); | ||
} | ||
|
||
public readonly struct Enumerable | ||
{ | ||
private readonly HashSet<T> _set; | ||
|
||
public Enumerable(HashSet<T> set) | ||
{ | ||
_set = set; | ||
} | ||
|
||
public Enumerator GetEnumerator() | ||
=> new(_set.GetEnumerator()); | ||
} | ||
|
||
public struct Enumerator | ||
{ | ||
private HashSet<T>.Enumerator _enumerator; | ||
|
||
public Enumerator(HashSet<T>.Enumerator enumerator) | ||
{ | ||
_enumerator = enumerator; | ||
} | ||
|
||
public bool MoveNext() | ||
=> _enumerator.MoveNext(); | ||
|
||
public T Current | ||
=> _enumerator.Current; | ||
} | ||
} | ||
} |