Skip to content

Instantly share code, notes, and snippets.

@teoadal
Last active March 18, 2023 15:58
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save teoadal/30b3177ae2608b1418075f0959803e71 to your computer and use it in GitHub Desktop.
Save teoadal/30b3177ae2608b1418075f0959803e71 to your computer and use it in GitHub Desktop.
using System;
using System.Collections.Generic;
using System.Linq;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
namespace Benchmarks.GlossaryBenchmarks;
[SimpleJob(RuntimeMoniker.Net70)]
[SimpleJob(RuntimeMoniker.Net60)]
[SimpleJob(RuntimeMoniker.NetCoreApp31)]
[SimpleJob(RuntimeMoniker.Net48)]
[MeanColumn]
public class BenchmarkTryGetValue
{
[Benchmark(Baseline = true)]
public int DictionaryTryGetValue()
{
var dictionary = _dictionary;
var sum = 0;
foreach (var key in _keys)
{
if (dictionary.TryGetValue(key, out var value))
{
sum += value;
}
}
return sum;
}
[Benchmark]
public int GlossaryTryGetValue()
{
var glossary = _glossary;
var sum = 0;
foreach (var key in _keys)
{
if (glossary.TryGetValue(key, out var value))
{
sum += value;
}
}
return sum;
}
[Benchmark]
public int GlossaryNonSealedTryGetValue()
{
var glossary = _glossaryNonSealed;
var sum = 0;
foreach (var key in _keys)
{
if (glossary.TryGetValue(key, out var value))
{
sum += value;
}
}
return sum;
}
[Benchmark]
public int GlossarySealedTryGetValue()
{
var glossary = _glossarySealed;
var sum = 0;
foreach (var key in _keys)
{
if (glossary.TryGetValue(key, out var value))
{
sum += value;
}
}
return sum;
}
#region Configuration
private Dictionary<int, int> _dictionary = null!;
private Glossary<int> _glossary;
private GlossaryNonSealedClass<int> _glossaryNonSealed = null!;
private GlossarySealedClass<int> _glossarySealed = null!;
private int[] _keys = null!;
[GlobalSetup]
public void Init()
{
const int count = 1021;
_dictionary = new Dictionary<int, int>(count);
_glossary = new Glossary<int>(count);
_glossaryNonSealed = new GlossaryNonSealedClass<int>(count);
_glossarySealed = new GlossarySealedClass<int>(count);
_keys = Enumerable.Range(0, count).ToArray();
for (var i = 0; i < count; i++)
{
var number = _keys[i];
_dictionary.Add(number, number);
_glossary.Add(number, number);
_glossaryNonSealed.Add(number, number);
_glossarySealed.Add(number, number);
}
Shuffle(_keys, new Random(1234));
}
private static void Shuffle<T>(T[] array, Random random)
{
var n = array.Length;
while (n > 1)
{
var k = random.Next(n--);
(array[n], array[k]) = (array[k], array[n]);
}
}
#endregion
}
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
namespace Benchmarks.GlossaryBenchmarks;
[DebuggerDisplay("Length = {Length}")]
[SuppressMessage("ReSharper", "InvertIf")]
internal struct Glossary<TValue>
where TValue : struct
{
private static TValue _defaultValue;
private const int StartOfFreeList = -3;
private int[] _buckets;
private Entry[] _entries;
private int _length;
private int _freeCount;
private int _freeList;
public Glossary(int capacity)
{
capacity = GlossaryHelper.GetPrime(capacity);
_buckets = new int[capacity];
_entries = new Entry[capacity];
_freeCount = 0;
_freeList = 0;
_length = 0;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ref TValue Add(int key)
{
if (_buckets.Length == 0) Resize();
var keyHash = (uint) key;
ref var bucket = ref GetBucket(keyHash);
var entries = _entries;
var i = bucket - 1;
while ((uint) i < (uint) entries.Length)
{
ref readonly var existsEntry = ref entries[i];
if (existsEntry.Key == key) GlossaryHelper.KeyAlreadyExists(key);
i = existsEntry.Next;
}
int index;
if (_freeCount > 0)
{
index = _freeList;
_freeList = StartOfFreeList - entries[index].Next;
_freeCount--;
}
else
{
index = _length;
if (index == entries.Length)
{
Resize();
bucket = ref GetBucket(keyHash);
entries = _entries;
}
_length++;
}
ref var entry = ref entries[index];
entry.Key = key;
entry.Next = bucket - 1;
bucket = index + 1;
return ref entry.Value;
}
public void Add(int key, in TValue value)
{
ref var entryValue = ref Add(key);
entryValue = value;
}
public void Clear()
{
if (_length == 0) return;
Array.Clear(_buckets, 0, _buckets.Length);
Array.Clear(_entries, 0, _entries.Length);
_freeCount = 0;
_freeList = 0;
_length = 0;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly bool ContainsKey(int key)
{
if (_length > 0)
{
var i = _buckets[(uint) key % (uint) _buckets.Length] - 1;
var entries = _entries;
while ((uint) i < (uint) entries.Length)
{
ref readonly var entry = ref entries[i];
if (entry.Key == key) return true;
i = entry.Next;
}
}
return false;
}
public readonly bool ContainsValue(in TValue value, IEqualityComparer<TValue>? comparer = null)
{
comparer ??= EqualityComparer<TValue>.Default;
foreach (var entry in _entries.AsSpan(0, _length))
{
if (entry.Next >= -1 && comparer.Equals(entry.Value, value))
{
return true;
}
}
return false;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly Enumerator GetEnumerator() => new(_entries, _length);
public readonly ref TValue GetValue(int key)
{
if (_length > 0)
{
var i = _buckets[(uint) key % (uint) _buckets.Length] - 1;
var entries = _entries;
while ((uint) i < (uint) entries.Length)
{
ref var entry = ref entries[i];
if (entry.Key == key) return ref entry.Value;
i = entry.Next;
}
}
GlossaryHelper.KeyNotFound(key);
return ref _defaultValue;
}
public readonly int Length
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _length - _freeCount;
}
public bool Remove(int key, bool clear = false)
{
if (_length == 0) return false;
ref var bucket = ref GetBucket((uint) key);
var entries = _entries;
var i = bucket - 1;
var last = -1;
while (i >= 0)
{
ref var entry = ref entries[i];
if (entry.Key == key)
{
if (clear) entry.Value = default;
if (last < 0) bucket = entry.Next + 1;
else entries[last].Next = entry.Next;
entry.Next = StartOfFreeList - _freeList;
_freeCount++;
_freeList = i;
return true;
}
last = i;
i = entry.Next;
}
return false;
}
public bool Remove(int key, out TValue value)
{
if (_length == 0)
{
value = default;
return false;
}
ref var bucket = ref GetBucket((uint) key);
var entries = _entries;
var i = bucket - 1;
var last = -1;
while (i >= 0)
{
ref var entry = ref entries[i];
if (entry.Key == key)
{
value = entry.Value; // copy
entry.Value = default;
if (last < 0) bucket = entry.Next + 1;
else entries[last].Next = entry.Next;
entry.Next = StartOfFreeList - _freeList;
_freeCount++;
_freeList = i;
return true;
}
last = i;
i = entry.Next;
}
value = default;
return false;
}
public void TrimExcess()
{
if (_length == 0)
{
_buckets = Array.Empty<int>();
_entries = Array.Empty<Entry>();
_freeCount = 0;
_freeList = -1;
return;
}
var oldEntries = _entries;
var newSize = _length;
var buckets = new int[newSize];
var entries = new Entry[newSize];
var count = 0;
for (var i = 0; i < newSize; i++)
{
if (oldEntries[i].Next < -1) continue;
ref var entry = ref entries[count];
entry = oldEntries[i];
var bucket = (uint) oldEntries[i].Key % (uint) newSize;
entry.Next = buckets[bucket] - 1;
buckets[bucket] = count + 1;
count++;
}
_buckets = buckets;
_entries = entries;
_freeCount = 0;
_freeList = -1;
}
public readonly bool TryGetValue(int key, out TValue value)
{
if (_length > 0)
{
var i = _buckets[(uint) key % (uint) _buckets.Length] - 1;
var entries = _entries;
while ((uint) i < (uint) entries.Length)
{
ref readonly var entry = ref entries[i];
if (entry.Key == key)
{
value = entry.Value;
return true;
}
i = entry.Next;
}
}
value = default;
return false;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private readonly ref int GetBucket(uint keyHash) => ref _buckets[keyHash % (uint) _buckets.Length];
private void Resize()
{
var length = _length;
var newSize = GlossaryHelper.GetPrime(length == 0 ? 4 : length << 1);
var buckets = new int[newSize];
var entries = new Entry[newSize];
Array.Copy(_entries, entries, length);
for (var i = 0; i < length; i++)
{
ref var entry = ref entries[i];
if (entry.Next < -1) continue;
ref var bucket = ref buckets[(uint) entry.Key % (uint) buckets.Length];
entry.Next = bucket - 1;
bucket = i + 1;
}
_buckets = buckets;
_entries = entries;
}
public struct Entry
{
internal int Next;
public int Key;
public TValue Value;
}
public ref struct Enumerator
{
private int _index;
private readonly int _length;
private readonly Entry[] _entries;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Enumerator(Entry[] entries, int length)
{
_index = -1;
_length = length;
_entries = entries;
}
public bool MoveNext()
{
var index = _index + 1;
var length = _length;
while ((uint) index < (uint) length)
{
ref readonly var entry = ref _entries[index];
if (entry.Next >= -1)
{
_index = index;
return true;
}
index++;
}
_index = _length;
return false;
}
public readonly ref Entry Current
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => ref _entries[_index];
}
}
}
using System;
namespace Benchmarks.GlossaryBenchmarks;
internal static class GlossaryHelper
{
public static int GetPrime(int min)
{
foreach (var prime in Primes)
{
if (prime >= min) return prime;
}
for (var candidate = min | 1; candidate < int.MaxValue; candidate += 2)
{
if (IsPrime(candidate) && (candidate - 1) % 101 != 0) return candidate;
}
return min;
}
public static void KeyAlreadyExists(int key)
{
throw new Exception($"Key {key} already exists");
}
public static void KeyNotFound(int key)
{
throw new Exception($"Key {key} isn't found");
}
private static bool IsPrime(int candidate)
{
if ((candidate & 1) == 0) return candidate == 2;
var num = (int) Math.Sqrt(candidate);
for (var index = 3; index <= num; index += 2)
{
if (candidate % index == 0)
return false;
}
return true;
}
private 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
};
}
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
namespace Benchmarks.GlossaryBenchmarks;
internal class GlossaryNonSealedClass<TValue>
where TValue : struct
{
private static TValue _defaultValue;
private const int StartOfFreeList = -3;
private int[] _buckets;
private Entry[] _entries;
private int _length;
private int _freeCount;
private int _freeList;
public GlossaryNonSealedClass(int capacity)
{
capacity = GlossaryHelper.GetPrime(capacity);
_buckets = new int[capacity];
_entries = new Entry[capacity];
_freeCount = 0;
_freeList = 0;
_length = 0;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ref TValue Add(int key)
{
if (_buckets.Length == 0) Resize();
var keyHash = (uint) key;
ref var bucket = ref GetBucket(keyHash);
var entries = _entries;
var i = bucket - 1;
while ((uint) i < (uint) entries.Length)
{
ref readonly var existsEntry = ref entries[i];
if (existsEntry.Key == key) GlossaryHelper.KeyAlreadyExists(key);
i = existsEntry.Next;
}
int index;
if (_freeCount > 0)
{
index = _freeList;
_freeList = StartOfFreeList - entries[index].Next;
_freeCount--;
}
else
{
index = _length;
if (index == entries.Length)
{
Resize();
bucket = ref GetBucket(keyHash);
entries = _entries;
}
_length++;
}
ref var entry = ref entries[index];
entry.Key = key;
entry.Next = bucket - 1;
bucket = index + 1;
return ref entry.Value;
}
public void Add(int key, in TValue value)
{
ref var entryValue = ref Add(key);
entryValue = value;
}
public void Clear()
{
if (_length == 0) return;
Array.Clear(_buckets, 0, _buckets.Length);
Array.Clear(_entries, 0, _entries.Length);
_freeCount = 0;
_freeList = 0;
_length = 0;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool ContainsKey(int key)
{
if (_length > 0)
{
var i = _buckets[(uint) key % (uint) _buckets.Length] - 1;
var entries = _entries;
while ((uint) i < (uint) entries.Length)
{
ref readonly var entry = ref entries[i];
if (entry.Key == key) return true;
i = entry.Next;
}
}
return false;
}
public bool ContainsValue(in TValue value, IEqualityComparer<TValue>? comparer = null)
{
comparer ??= EqualityComparer<TValue>.Default;
foreach (var entry in _entries.AsSpan(0, _length))
{
if (entry.Next >= -1 && comparer.Equals(entry.Value, value))
{
return true;
}
}
return false;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Enumerator GetEnumerator() => new(_entries, _length);
public ref TValue GetValue(int key)
{
if (_length > 0)
{
var i = _buckets[(uint) key % (uint) _buckets.Length] - 1;
var entries = _entries;
while ((uint) i < (uint) entries.Length)
{
ref var entry = ref entries[i];
if (entry.Key == key) return ref entry.Value;
i = entry.Next;
}
}
GlossaryHelper.KeyNotFound(key);
return ref _defaultValue;
}
public int Length
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _length - _freeCount;
}
public bool Remove(int key, bool clear = false)
{
if (_length == 0) return false;
ref var bucket = ref GetBucket((uint) key);
var entries = _entries;
var i = bucket - 1;
var last = -1;
while (i >= 0)
{
ref var entry = ref entries[i];
if (entry.Key == key)
{
if (clear) entry.Value = default;
if (last < 0) bucket = entry.Next + 1;
else entries[last].Next = entry.Next;
entry.Next = StartOfFreeList - _freeList;
_freeCount++;
_freeList = i;
return true;
}
last = i;
i = entry.Next;
}
return false;
}
public bool Remove(int key, out TValue value)
{
if (_length == 0)
{
value = default;
return false;
}
ref var bucket = ref GetBucket((uint) key);
var entries = _entries;
var i = bucket - 1;
var last = -1;
while (i >= 0)
{
ref var entry = ref entries[i];
if (entry.Key == key)
{
value = entry.Value; // copy
entry.Value = default;
if (last < 0) bucket = entry.Next + 1;
else entries[last].Next = entry.Next;
entry.Next = StartOfFreeList - _freeList;
_freeCount++;
_freeList = i;
return true;
}
last = i;
i = entry.Next;
}
value = default;
return false;
}
public void TrimExcess()
{
if (_length == 0)
{
_buckets = Array.Empty<int>();
_entries = Array.Empty<Entry>();
_freeCount = 0;
_freeList = -1;
return;
}
var oldEntries = _entries;
var newSize = _length;
var buckets = new int[newSize];
var entries = new Entry[newSize];
var count = 0;
for (var i = 0; i < newSize; i++)
{
if (oldEntries[i].Next < -1) continue;
ref var entry = ref entries[count];
entry = oldEntries[i];
var bucket = (uint) oldEntries[i].Key % (uint) newSize;
entry.Next = buckets[bucket] - 1;
buckets[bucket] = count + 1;
count++;
}
_buckets = buckets;
_entries = entries;
_freeCount = 0;
_freeList = -1;
}
public bool TryGetValue(int key, out TValue value)
{
if (_length > 0)
{
var i = _buckets[(uint) key % (uint) _buckets.Length] - 1;
var entries = _entries;
while ((uint) i < (uint) entries.Length)
{
ref readonly var entry = ref entries[i];
if (entry.Key == key)
{
value = entry.Value;
return true;
}
i = entry.Next;
}
}
value = default;
return false;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private ref int GetBucket(uint keyHash) => ref _buckets[keyHash % (uint) _buckets.Length];
private void Resize()
{
var length = _length;
var newSize = GlossaryHelper.GetPrime(length == 0 ? 4 : length << 1);
var buckets = new int[newSize];
var entries = new Entry[newSize];
Array.Copy(_entries, entries, length);
for (var i = 0; i < length; i++)
{
ref var entry = ref entries[i];
if (entry.Next < -1) continue;
ref var bucket = ref buckets[(uint) entry.Key % (uint) buckets.Length];
entry.Next = bucket - 1;
bucket = i + 1;
}
_buckets = buckets;
_entries = entries;
}
public struct Entry
{
internal int Next;
public int Key;
public TValue Value;
}
public ref struct Enumerator
{
private int _index;
private readonly int _length;
private readonly Entry[] _entries;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Enumerator(Entry[] entries, int length)
{
_index = -1;
_length = length;
_entries = entries;
}
public bool MoveNext()
{
var index = _index + 1;
var length = _length;
while ((uint) index < (uint) length)
{
ref readonly var entry = ref _entries[index];
if (entry.Next >= -1)
{
_index = index;
return true;
}
index++;
}
_index = _length;
return false;
}
public readonly ref Entry Current
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => ref _entries[_index];
}
}
}
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
namespace Benchmarks.GlossaryBenchmarks;
internal sealed class GlossarySealedClass<TValue>
where TValue : struct
{
private static TValue _defaultValue;
private const int StartOfFreeList = -3;
private int[] _buckets;
private Entry[] _entries;
private int _length;
private int _freeCount;
private int _freeList;
public GlossarySealedClass(int capacity)
{
capacity = GlossaryHelper.GetPrime(capacity);
_buckets = new int[capacity];
_entries = new Entry[capacity];
_freeCount = 0;
_freeList = 0;
_length = 0;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ref TValue Add(int key)
{
if (_buckets.Length == 0) Resize();
var keyHash = (uint) key;
ref var bucket = ref GetBucket(keyHash);
var entries = _entries;
var i = bucket - 1;
while ((uint) i < (uint) entries.Length)
{
ref readonly var existsEntry = ref entries[i];
if (existsEntry.Key == key) GlossaryHelper.KeyAlreadyExists(key);
i = existsEntry.Next;
}
int index;
if (_freeCount > 0)
{
index = _freeList;
_freeList = StartOfFreeList - entries[index].Next;
_freeCount--;
}
else
{
index = _length;
if (index == entries.Length)
{
Resize();
bucket = ref GetBucket(keyHash);
entries = _entries;
}
_length++;
}
ref var entry = ref entries[index];
entry.Key = key;
entry.Next = bucket - 1;
bucket = index + 1;
return ref entry.Value;
}
public void Add(int key, in TValue value)
{
ref var entryValue = ref Add(key);
entryValue = value;
}
public void Clear()
{
if (_length == 0) return;
Array.Clear(_buckets, 0, _buckets.Length);
Array.Clear(_entries, 0, _entries.Length);
_freeCount = 0;
_freeList = 0;
_length = 0;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool ContainsKey(int key)
{
if (_length > 0)
{
var i = _buckets[(uint) key % (uint) _buckets.Length] - 1;
var entries = _entries;
while ((uint) i < (uint) entries.Length)
{
ref readonly var entry = ref entries[i];
if (entry.Key == key) return true;
i = entry.Next;
}
}
return false;
}
public bool ContainsValue(in TValue value, IEqualityComparer<TValue>? comparer = null)
{
comparer ??= EqualityComparer<TValue>.Default;
foreach (var entry in _entries.AsSpan(0, _length))
{
if (entry.Next >= -1 && comparer.Equals(entry.Value, value))
{
return true;
}
}
return false;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Enumerator GetEnumerator() => new(_entries, _length);
public ref TValue GetValue(int key)
{
if (_length > 0)
{
var i = _buckets[(uint) key % (uint) _buckets.Length] - 1;
var entries = _entries;
while ((uint) i < (uint) entries.Length)
{
ref var entry = ref entries[i];
if (entry.Key == key) return ref entry.Value;
i = entry.Next;
}
}
GlossaryHelper.KeyNotFound(key);
return ref _defaultValue;
}
public int Length
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _length - _freeCount;
}
public bool Remove(int key, bool clear = false)
{
if (_length == 0) return false;
ref var bucket = ref GetBucket((uint) key);
var entries = _entries;
var i = bucket - 1;
var last = -1;
while (i >= 0)
{
ref var entry = ref entries[i];
if (entry.Key == key)
{
if (clear) entry.Value = default;
if (last < 0) bucket = entry.Next + 1;
else entries[last].Next = entry.Next;
entry.Next = StartOfFreeList - _freeList;
_freeCount++;
_freeList = i;
return true;
}
last = i;
i = entry.Next;
}
return false;
}
public bool Remove(int key, out TValue value)
{
if (_length == 0)
{
value = default;
return false;
}
ref var bucket = ref GetBucket((uint) key);
var entries = _entries;
var i = bucket - 1;
var last = -1;
while (i >= 0)
{
ref var entry = ref entries[i];
if (entry.Key == key)
{
value = entry.Value; // copy
entry.Value = default;
if (last < 0) bucket = entry.Next + 1;
else entries[last].Next = entry.Next;
entry.Next = StartOfFreeList - _freeList;
_freeCount++;
_freeList = i;
return true;
}
last = i;
i = entry.Next;
}
value = default;
return false;
}
public void TrimExcess()
{
if (_length == 0)
{
_buckets = Array.Empty<int>();
_entries = Array.Empty<Entry>();
_freeCount = 0;
_freeList = -1;
return;
}
var oldEntries = _entries;
var newSize = _length;
var buckets = new int[newSize];
var entries = new Entry[newSize];
var count = 0;
for (var i = 0; i < newSize; i++)
{
if (oldEntries[i].Next < -1) continue;
ref var entry = ref entries[count];
entry = oldEntries[i];
var bucket = (uint) oldEntries[i].Key % (uint) newSize;
entry.Next = buckets[bucket] - 1;
buckets[bucket] = count + 1;
count++;
}
_buckets = buckets;
_entries = entries;
_freeCount = 0;
_freeList = -1;
}
public bool TryGetValue(int key, out TValue value)
{
if (_length > 0)
{
var i = _buckets[(uint) key % (uint) _buckets.Length] - 1;
var entries = _entries;
while ((uint) i < (uint) entries.Length)
{
ref readonly var entry = ref entries[i];
if (entry.Key == key)
{
value = entry.Value;
return true;
}
i = entry.Next;
}
}
value = default;
return false;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private ref int GetBucket(uint keyHash) => ref _buckets[keyHash % (uint) _buckets.Length];
private void Resize()
{
var length = _length;
var newSize = GlossaryHelper.GetPrime(length == 0 ? 4 : length << 1);
var buckets = new int[newSize];
var entries = new Entry[newSize];
Array.Copy(_entries, entries, length);
for (var i = 0; i < length; i++)
{
ref var entry = ref entries[i];
if (entry.Next < -1) continue;
ref var bucket = ref buckets[(uint) entry.Key % (uint) buckets.Length];
entry.Next = bucket - 1;
bucket = i + 1;
}
_buckets = buckets;
_entries = entries;
}
public struct Entry
{
internal int Next;
public int Key;
public TValue Value;
}
public ref struct Enumerator
{
private int _index;
private readonly int _length;
private readonly Entry[] _entries;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Enumerator(Entry[] entries, int length)
{
_index = -1;
_length = length;
_entries = entries;
}
public bool MoveNext()
{
var index = _index + 1;
var length = _length;
while ((uint) index < (uint) length)
{
ref readonly var entry = ref _entries[index];
if (entry.Next >= -1)
{
_index = index;
return true;
}
index++;
}
_index = _length;
return false;
}
public readonly ref Entry Current
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => ref _entries[_index];
}
}
}
BenchmarkDotNet=v0.13.5, OS=Windows 11 (10.0.22621.1413/22H2/2022Update/SunValley2)
AMD Ryzen 7 5800H with Radeon Graphics, 1 CPU, 16 logical and 8 physical cores
.NET SDK=7.0.102
[Host] : .NET 6.0.13 (6.0.1322.58009), X64 RyuJIT AVX2
.NET 6.0 : .NET 6.0.13 (6.0.1322.58009), X64 RyuJIT AVX2
.NET 7.0 : .NET 7.0.2 (7.0.222.60605), X64 RyuJIT AVX2
.NET Core 3.1 : .NET Core 3.1.22 (CoreCLR 4.700.21.56803, CoreFX 4.700.21.57101), X64 RyuJIT AVX2
.NET Framework 4.8 : .NET Framework 4.8.1 (4.8.9139.0), X64 RyuJIT VectorSize=256
| Method | Job | Runtime | Mean | Error | StdDev | Ratio | RatioSD |
|----------------------------- |------------------- |------------------- |---------:|----------:|----------:|------:|--------:|
| DictionaryTryGetValue | .NET 6.0 | .NET 6.0 | 4.085 us | 0.0797 us | 0.0885 us | 1.00 | 0.00 |
| GlossaryTryGetValue | .NET 6.0 | .NET 6.0 | 1.741 us | 0.0343 us | 0.0395 us | 0.43 | 0.01 |
| GlossaryNonSealedTryGetValue | .NET 6.0 | .NET 6.0 | 2.551 us | 0.0489 us | 0.0457 us | 0.63 | 0.02 |
| GlossarySealedTryGetValue | .NET 6.0 | .NET 6.0 | 2.499 us | 0.0495 us | 0.0853 us | 0.61 | 0.02 |
| | | | | | | | |
| DictionaryTryGetValue | .NET 7.0 | .NET 7.0 | 3.768 us | 0.0753 us | 0.0868 us | 1.00 | 0.00 |
| GlossaryTryGetValue | .NET 7.0 | .NET 7.0 | 1.510 us | 0.0284 us | 0.0265 us | 0.40 | 0.01 |
| GlossaryNonSealedTryGetValue | .NET 7.0 | .NET 7.0 | 2.638 us | 0.0492 us | 0.0527 us | 0.70 | 0.03 |
| GlossarySealedTryGetValue | .NET 7.0 | .NET 7.0 | 2.617 us | 0.0502 us | 0.0516 us | 0.70 | 0.02 |
| | | | | | | | |
| DictionaryTryGetValue | .NET Core 3.1 | .NET Core 3.1 | 4.965 us | 0.0882 us | 0.0825 us | 1.00 | 0.00 |
| GlossaryTryGetValue | .NET Core 3.1 | .NET Core 3.1 | 2.534 us | 0.0503 us | 0.0538 us | 0.51 | 0.01 |
| GlossaryNonSealedTryGetValue | .NET Core 3.1 | .NET Core 3.1 | 2.526 us | 0.0504 us | 0.0638 us | 0.51 | 0.01 |
| GlossarySealedTryGetValue | .NET Core 3.1 | .NET Core 3.1 | 2.528 us | 0.0404 us | 0.0378 us | 0.51 | 0.01 |
| | | | | | | | |
| DictionaryTryGetValue | .NET Framework 4.8 | .NET Framework 4.8 | 6.965 us | 0.1388 us | 0.1805 us | 1.00 | 0.00 |
| GlossaryTryGetValue | .NET Framework 4.8 | .NET Framework 4.8 | 2.575 us | 0.0430 us | 0.0403 us | 0.37 | 0.01 |
| GlossaryNonSealedTryGetValue | .NET Framework 4.8 | .NET Framework 4.8 | 2.546 us | 0.0118 us | 0.0104 us | 0.37 | 0.01 |
| GlossarySealedTryGetValue | .NET Framework 4.8 | .NET Framework 4.8 | 2.528 us | 0.0464 us | 0.0387 us | 0.37 | 0.01 |
// * Warnings *
MultimodalDistribution
BenchmarkTryGetValue.GlossarySealedTryGetValue: .NET 6.0 -> It seems that the distribution can have several modes (mValue = 3.07)
// * Hints *
Outliers
BenchmarkTryGetValue.DictionaryTryGetValue: .NET 6.0 -> 3 outliers were detected (3.87 us..3.98 us)
BenchmarkTryGetValue.GlossaryTryGetValue: .NET 6.0 -> 4 outliers were detected (1.66 us..1.68 us)
BenchmarkTryGetValue.GlossaryNonSealedTryGetValue: .NET 6.0 -> 2 outliers were detected (2.43 us, 2.46 us)
BenchmarkTryGetValue.DictionaryTryGetValue: .NET 7.0 -> 1 outlier was detected (3.58 us)
BenchmarkTryGetValue.GlossaryTryGetValue: .NET 7.0 -> 1 outlier was detected (1.46 us)
BenchmarkTryGetValue.GlossaryNonSealedTryGetValue: .NET 7.0 -> 3 outliers were detected (2.51 us..2.55 us)
BenchmarkTryGetValue.DictionaryTryGetValue: .NET Core 3.1 -> 1 outlier was detected (4.78 us)
BenchmarkTryGetValue.GlossaryNonSealedTryGetValue: .NET Core 3.1 -> 1 outlier was detected (2.36 us)
BenchmarkTryGetValue.GlossarySealedTryGetValue: .NET Core 3.1 -> 3 outliers were detected (2.44 us..2.50 us)
BenchmarkTryGetValue.DictionaryTryGetValue: .NET Framework 4.8 -> 1 outlier was detected (6.51 us)
BenchmarkTryGetValue.GlossaryTryGetValue: .NET Framework 4.8 -> 2 outliers were detected (2.49 us, 2.50 us)
BenchmarkTryGetValue.GlossaryNonSealedTryGetValue: .NET Framework 4.8 -> 1 outlier was removed (2.59 us)
BenchmarkTryGetValue.GlossarySealedTryGetValue: .NET Framework 4.8 -> 2 outliers were removed, 3 outliers were detected (2.40 us, 2.56 us, 2.64 us)
// * Legends *
Mean : Arithmetic mean of all measurements
Error : Half of 99.9% confidence interval
StdDev : Standard deviation of all measurements
Ratio : Mean of the ratio distribution ([Current]/[Baseline])
RatioSD : Standard deviation of the ratio distribution ([Current]/[Baseline])
1 us : 1 Microsecond (0.000001 sec)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment