Skip to content

Commit

Permalink
Use SeqLocks for ReflectionMetadata.
Browse files Browse the repository at this point in the history
  • Loading branch information
Bobris committed Apr 6, 2024
1 parent aa38910 commit 269c86f
Show file tree
Hide file tree
Showing 3 changed files with 197 additions and 38 deletions.
131 changes: 116 additions & 15 deletions BTDB/Collections/RefDictionary.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.CompilerServices;
using BTDB.Locks;

namespace BTDB.Collections;

Expand All @@ -32,8 +33,10 @@ public class RefDictionary<TKey, TValue> : IReadOnlyCollection<KeyValuePair<TKey
static readonly Entry[] InitialEntries = new Entry[1];

int _count;

// 0-based index into _entries of head of free chain: -1 means empty
int _freeList = -1;

// 1-based index into _entries; 0 means empty
int[] _buckets;
Entry[] _entries;
Expand All @@ -44,7 +47,9 @@ public class RefDictionary<TKey, TValue> : IReadOnlyCollection<KeyValuePair<TKey
struct Entry
{
public TKey key;

public TValue value;

// 0-based index of next entry in chain: -1 means end of chain
// also encodes whether this entry _itself_ is part of the free list by changing sign and subtracting 3,
// so -2 means end of free list, -3 means index 0 but on free list, -4 means index 1 but on free list, etc.
Expand Down Expand Up @@ -78,7 +83,8 @@ public bool ContainsKey(TKey key)
var entries = _entries;
var collisionCount = 0;
for (var i = _buckets[key.GetHashCode() & (_buckets.Length - 1)] - 1;
(uint)i < (uint)entries.Length; i = entries[i].next)
(uint)i < (uint)entries.Length;
i = entries[i].next)
{
if (key.Equals(entries[i].key))
return true;
Expand All @@ -88,6 +94,7 @@ public bool ContainsKey(TKey key)
// Break out of the loop and throw, rather than looping forever.
HashHelpers.ThrowInvalidOperationException_ConcurrentOperationsNotSupported();
}

collisionCount++;
}

Expand All @@ -100,26 +107,76 @@ public bool TryGetValue(TKey key, out TValue value)
var entries = _entries;
var collisionCount = 0;
for (var i = _buckets[key.GetHashCode() & (_buckets.Length - 1)] - 1;
(uint)i < (uint)entries.Length; i = entries[i].next)
(uint)i < (uint)entries.Length;
i = entries[i].next)
{
if (key.Equals(entries[i].key))
{
value = entries[i].value;
return true;
}

if (collisionCount == entries.Length)
{
// The chain of entries forms a loop; which means a concurrent update has happened.
// Break out of the loop and throw, rather than looping forever.
HashHelpers.ThrowInvalidOperationException_ConcurrentOperationsNotSupported();
}

collisionCount++;
}

value = default;
return false;
}

public bool TryGetValueSeqLock(TKey key, out TValue value, ref SeqLock seqLock)
{
if (key == null) HashHelpers.ThrowKeyArgumentNullException();
var hash = key.GetHashCode();
var seqCounter = seqLock.StartRead();
retry:
try
{
var entries = _entries;
var collisionCount = 0;
for (var i = _buckets[hash & (_buckets.Length - 1)] - 1;
(uint)i < (uint)entries.Length;
i = entries[i].next)
{
if (key.Equals(entries[i].key))
{
value = entries[i].value;
if (seqLock.RetryRead(ref seqCounter)) goto retry;
return true;
}

if (collisionCount == entries.Length)
{
if (seqLock.RetryRead(ref seqCounter)) goto retry;
// The chain of entries forms a loop; which means a concurrent update has happened.
// Break out of the loop and throw, rather than looping forever.
HashHelpers.ThrowInvalidOperationException_ConcurrentOperationsNotSupported();
}
else if ((collisionCount & 0xF) == 4)
{
if (seqLock.RetryRead(ref seqCounter)) goto retry;
}

collisionCount++;
}

if (seqLock.RetryRead(ref seqCounter)) goto retry;
value = default;
return false;
}
catch
{
if (seqLock.RetryRead(ref seqCounter)) goto retry;
throw;
}
}

