forked from dotnet/runtime
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Faster optimized frozen dictionary creation (6/6) (dotnet#88093)
* don't use custom ToArray for small frozen collections, up to 50% gain for creation time for collections with <= 4 items * for these types GetHashCode returns their value casted to int, so when we receive a Dictionary/HashSet where there are key we know that all hash codes are unique and we can avoid some work later 10-15% CPU time gain and 15-20% allocation reduction for FrozenDictionary and FrozenHashSet where TKey is uint, short, ushort, byte, sbyte * move Length Buckets code to a dedicated helper type to reduce code duplication and decrease code size * add tests for Frozen Dictionaries with key being uint, short, ushort, byte, sbyte, nint and nuint * fix discovered bug: IntPtr started implementing IComparable<IntPtr> in .NET 5
- Loading branch information
1 parent
84fd859
commit 76a8f4f
Showing
14 changed files
with
255 additions
and
209 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
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
102 changes: 102 additions & 0 deletions
102
...raries/System.Collections.Immutable/src/System/Collections/Frozen/String/LengthBuckets.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,102 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
using System.Buffers; | ||
using System.Collections.Generic; | ||
using System.Diagnostics; | ||
|
||
namespace System.Collections.Frozen | ||
{ | ||
internal static class LengthBuckets | ||
{ | ||
/// <summary>The maximum number of items allowed per bucket. The larger the value, the longer it can take to search a bucket, which is sequentially examined.</summary> | ||
internal const int MaxPerLength = 5; | ||
/// <summary>Allowed ratio between buckets with values and total buckets. Under this ratio, this implementation won't be used due to too much wasted space.</summary> | ||
private const double EmptyLengthsRatio = 0.2; | ||
|
||
internal static int[]? CreateLengthBucketsArrayIfAppropriate(string[] keys, IEqualityComparer<string> comparer, int minLength, int maxLength) | ||
{ | ||
Debug.Assert(comparer == EqualityComparer<string>.Default || comparer == StringComparer.Ordinal || comparer == StringComparer.OrdinalIgnoreCase); | ||
Debug.Assert(minLength >= 0 && maxLength >= minLength); | ||
|
||
// If without even looking at the keys we know that some bucket will exceed the max per-bucket | ||
// limit (pigeon hole principle), we can early-exit out without doing any further work. | ||
int spread = maxLength - minLength + 1; | ||
if (keys.Length / spread > MaxPerLength) | ||
{ | ||
return null; | ||
} | ||
|
||
int arraySize = spread * MaxPerLength; | ||
#if NET6_0_OR_GREATER | ||
if (arraySize > Array.MaxLength) | ||
#else | ||
if (arraySize > 0X7FFFFFC7) | ||
#endif | ||
{ | ||
// In the future we may lower the value, as it may be quite unlikely | ||
// to have a LOT of strings of different sizes. | ||
return null; | ||
} | ||
|
||
// Instead of creating a dictionary of lists or a multi-dimensional array | ||
// we rent a single dimension array, where every bucket has five slots. | ||
// The bucket starts at (key.Length - minLength) * 5. | ||
// Each value is an index of the key from _keys array | ||
// or just -1, which represents "null". | ||
int[] buckets = ArrayPool<int>.Shared.Rent(arraySize); | ||
buckets.AsSpan(0, arraySize).Fill(-1); | ||
|
||
int nonEmptyCount = 0; | ||
for (int i = 0; i < keys.Length; i++) | ||
{ | ||
string key = keys[i]; | ||
int startIndex = (key.Length - minLength) * MaxPerLength; | ||
int endIndex = startIndex + MaxPerLength; | ||
int index = startIndex; | ||
|
||
while (index < endIndex) | ||
{ | ||
ref int bucket = ref buckets[index]; | ||
if (bucket < 0) | ||
{ | ||
if (index == startIndex) | ||
{ | ||
nonEmptyCount++; | ||
} | ||
|
||
bucket = i; | ||
break; | ||
} | ||
|
||
index++; | ||
} | ||
|
||
if (index == endIndex) | ||
{ | ||
// If we've already hit the max per-bucket limit, bail. | ||
ArrayPool<int>.Shared.Return(buckets); | ||
return null; | ||
} | ||
} | ||
|
||
// If there would be too much empty space in the lookup array, bail. | ||
if (nonEmptyCount / (double)spread < EmptyLengthsRatio) | ||
{ | ||
ArrayPool<int>.Shared.Return(buckets); | ||
return null; | ||
} | ||
|
||
#if NET6_0_OR_GREATER | ||
// We don't need an array with every value initialized to zero if we are just about to overwrite every value anyway. | ||
int[] copy = GC.AllocateUninitializedArray<int>(arraySize); | ||
Array.Copy(buckets, copy, arraySize); | ||
#else | ||
int[] copy = buckets.AsSpan(0, arraySize).ToArray(); | ||
#endif | ||
ArrayPool<int>.Shared.Return(buckets); | ||
|
||
return copy; | ||
} | ||
} | ||
} |
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
Oops, something went wrong.