From: Jan Kotas Date: Thu, 2 Nov 2017 16:22:19 +0000 (-0700) Subject: Move Dictionary to shared CoreLib partition (#14795) X-Git-Tag: accepted/tizen/base/20180629.140029~711 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=0fc8b13bfbf14a51964b95e29a67cbf53f8609c3;p=platform%2Fupstream%2Fcoreclr.git Move Dictionary to shared CoreLib partition (#14795) --- diff --git a/src/mscorlib/System.Private.CoreLib.csproj b/src/mscorlib/System.Private.CoreLib.csproj index c78274e..72368f0 100644 --- a/src/mscorlib/System.Private.CoreLib.csproj +++ b/src/mscorlib/System.Private.CoreLib.csproj @@ -594,7 +594,6 @@ - diff --git a/src/mscorlib/shared/System.Private.CoreLib.Shared.projitems b/src/mscorlib/shared/System.Private.CoreLib.Shared.projitems index 90a656f..5b7629f 100644 --- a/src/mscorlib/shared/System.Private.CoreLib.Shared.projitems +++ b/src/mscorlib/shared/System.Private.CoreLib.Shared.projitems @@ -53,6 +53,7 @@ + @@ -67,7 +68,9 @@ + + diff --git a/src/mscorlib/src/System/Collections/Generic/Dictionary.cs b/src/mscorlib/shared/System/Collections/Generic/Dictionary.cs similarity index 95% rename from src/mscorlib/src/System/Collections/Generic/Dictionary.cs rename to src/mscorlib/shared/System/Collections/Generic/Dictionary.cs index 761f775..5b57697 100644 --- a/src/mscorlib/src/System/Collections/Generic/Dictionary.cs +++ b/src/mscorlib/shared/System/Collections/Generic/Dictionary.cs @@ -2,31 +2,14 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -/*============================================================ -** -** -** -** -** Purpose: Generic hash table implementation -** -** #DictionaryVersusHashtableThreadSafety -** Hashtable has multiple reader/single writer (MR/SW) thread safety built into -** certain methods and properties, whereas Dictionary doesn't. If you're -** converting framework code that formerly used Hashtable to Dictionary, it's -** important to consider whether callers may have taken a dependence on MR/SW -** thread safety. If a reader writer lock is available, then that may be used -** with a Dictionary to get the same thread safety guarantee. -** -===========================================================*/ +using System; +using System.Collections; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.Serialization; namespace System.Collections.Generic { - using System; - using System.Collections; - using System.Diagnostics; - using System.Runtime.CompilerServices; - using System.Runtime.Serialization; - /// /// Used internally to control behavior of insertion into a . /// @@ -51,7 +34,7 @@ namespace System.Collections.Generic [DebuggerTypeProxy(typeof(IDictionaryDebugView<,>))] [DebuggerDisplay("Count = {Count}")] [Serializable] - [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")] + [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")] public class Dictionary : IDictionary, IDictionary, IReadOnlyDictionary, ISerializable, IDeserializationCallback { private struct Entry @@ -71,13 +54,13 @@ namespace System.Collections.Generic private IEqualityComparer comparer; private KeyCollection keys; private ValueCollection values; - private Object _syncRoot; + private object _syncRoot; // constants for serialization - private const String VersionName = "Version"; // Do not rename (binary serialization) - private const String HashSizeName = "HashSize"; // Do not rename (binary serialization). Must save buckets.Length - private const String KeyValuePairsName = "KeyValuePairs"; // Do not rename (binary serialization) - private const String ComparerName = "Comparer"; // Do not rename (binary serialization) + private const string VersionName = "Version"; // Do not rename (binary serialization) + private const string HashSizeName = "HashSize"; // Do not rename (binary serialization). Must save buckets.Length + private const string KeyValuePairsName = "KeyValuePairs"; // Do not rename (binary serialization) + private const string ComparerName = "Comparer"; // Do not rename (binary serialization) public Dictionary() : this(0, null) { } @@ -132,9 +115,7 @@ namespace System.Collections.Generic } } - public Dictionary(IEnumerable> collection) : - this(collection, null) - { } + public Dictionary(IEnumerable> collection) : this(collection, null) { } public Dictionary(IEnumerable> collection, IEqualityComparer comparer) : this((collection as ICollection>)?.Count ?? 0, comparer) @@ -152,9 +133,9 @@ namespace System.Collections.Generic protected Dictionary(SerializationInfo info, StreamingContext context) { - //We can't do anything with the keys and values until the entire graph has been deserialized - //and we have a resonable estimate that GetHashCode is not going to fail. For the time being, - //we'll just cache this. The graph is not valid until OnDeserialization has been called. + // We can't do anything with the keys and values until the entire graph has been deserialized + // and we have a resonable estimate that GetHashCode is not going to fail. For the time being, + // we'll just cache this. The graph is not valid until OnDeserialization has been called. HashHelpers.SerializationInfoTable.Add(this, info); } @@ -355,12 +336,14 @@ namespace System.Collections.Generic { ThrowHelper.ThrowArgumentNullException(ExceptionArgument.info); } + info.AddValue(VersionName, version); info.AddValue(ComparerName, comparer, typeof(IEqualityComparer)); - info.AddValue(HashSizeName, buckets == null ? 0 : buckets.Length); //This is the length of the bucket array. + info.AddValue(HashSizeName, buckets == null ? 0 : buckets.Length); // This is the length of the bucket array + if (buckets != null) { - KeyValuePair[] array = new KeyValuePair[Count]; + var array = new KeyValuePair[Count]; CopyTo(array, 0); info.AddValue(KeyValuePairsName, array, typeof(KeyValuePair[])); } @@ -402,7 +385,7 @@ namespace System.Collections.Generic if (buckets == null) Initialize(0); int hashCode = comparer.GetHashCode(key) & 0x7FFFFFFF; - int targetBucket = hashCode % buckets.Length; + int targetBucket = hashCode % buckets.Length; int collisionCount = 0; for (int i = buckets[targetBucket]; i >= 0; i = entries[i].next) @@ -423,9 +406,9 @@ namespace System.Collections.Generic return false; } - collisionCount++; } + int index; if (freeCount > 0) { @@ -454,7 +437,7 @@ namespace System.Collections.Generic // If we hit the collision threshold we'll need to switch to the comparer which is using randomized string hashing // i.e. EqualityComparer.Default. - if (collisionCount > HashHelpers.HashCollisionThreshold && comparer == NonRandomizedStringEqualityComparer.Default) + if (collisionCount > HashHelpers.HashCollisionThreshold && comparer is NonRandomizedStringEqualityComparer) { comparer = (IEqualityComparer)EqualityComparer.Default; Resize(entries.Length, true); @@ -463,17 +446,15 @@ namespace System.Collections.Generic return true; } - public virtual void OnDeserialization(Object sender) + public virtual void OnDeserialization(object sender) { SerializationInfo siInfo; HashHelpers.SerializationInfoTable.TryGetValue(this, out siInfo); if (siInfo == null) { - // It might be necessary to call OnDeserialization from a container if the container object also implements - // OnDeserialization. However, remoting will call OnDeserialization again. // We can return immediately if this function is called twice. - // Note we set remove the serialization info from the table at the end of this method. + // Note we remove the serialization info from the table at the end of this method. return; } @@ -526,6 +507,7 @@ namespace System.Collections.Generic for (int i = 0; i < newBuckets.Length; i++) newBuckets[i] = -1; Entry[] newEntries = new Entry[newSize]; Array.Copy(entries, 0, newEntries, 0, count); + if (forceNewHashCodes) { for (int i = 0; i < count; i++) @@ -536,6 +518,7 @@ namespace System.Collections.Generic } } } + for (int i = 0; i < count; i++) { if (newEntries[i].hashCode >= 0) @@ -545,6 +528,7 @@ namespace System.Collections.Generic newBuckets[bucket] = i; } } + buckets = newBuckets; entries = newEntries; } @@ -672,6 +656,7 @@ namespace System.Collections.Generic value = default(TValue); return false; } + public bool TryAdd(TKey key, TValue value) => TryInsert(key, value, InsertionBehavior.None); bool ICollection>.IsReadOnly @@ -1170,7 +1155,7 @@ namespace System.Collections.Generic get { return false; } } - Object ICollection.SyncRoot + object ICollection.SyncRoot { get { return ((ICollection)dictionary).SyncRoot; } } @@ -1225,7 +1210,7 @@ namespace System.Collections.Generic } } - Object System.Collections.IEnumerator.Current + object System.Collections.IEnumerator.Current { get { @@ -1396,7 +1381,7 @@ namespace System.Collections.Generic get { return false; } } - Object ICollection.SyncRoot + object ICollection.SyncRoot { get { return ((ICollection)dictionary).SyncRoot; } } @@ -1450,7 +1435,7 @@ namespace System.Collections.Generic } } - Object System.Collections.IEnumerator.Current + object System.Collections.IEnumerator.Current { get { diff --git a/src/mscorlib/shared/System/Collections/Generic/NonRandomizedStringEqualityComparer.cs b/src/mscorlib/shared/System/Collections/Generic/NonRandomizedStringEqualityComparer.cs new file mode 100644 index 0000000..ef44fef --- /dev/null +++ b/src/mscorlib/shared/System/Collections/Generic/NonRandomizedStringEqualityComparer.cs @@ -0,0 +1,38 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Runtime.Serialization; + +namespace System.Collections.Generic +{ + // NonRandomizedStringEqualityComparer is the comparer used by default with the Dictionary + // We use NonRandomizedStringEqualityComparer as default comparer as it doesnt use the randomized string hashing which + // keeps the performance not affected till we hit collision threshold and then we switch to the comparer which is using + // randomized string hashing. + [Serializable] // Required for compatibility with .NET Core 2.0 as we exposed the NonRandomizedStringEqualityComparer inside the serialization blob +#if CORECLR + internal +#else + public +#endif + sealed class NonRandomizedStringEqualityComparer : EqualityComparer, ISerializable + { + internal static new IEqualityComparer Default { get; } = new NonRandomizedStringEqualityComparer(); + + private NonRandomizedStringEqualityComparer() { } + + // This is used by the serialization engine. + private NonRandomizedStringEqualityComparer(SerializationInfo information, StreamingContext context) { } + + public sealed override bool Equals(string x, string y) => string.Equals(x, y); + + public sealed override int GetHashCode(string obj) => obj?.GetLegacyNonRandomizedHashCode() ?? 0; + + public void GetObjectData(SerializationInfo info, StreamingContext context) + { + // We are doing this to stay compatible with .NET Framework. + info.SetType(typeof(GenericEqualityComparer)); + } + } +} diff --git a/src/mscorlib/shared/System/Collections/HashHelpers.cs b/src/mscorlib/shared/System/Collections/HashHelpers.cs new file mode 100644 index 0000000..49cff85 --- /dev/null +++ b/src/mscorlib/shared/System/Collections/HashHelpers.cs @@ -0,0 +1,108 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.Serialization; +using System.Threading; + +namespace System.Collections +{ + internal static class HashHelpers + { + public const int HashCollisionThreshold = 100; + + public const int HashPrime = 101; + + // Table of prime numbers to use as hash table sizes. + // A typical resize algorithm would pick the smallest prime number in this array + // that is larger than twice the previous capacity. + // Suppose our Hashtable currently has capacity x and enough elements are added + // such that a resize needs to occur. Resizing first computes 2x then finds the + // first prime in the table greater than 2x, i.e. if primes are ordered + // p_1, p_2, ..., p_i, ..., it finds p_n such that p_n-1 < 2x < p_n. + // Doubling is important for preserving the asymptotic complexity of the + // hashtable operations such as add. Having a prime guarantees that double + // hashing does not lead to infinite loops. IE, your hash function will be + // h1(key) + i*h2(key), 0 <= i < size. h2 and the size must be relatively prime. + public static readonly int[] primes = { + 3, 7, 11, 17, 23, 29, 37, 47, 59, 71, 89, 107, 131, 163, 197, 239, 293, 353, 431, 521, 631, 761, 919, + 1103, 1327, 1597, 1931, 2333, 2801, 3371, 4049, 4861, 5839, 7013, 8419, 10103, 12143, 14591, + 17519, 21023, 25229, 30293, 36353, 43627, 52361, 62851, 75431, 90523, 108631, 130363, 156437, + 187751, 225307, 270371, 324449, 389357, 467237, 560689, 672827, 807403, 968897, 1162687, 1395263, + 1674319, 2009191, 2411033, 2893249, 3471899, 4166287, 4999559, 5999471, 7199369}; + + public static bool IsPrime(int candidate) + { + if ((candidate & 1) != 0) + { + int limit = (int)Math.Sqrt(candidate); + for (int divisor = 3; divisor <= limit; divisor += 2) + { + if ((candidate % divisor) == 0) + return false; + } + return true; + } + return (candidate == 2); + } + + public static int GetPrime(int min) + { + if (min < 0) + throw new ArgumentException(SR.Arg_HTCapacityOverflow); + + for (int i = 0; i < primes.Length; i++) + { + int prime = primes[i]; + if (prime >= min) return prime; + } + + //outside of our predefined table. + //compute the hard way. + for (int i = (min | 1); i < Int32.MaxValue; i += 2) + { + if (IsPrime(i) && ((i - 1) % HashPrime != 0)) + return i; + } + return min; + } + + // Returns size of hashtable to grow to. + public static int ExpandPrime(int oldSize) + { + int newSize = 2 * oldSize; + + // Allow the hashtables to grow to maximum possible size (~2G elements) before encoutering capacity overflow. + // Note that this check works even when _items.Length overflowed thanks to the (uint) cast + if ((uint)newSize > MaxPrimeArrayLength && MaxPrimeArrayLength > oldSize) + { + Debug.Assert(MaxPrimeArrayLength == GetPrime(MaxPrimeArrayLength), "Invalid MaxPrimeArrayLength"); + return MaxPrimeArrayLength; + } + + return GetPrime(newSize); + } + + + // This is the maximum prime smaller than Array.MaxArrayLength + public const int MaxPrimeArrayLength = 0x7FEFFFFD; + + + // Used by Hashtable and Dictionary's SeralizationInfo .ctor's to store the SeralizationInfo + // object until OnDeserialization is called. + private static ConditionalWeakTable s_serializationInfoTable; + + internal static ConditionalWeakTable SerializationInfoTable + { + get + { + if (s_serializationInfoTable == null) + Interlocked.CompareExchange(ref s_serializationInfoTable, new ConditionalWeakTable(), null); + + return s_serializationInfoTable; + } + } + } +} diff --git a/src/mscorlib/src/System/Collections/Generic/EqualityComparer.cs b/src/mscorlib/src/System/Collections/Generic/EqualityComparer.cs index 05297b0..57b63eb 100644 --- a/src/mscorlib/src/System/Collections/Generic/EqualityComparer.cs +++ b/src/mscorlib/src/System/Collections/Generic/EqualityComparer.cs @@ -269,56 +269,6 @@ namespace System.Collections.Generic GetType().GetHashCode(); } - // We use NonRandomizedStringEqualityComparer as default comparer as it doesnt use the randomized string hashing which - // keeps the performance unaffected till we hit collision threshold and then we switch to the comparer which is using - // randomized string hashing GenericEqualityComparer - // We are keeping serialization support here to support deserialization of .NET Core 2.0 serialization payloads with this type in it. - [Serializable] - internal sealed class NonRandomizedStringEqualityComparer : EqualityComparer, ISerializable - { - private static IEqualityComparer s_nonRandomizedComparer; - - private NonRandomizedStringEqualityComparer() { } - - // This is used by the serialization engine. - private NonRandomizedStringEqualityComparer(SerializationInfo information, StreamingContext context) { } - - internal static new IEqualityComparer Default - { - get - { - if (s_nonRandomizedComparer == null) - { - s_nonRandomizedComparer = new NonRandomizedStringEqualityComparer(); - } - return s_nonRandomizedComparer; - } - } - - public override bool Equals(string x, string y) - { - if (x != null) - { - if (y != null) return x.Equals(y); - return false; - } - if (y != null) return false; - return true; - } - - public override int GetHashCode(string obj) - { - if (obj == null) return 0; - return obj.GetLegacyNonRandomizedHashCode(); - } - - public void GetObjectData(SerializationInfo info, StreamingContext context) - { - // We are doing this to stay compatible with .NET Framework. - info.SetType(typeof(GenericEqualityComparer)); - } - } - // Performance of IndexOf on byte array is very important for some scenarios. // We will call the C runtime function memchr, which is optimized. [Serializable] diff --git a/src/mscorlib/src/System/Collections/Hashtable.cs b/src/mscorlib/src/System/Collections/Hashtable.cs index b890bfb..6a23fde 100644 --- a/src/mscorlib/src/System/Collections/Hashtable.cs +++ b/src/mscorlib/src/System/Collections/Hashtable.cs @@ -123,7 +123,6 @@ namespace System.Collections -- */ - internal const Int32 HashPrime = 101; private const Int32 InitialSize = 3; private const String LoadFactorName = "LoadFactor"; private const String VersionName = "Version"; @@ -268,7 +267,7 @@ namespace System.Collections // visit every bucket in the table exactly once within hashsize // iterations. Violate this and it'll cause obscure bugs forever. // If you change this calculation for h2(key), update putEntry too! - incr = (uint)(1 + ((seed * HashPrime) % ((uint)hashsize - 1))); + incr = (uint)(1 + ((seed * HashHelpers.HashPrime) % ((uint)hashsize - 1))); return hashcode; } @@ -806,7 +805,7 @@ namespace System.Collections Debug.Assert(hashcode >= 0, "hashcode >= 0"); // make sure collision bit (sign bit) wasn't set. uint seed = (uint)hashcode; - uint incr = (uint)(1 + ((seed * HashPrime) % ((uint)newBuckets.Length - 1))); + uint incr = (uint)(1 + ((seed * HashHelpers.HashPrime) % ((uint)newBuckets.Length - 1))); int bucketNumber = (int)(seed % (uint)newBuckets.Length); do { @@ -1378,102 +1377,4 @@ namespace System.Collections private Hashtable hashtable; } } - - [FriendAccessAllowed] - internal static class HashHelpers - { - public const int HashCollisionThreshold = 100; - - // Table of prime numbers to use as hash table sizes. - // A typical resize algorithm would pick the smallest prime number in this array - // that is larger than twice the previous capacity. - // Suppose our Hashtable currently has capacity x and enough elements are added - // such that a resize needs to occur. Resizing first computes 2x then finds the - // first prime in the table greater than 2x, i.e. if primes are ordered - // p_1, p_2, ..., p_i, ..., it finds p_n such that p_n-1 < 2x < p_n. - // Doubling is important for preserving the asymptotic complexity of the - // hashtable operations such as add. Having a prime guarantees that double - // hashing does not lead to infinite loops. IE, your hash function will be - // h1(key) + i*h2(key), 0 <= i < size. h2 and the size must be relatively prime. - public static readonly int[] primes = { - 3, 7, 11, 17, 23, 29, 37, 47, 59, 71, 89, 107, 131, 163, 197, 239, 293, 353, 431, 521, 631, 761, 919, - 1103, 1327, 1597, 1931, 2333, 2801, 3371, 4049, 4861, 5839, 7013, 8419, 10103, 12143, 14591, - 17519, 21023, 25229, 30293, 36353, 43627, 52361, 62851, 75431, 90523, 108631, 130363, 156437, - 187751, 225307, 270371, 324449, 389357, 467237, 560689, 672827, 807403, 968897, 1162687, 1395263, - 1674319, 2009191, 2411033, 2893249, 3471899, 4166287, 4999559, 5999471, 7199369}; - - // Used by Hashtable and Dictionary's SeralizationInfo .ctor's to store the SeralizationInfo - // object until OnDeserialization is called. - private static ConditionalWeakTable s_SerializationInfoTable; - - internal static ConditionalWeakTable SerializationInfoTable - { - get - { - if (s_SerializationInfoTable == null) - { - ConditionalWeakTable newTable = new ConditionalWeakTable(); - Interlocked.CompareExchange(ref s_SerializationInfoTable, newTable, null); - } - - return s_SerializationInfoTable; - } - } - - public static bool IsPrime(int candidate) - { - if ((candidate & 1) != 0) - { - int limit = (int)Math.Sqrt(candidate); - for (int divisor = 3; divisor <= limit; divisor += 2) - { - if ((candidate % divisor) == 0) - return false; - } - return true; - } - return (candidate == 2); - } - - public static int GetPrime(int min) - { - if (min < 0) - throw new ArgumentException(SR.Arg_HTCapacityOverflow); - - for (int i = 0; i < primes.Length; i++) - { - int prime = primes[i]; - if (prime >= min) return prime; - } - - //outside of our predefined table. - //compute the hard way. - for (int i = (min | 1); i < Int32.MaxValue; i += 2) - { - if (IsPrime(i) && ((i - 1) % Hashtable.HashPrime != 0)) - return i; - } - return min; - } - - // Returns size of hashtable to grow to. - public static int ExpandPrime(int oldSize) - { - int newSize = 2 * oldSize; - - // Allow the hashtables to grow to maximum possible size (~2G elements) before encoutering capacity overflow. - // Note that this check works even when _items.Length overflowed thanks to the (uint) cast - if ((uint)newSize > MaxPrimeArrayLength && MaxPrimeArrayLength > oldSize) - { - Debug.Assert(MaxPrimeArrayLength == GetPrime(MaxPrimeArrayLength), "Invalid MaxPrimeArrayLength"); - return MaxPrimeArrayLength; - } - - return GetPrime(newSize); - } - - - // This is the maximum prime smaller than Array.MaxArrayLength - public const int MaxPrimeArrayLength = 0x7FEFFFFD; - } }