public bool Remove(TKey key)
{
if (key == null) HashHelpers.ThrowKeyArgumentNullException();
Expand All @@ -135,11 +192,13 @@ public bool Remove(TKey key)
if (candidate.key.Equals(key))
{
if (lastIndex != -1)
{ // Fixup preceding element in chain to point to next (if any)
{
// Fixup preceding element in chain to point to next (if any)
entries[lastIndex].next = candidate.next;
}
else
{ // Fixup bucket to new head (if any)
{
// Fixup bucket to new head (if any)
_buckets[bucketIndex] = candidate.next + 1;
}

Expand All @@ -151,6 +210,7 @@ public bool Remove(TKey key)
_count--;
return true;
}

lastIndex = entryIndex;
entryIndex = candidate.next;

Expand All @@ -160,6 +220,7 @@ public bool Remove(TKey key)
// Break out of the loop and throw, rather than looping forever.
HashHelpers.ThrowInvalidOperationException_ConcurrentOperationsNotSupported();
}

collisionCount++;
}

Expand All @@ -174,7 +235,8 @@ public bool Remove(TKey key)
var collisionCount = 0;
var bucketIndex = key.GetHashCode() & (_buckets.Length - 1);
for (var i = _buckets[bucketIndex] - 1;
(uint)i < (uint)entries.Length; i = entries[i].next)
(uint)i < (uint)entries.Length;
i = entries[i].next)
{
if (key.Equals(entries[i].key))
return ref entries[i].value;
Expand All @@ -184,6 +246,7 @@ public bool Remove(TKey key)
// Break out of the loop and throw, rather than looping forever.
HashHelpers.ThrowInvalidOperationException_ConcurrentOperationsNotSupported();
}

collisionCount++;
}

Expand All @@ -197,19 +260,22 @@ public bool Remove(TKey key)
var collisionCount = 0;
var bucketIndex = key.GetHashCode() & (_buckets.Length - 1);
for (var i = _buckets[bucketIndex] - 1;
(uint)i < (uint)entries.Length; i = entries[i].next)
(uint)i < (uint)entries.Length;
i = entries[i].next)
{
if (key.Equals(entries[i].key))
{
found = true;
return ref entries[i].value;
}

if (collisionCount == entries.Length)
{
// The chain of entries forms a loop; which means a concurrent update has happened.
// Break out of the loop and throw, rather than looping forever.
HashHelpers.ThrowInvalidOperationException_ConcurrentOperationsNotSupported();
}

collisionCount++;
}

Expand All @@ -225,7 +291,8 @@ public bool Remove(TKey key)
var collisionCount = 0;
var bucketIndex = key.GetHashCode() & (_buckets.Length - 1);
for (var i = _buckets[bucketIndex] - 1;
(uint)i < (uint)entries.Length; i = entries[i].next)
(uint)i < (uint)entries.Length;
i = entries[i].next)
{
if (key.Equals(entries[i].key))
return ref entries[i].value;
Expand All @@ -235,12 +302,39 @@ public bool Remove(TKey key)
// Break out of the loop and throw, rather than looping forever.
HashHelpers.ThrowInvalidOperationException_ConcurrentOperationsNotSupported();
}

collisionCount++;
}

return ref AddKey(key, bucketIndex);
}

public bool TryAdd(TKey key, in TValue value)
{
if (key == null) HashHelpers.ThrowKeyArgumentNullException();
var entries = _entries;
var collisionCount = 0;
var bucketIndex = key.GetHashCode() & (_buckets.Length - 1);
for (var i = _buckets[bucketIndex] - 1;
(uint)i < (uint)entries.Length;
i = entries[i].next)
{
if (key.Equals(entries[i].key))
return false;
if (collisionCount == entries.Length)
{
// The chain of entries forms a loop; which means a concurrent update has happened.
// Break out of the loop and throw, rather than looping forever.
HashHelpers.ThrowInvalidOperationException_ConcurrentOperationsNotSupported();
}

collisionCount++;
}

AddKey(key, bucketIndex) = value;
return true;
}

