diff --git a/src/Compilers/Core/Portable/Collections/OrderPreservingMultiDictionary.cs b/src/Compilers/Core/Portable/Collections/OrderPreservingMultiDictionary.cs index a7f32443622e9..40c444b592a8f 100644 --- a/src/Compilers/Core/Portable/Collections/OrderPreservingMultiDictionary.cs +++ b/src/Compilers/Core/Portable/Collections/OrderPreservingMultiDictionary.cs @@ -1,5 +1,7 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; +using System.Collections; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; @@ -8,14 +10,14 @@ namespace Microsoft.CodeAnalysis.Collections { /// - /// A MultiDictionary that allows only adding, and - /// preserves the order of values added to the dictionary. - /// Thread-safe for reading, but not for adding. + /// A MultiDictionary that allows only adding, and preserves the order of values added to the + /// dictionary. Thread-safe for reading, but not for adding. /// /// /// Always uses the default comparer. /// - internal sealed class OrderPreservingMultiDictionary + internal sealed class OrderPreservingMultiDictionary : + IEnumerable.ValueSet>> { #region Pooling @@ -30,6 +32,12 @@ public void Free() { if (_dictionary != null) { + // Allow our ValueSets to return their underlying ArrayBuilders to the pool. + foreach (var kvp in _dictionary) + { + kvp.Value.Free(); + } + _dictionary.Free(); _dictionary = null; } @@ -57,159 +65,231 @@ public void Free() #endregion Pooling + // An empty dictionary we keep around to simplify certain operations (like "Keys") + // when we don't have an underlying dictionary of our own. + private static readonly Dictionary s_emptyDictionary = new Dictionary(); + + // The underlying dictionary we store our data in. null if we are empty. + private PooledDictionary _dictionary; + public OrderPreservingMultiDictionary() { } - // store either a single V or an ArrayBuilder - /// - /// Each value is either a single V or an . - /// Null when the dictionary is empty. - /// Don't access the field directly. - /// - private PooledDictionary _dictionary; - private void EnsureDictionary() { - _dictionary = _dictionary ?? PooledDictionary.GetInstance(); + _dictionary = _dictionary ?? PooledDictionary.GetInstance(); } - public bool IsEmpty - { - get { return _dictionary == null; } - } + public bool IsEmpty => _dictionary == null; /// /// Add a value to the dictionary. /// public void Add(K k, V v) { - object item; - if (!this.IsEmpty && _dictionary.TryGetValue(k, out item)) + ValueSet valueSet; + if (!this.IsEmpty && _dictionary.TryGetValue(k, out valueSet)) { - var arrayBuilder = item as ArrayBuilder; - if (arrayBuilder == null) - { - // Promote from singleton V to ArrayBuilder. - Debug.Assert(item is V, "Item must be either a V or an ArrayBuilder"); - arrayBuilder = new ArrayBuilder(2); - arrayBuilder.Add((V)item); - arrayBuilder.Add(v); - _dictionary[k] = arrayBuilder; - } - else - { - arrayBuilder.Add(v); - } + Debug.Assert(valueSet.Count >= 1); + // Have to re-store the ValueSet in case we upgraded the existing ValueSet from + // holding a single item to holding multiple items. + _dictionary[k] = valueSet.WithAddedItem(v); } else { this.EnsureDictionary(); - _dictionary[k] = v; + _dictionary[k] = new ValueSet(v); } } - /// - /// Add multiple values to the dictionary. - /// - public void AddRange(K k, ImmutableArray values) + public Dictionary.Enumerator GetEnumerator() { - if (values.IsEmpty) - return; - - object item; - ArrayBuilder arrayBuilder; + return IsEmpty ? s_emptyDictionary.GetEnumerator() : _dictionary.GetEnumerator(); + } - if (!this.IsEmpty && _dictionary.TryGetValue(k, out item)) - { - arrayBuilder = item as ArrayBuilder; - if (arrayBuilder == null) - { - // Promote from singleton V to ArrayBuilder. - Debug.Assert(item is V, "Item must be either a V or an ArrayBuilder"); - arrayBuilder = new ArrayBuilder(1 + values.Length); - arrayBuilder.Add((V)item); - arrayBuilder.AddRange(values); - _dictionary[k] = arrayBuilder; - } - else - { - arrayBuilder.AddRange(values); - } - } - else - { - this.EnsureDictionary(); + IEnumerator> IEnumerable>.GetEnumerator() + { + return GetEnumerator(); + } - if (values.Length == 1) - { - _dictionary[k] = values[0]; - } - else - { - arrayBuilder = new ArrayBuilder(values.Length); - arrayBuilder.AddRange(values); - _dictionary[k] = arrayBuilder; - } - } + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); } /// - /// Get the number of values associated with a key. + /// Get all values associated with K, in the order they were added. + /// Returns empty read-only array if no values were present. /// - public int GetCountForKey(K k) + public ImmutableArray this[K k] { - object item; - if (!this.IsEmpty && _dictionary.TryGetValue(k, out item)) + get { - return (item as ArrayBuilder)?.Count ?? 1; - } + ValueSet valueSet; + if (!this.IsEmpty && _dictionary.TryGetValue(k, out valueSet)) + { + Debug.Assert(valueSet.Count >= 1); + return valueSet.Items; + } - return 0; + return ImmutableArray.Empty; + } } /// - /// Returns true if one or more items with given key have been added. + /// Get a collection of all the keys. /// - public bool ContainsKey(K k) + public Dictionary.KeyCollection Keys { - return !this.IsEmpty && _dictionary.ContainsKey(k); + get { return this.IsEmpty ? s_emptyDictionary.Keys : _dictionary.Keys; } } - /// - /// Get all values associated with K, in the order they were added. - /// Returns empty read-only array if no values were present. - /// - public ImmutableArray this[K k] + public struct ValueSet : IEnumerable { - get + /// + /// Each value is either a single V or an . + /// Never null. + /// + private readonly object _value; + + internal ValueSet(V value) { - object item; - if (!this.IsEmpty && _dictionary.TryGetValue(k, out item)) + _value = value; + } + + internal ValueSet(ArrayBuilder values) + { + _value = values; + } + + internal void Free() + { + var arrayBuilder = _value as ArrayBuilder; + arrayBuilder?.Free(); + } + + internal V this[int index] + { + get { - var arrayBuilder = item as ArrayBuilder; + Debug.Assert(this.Count >= 1); + + var arrayBuilder = _value as ArrayBuilder; + if (arrayBuilder == null) + { + if (index == 0) + { + return (V)_value; + } + else + { + throw new IndexOutOfRangeException(); + } + } + else + { + return arrayBuilder[index]; + } + } + } + + internal ImmutableArray Items + { + get + { + Debug.Assert(this.Count >= 1); + + var arrayBuilder = _value as ArrayBuilder; if (arrayBuilder == null) { // promote singleton to set - Debug.Assert(item is V, "Item must be either a V or an ArrayBuilder"); - return ImmutableArray.Create((V)item); + Debug.Assert(_value is V, "Item must be a a V"); + return ImmutableArray.Create((V)_value); } else { return arrayBuilder.ToImmutable(); } } + } - return ImmutableArray.Empty; + internal int Count => (_value as ArrayBuilder)?.Count ?? 1; + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); } - } - /// - /// Get a collection of all the keys. - /// - public ICollection Keys - { - get { return this.IsEmpty ? SpecializedCollections.EmptyCollection() : _dictionary.Keys; } + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public Enumerator GetEnumerator() + { + return new Enumerator(this); + } + + internal ValueSet WithAddedItem(V item) + { + Debug.Assert(this.Count >= 1); + + var arrayBuilder = _value as ArrayBuilder; + if (arrayBuilder == null) + { + // Promote from singleton V to ArrayBuilder. + Debug.Assert(_value is V, "_value must be a V"); + + // By default we allocate array builders with a size of two. That's to store + // the single item already in _value, and to store the item we're adding. + // In general, we presume that the amount of values per key will be low, so this + // means we have very little overhead when there are multiple keys per value. + arrayBuilder = ArrayBuilder.GetInstance(capacity: 2); + arrayBuilder.Add((V)_value); + arrayBuilder.Add(item); + } + else + { + arrayBuilder.Add(item); + } + + return new ValueSet(arrayBuilder); + } + + public struct Enumerator : IEnumerator + { + private readonly ValueSet _valueSet; + private readonly int _count; + private int _index; + + public Enumerator(ValueSet valueSet) + { + _valueSet = valueSet; + _count = _valueSet.Count; + Debug.Assert(_count >= 1); + _index = -1; + } + + public V Current => _valueSet[_index]; + + object IEnumerator.Current => Current; + + public bool MoveNext() + { + _index++; + return _index < _count; + } + + public void Reset() + { + _index = -1; + } + + public void Dispose() + { + } + } } } -} +} \ No newline at end of file diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Metadata.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Metadata.cs index a7572892445d3..4f182b14d43f4 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Metadata.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Metadata.cs @@ -6,26 +6,13 @@ using System.Text; using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Collections; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.FindSymbols { internal partial class SymbolTreeInfo { - private static SimplePool> s_definitionMapPool = - new SimplePool>(() => new MultiDictionary()); - - private static MultiDictionary AllocateDefinitionMap() - { - return s_definitionMapPool.Allocate(); - } - - private static void FreeDefinitionMap(MultiDictionary definitionMap) - { - definitionMap.Clear(); - s_definitionMapPool.Free(definitionMap); - } - /// /// this gives you SymbolTreeInfo for a metadata /// @@ -117,7 +104,7 @@ private static void GenerateMetadataNodes(MetadataReader metadataReader, List unsortedNodes) { - var definitionMap = AllocateDefinitionMap(); + var definitionMap = OrderPreservingMultiDictionary.GetInstance(); try { LookupMetadataDefinitions(reader, globalNamespace, definitionMap); @@ -132,7 +119,7 @@ private static void GenerateMetadataNodes(MetadataReader metadataReader, List.ValueSet definitionsWithSameName, + OrderPreservingMultiDictionary.ValueSet definitionsWithSameName, List unsortedNodes) { var node = new Node(name, parentIndex); @@ -148,7 +135,7 @@ private static void GenerateMetadataNodes(MetadataReader metadataReader, List.GetInstance(); try { foreach (var definition in definitionsWithSameName) @@ -166,13 +153,13 @@ private static void GenerateMetadataNodes(MetadataReader metadataReader, List definitionMap) + OrderPreservingMultiDictionary definitionMap) { switch (definition.Kind) { @@ -187,7 +174,7 @@ private static void GenerateMetadataNodes(MetadataReader metadataReader, List definitionMap) + OrderPreservingMultiDictionary definitionMap) { // Only bother looking for extension methods in static types. if ((typeDefinition.Attributes & TypeAttributes.Abstract) != 0 && @@ -235,7 +222,7 @@ private static void GenerateMetadataNodes(MetadataReader metadataReader, List definitionMap) + OrderPreservingMultiDictionary definitionMap) { foreach (var child in namespaceDefinition.NamespaceDefinitions) { diff --git a/src/Workspaces/Core/Portable/Workspaces.csproj b/src/Workspaces/Core/Portable/Workspaces.csproj index c81ead23270a7..9d0f61ec65b76 100644 --- a/src/Workspaces/Core/Portable/Workspaces.csproj +++ b/src/Workspaces/Core/Portable/Workspaces.csproj @@ -41,6 +41,9 @@ InternalUtilities\ImmutableArrayExtensions.cs + + Utilities\CompilerUtilities\OrderPreservingMultiDictionary.cs + Collections\PooledStringBuilder.cs