Skip to content

Commit

Permalink
add initial MapSpan
Browse files Browse the repository at this point in the history
  • Loading branch information
AnthonyLloyd committed Mar 21, 2023
1 parent 51044a7 commit adaec90
Show file tree
Hide file tree
Showing 3 changed files with 118 additions and 3 deletions.
7 changes: 4 additions & 3 deletions Optimized.Collections/Map.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
public sealed class Map<K, V> : IReadOnlyDictionary<K, V>, IReadOnlyList<KeyValuePair<K, V>> where K : IEquatable<K>
{
const int FIBONACCI_HASH = -1640531527;
struct Entry { internal int Bucket; internal int Next; internal K Key; internal V Value; }
internal struct Entry { internal int Bucket; internal int Next; internal K Key; internal V Value; }
static class Holder { internal readonly static Entry[] Initial = new Entry[1]; }
int _count;
Entry[] _entries;
Expand Down Expand Up @@ -165,9 +165,7 @@ public V this[K key]
{
ref var entry = ref entries[i];
if (entry.Key.Equals(key))
{
return entry.Value;
}
i = entry.Next;
}
throw new KeyNotFoundException();
Expand Down Expand Up @@ -213,6 +211,9 @@ public bool TryGetValue(K key, [MaybeNullWhen(false)] out V value)
return false;
}

/// <summary>Creates a <see cref="MapSpan{K, V}"/></summary>
public MapSpan<K, V> AsSpan() => new(_entries, _count);

/// <summary>Adds a key/value pair to the <see cref="Map{K, V}"/> by using the specified function if the key does not already exist. Returns the new value, or the existing value if the key exists.</summary>
/// <param name="key">The key of the element to add.</param>
/// <param name="valueFactory">The function used to generate a value for the key.</param>
Expand Down
76 changes: 76 additions & 0 deletions Optimized.Collections/MapSpan.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
namespace Optimized.Collections;

using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;

/// <summary>Represents a strongly typed grow only collection of keys and values.</summary>
/// <remarks>
/// - Lock free for reads during modification for reference types (and value types that set atomically).<br/>
/// - Better performance than <see cref="Dictionary{K, V}"/> in general.<br/>
/// </remarks>
/// <typeparam name="K">The type of the keys in the <see cref="Map{K, V}"/>.</typeparam>
/// <typeparam name="V">The type of the values in the <see cref="Map{K, V}"/>.</typeparam>
[DebuggerTypeProxy(typeof(MapDebugView<,>))]
[DebuggerDisplay("Count = {Count}")]
public ref struct MapSpan<K, V> where K : IEquatable<K>
{
const int FIBONACCI_HASH = -1640531527;
readonly ref Map<K, V>.Entry _entries;
readonly int _mask;

/// <summary>ss</summary>
/// <param name="entries"></param>
/// <param name="count"></param>
internal MapSpan(Map<K, V>.Entry[] entries, int count)
{
_entries = ref MemoryMarshal.GetArrayDataReference(entries);
_mask = entries.Length - 1;
Count = count;
}

/// <summary>Gets the number of key/value pairs contained in the <see cref="Map{K, V}"/>.</summary>
/// <returns>The number of key/value pairs contained in the <see cref="Map{K, V}"/>.</returns>
public int Count { get; }

/// <summary>Gets or sets the value associated with the specified key.</summary>
/// <param name="key">The key of the value to get or set.</param>
/// <returns>The value associated with the specified key. If the specified key is not found, a get operation throws a <see cref="KeyNotFoundException"/>, and a set operation creates a new element with the specified key.</returns>
public readonly V this[K key]
{
get
{
var i = Unsafe.Add(ref _entries, (key.GetHashCode() * FIBONACCI_HASH) & _mask).Bucket - 1;
while (i >= 0)
{
ref var entry = ref Unsafe.Add(ref _entries, i);
if (entry.Key.Equals(key))
return entry.Value;
i = entry.Next;
}
throw new KeyNotFoundException();
}
}

/// <summary>Gets the value associated with the specified key.</summary>
/// <param name="key">The key of the value to get.</param>
/// <param name="value">When this method returns, contains the value associated with the specified key, if the key is found; otherwise, the default value for the type of the value parameter. This parameter is passed uninitialized.</param>
/// <returns>true if the <see cref="Map{K, V}"/> contains an element with the specified key; otherwise, false.</returns>
public readonly bool TryGetValue(K key, [MaybeNullWhen(false)] out V value)
{
var i = Unsafe.Add(ref _entries, (key.GetHashCode() * FIBONACCI_HASH) & _mask).Bucket - 1;
while (i >= 0)
{
ref var entry = ref Unsafe.Add(ref _entries, i);
if (entry.Key.Equals(key))
{
value = entry.Value;
return true;
}
i = entry.Next;
}
value = default;
return false;
}
}
38 changes: 38 additions & 0 deletions Tests/MapSpanTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
namespace Tests;

using System.Linq;
using CsCheck;
using Optimized.Collections;
using Xunit;

public class MapSpanTests
{
readonly Action<string> writeLine;
public MapSpanTests(Xunit.Abstractions.ITestOutputHelper output) => writeLine = output.WriteLine;

[Fact]
public void MapSpan_TryGetValue_Performance()
{
Gen.Select(Gen.Int[0, 1000].Array[1000], Gen.Dictionary(Gen.Int[0, 1000], Gen.Int))
.Select((i, d) => (i, new Map<int, int>(d), new Dictionary<int, int>(d)))
.Faster(
(ints, map, __) =>
{
var span = map.AsSpan();
var count = 0;
foreach (var i in ints)
if (span.TryGetValue(i, out _))
count++;
return count;
},
(ints, __, dic) =>
{
var count = 0;
foreach (var i in ints)
if (dic.TryGetValue(i, out _))
count++;
return count;
}
).Output(writeLine);
}
}

0 comments on commit adaec90

Please sign in to comment.