[MethodImpl(MethodImplOptions.NoInlining)]
ref TValue AddKey(TKey key, int bucketIndex)
{
Expand All @@ -259,6 +353,7 @@ public bool Remove(TKey key)
bucketIndex = key.GetHashCode() & (_buckets.Length - 1);
// entry indexes were not changed by Resize
}

entryIndex = _count;
}

Expand Down Expand Up @@ -312,13 +407,16 @@ public void CopyTo(KeyValuePair<TKey, TValue>[] array, int index)
entry.key,
entry.value);
}

i++;
}
}

public Enumerator GetEnumerator() => new Enumerator(this); // avoid boxing

IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator() =>
new Enumerator(this);

IEnumerator IEnumerable.GetEnumerator() => new Enumerator(this);

public struct Enumerator : IEnumerator<KeyValuePair<TKey, TValue>>
Expand Down Expand Up @@ -365,7 +463,9 @@ void IEnumerator.Reset()
_count = _dictionary._count;
}

public void Dispose() { }
public void Dispose()
{
}
}


Expand All @@ -386,7 +486,10 @@ void IEnumerator.Reset()
return ref _entries[(int)index].value;
}

public IndexEnumerator Index { get => new IndexEnumerator(this); }
public IndexEnumerator Index
{
get => new IndexEnumerator(this);
}

public struct IndexEnumerator : IEnumerable<uint>
{
Expand Down Expand Up @@ -444,9 +547,10 @@ void IEnumerator.Reset()
_count = _dictionary._count;
}

public void Dispose() { }
public void Dispose()
{
}
}

}
}

Expand All @@ -462,9 +566,6 @@ public RefDictionaryDebugView(RefDictionary<K, V> dictionary)
[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
public KeyValuePair<K, V>[] Items
{
get
{
return _dictionary.ToArray();
}
get { return _dictionary.ToArray(); }
}
}
48 changes: 48 additions & 0 deletions BTDB/Collections/SpanByteNoRemoveDictionary.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using BTDB.Locks;

namespace BTDB.Collections;

Expand Down Expand Up @@ -141,6 +142,53 @@ public bool TryGetValue(in ReadOnlySpan<byte> key, out TValue value)
return false;
}


public bool TryGetValueSeqLock(in ReadOnlySpan<byte> key, out TValue value, ref SeqLock seqLock)
{
var hash = CalcHash(key);
var seqCounter = seqLock.StartRead();
retry:
try
{
var entries = _entries;
var collisionCount = 0;
for (var i = _buckets[hash & (_buckets.Length - 1)] - 1;
(uint)i < (uint)entries.Length;
i = entries[i].next)
{
if (Equal(key, hash, entries[i]))
{
value = entries[i].value;
if (seqLock.RetryRead(ref seqCounter)) goto retry;
return true;
}

if (collisionCount == entries.Length)
{
if (seqLock.RetryRead(ref seqCounter)) goto retry;
// The chain of entries forms a loop; which means a concurrent update has happened.
// Break out of the loop and throw, rather than looping forever.
HashHelpers.ThrowInvalidOperationException_ConcurrentOperationsNotSupported();
}
else if ((collisionCount & 0xF) == 4)
{
if (seqLock.RetryRead(ref seqCounter)) goto retry;
}

collisionCount++;
}

if (seqLock.RetryRead(ref seqCounter)) goto retry;
value = default;
return false;
}
catch
{
if (seqLock.RetryRead(ref seqCounter)) goto retry;
throw;
}
}

// Not safe for concurrent _reads_ (at least, if either of them add)
public ref TValue GetOrAddValueRef(in ReadOnlySpan<byte> key)
{
Expand Down

0 comments on commit 269c86f

Please sign in to comment.