Skip to content

Commit 269c86f

Browse files
committed
Use SeqLocks for ReflectionMetadata.
1 parent aa38910 commit 269c86f

File tree

3 files changed

+197
-38
lines changed

3 files changed

+197
-38
lines changed

BTDB/Collections/RefDictionary.cs

+116-15
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
using System.Diagnostics.CodeAnalysis;
1111
using System.Linq;
1212
using System.Runtime.CompilerServices;
13+
using BTDB.Locks;
1314

1415
namespace BTDB.Collections;
1516

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

3435
int _count;
36+
3537
// 0-based index into _entries of head of free chain: -1 means empty
3638
int _freeList = -1;
39+
3740
// 1-based index into _entries; 0 means empty
3841
int[] _buckets;
3942
Entry[] _entries;
@@ -44,7 +47,9 @@ public class RefDictionary<TKey, TValue> : IReadOnlyCollection<KeyValuePair<TKey
4447
struct Entry
4548
{
4649
public TKey key;
50+
4751
public TValue value;
52+
4853
// 0-based index of next entry in chain: -1 means end of chain
4954
// also encodes whether this entry _itself_ is part of the free list by changing sign and subtracting 3,
5055
// so -2 means end of free list, -3 means index 0 but on free list, -4 means index 1 but on free list, etc.
@@ -78,7 +83,8 @@ public bool ContainsKey(TKey key)
7883
var entries = _entries;
7984
var collisionCount = 0;
8085
for (var i = _buckets[key.GetHashCode() & (_buckets.Length - 1)] - 1;
81-
(uint)i < (uint)entries.Length; i = entries[i].next)
86+
(uint)i < (uint)entries.Length;
87+
i = entries[i].next)
8288
{
8389
if (key.Equals(entries[i].key))
8490
return true;
@@ -88,6 +94,7 @@ public bool ContainsKey(TKey key)
8894
// Break out of the loop and throw, rather than looping forever.
8995
HashHelpers.ThrowInvalidOperationException_ConcurrentOperationsNotSupported();
9096
}
97+
9198
collisionCount++;
9299
}
93100

@@ -100,26 +107,76 @@ public bool TryGetValue(TKey key, out TValue value)
100107
var entries = _entries;
101108
var collisionCount = 0;
102109
for (var i = _buckets[key.GetHashCode() & (_buckets.Length - 1)] - 1;
103-
(uint)i < (uint)entries.Length; i = entries[i].next)
110+
(uint)i < (uint)entries.Length;
111+
i = entries[i].next)
104112
{
105113
if (key.Equals(entries[i].key))
106114
{
107115
value = entries[i].value;
108116
return true;
109117
}
118+
110119
if (collisionCount == entries.Length)
111120
{
112121
// The chain of entries forms a loop; which means a concurrent update has happened.
113122
// Break out of the loop and throw, rather than looping forever.
114123
HashHelpers.ThrowInvalidOperationException_ConcurrentOperationsNotSupported();
115124
}
125+
116126
collisionCount++;
117127
}
118128

119129
value = default;
120130
return false;
121131
}
122132

133+
public bool TryGetValueSeqLock(TKey key, out TValue value, ref SeqLock seqLock)
134+
{
135+
if (key == null) HashHelpers.ThrowKeyArgumentNullException();
136+
var hash = key.GetHashCode();
137+
var seqCounter = seqLock.StartRead();
138+
retry:
139+
try
140+
{
141+
var entries = _entries;
142+
var collisionCount = 0;
143+
for (var i = _buckets[hash & (_buckets.Length - 1)] - 1;
144+
(uint)i < (uint)entries.Length;
145+
i = entries[i].next)
146+
{
147+
if (key.Equals(entries[i].key))
148+
{
149+
value = entries[i].value;
150+
if (seqLock.RetryRead(ref seqCounter)) goto retry;
151+
return true;
152+
}
153+
154+
if (collisionCount == entries.Length)
155+
{
156+
if (seqLock.RetryRead(ref seqCounter)) goto retry;
157+
// The chain of entries forms a loop; which means a concurrent update has happened.
158+
// Break out of the loop and throw, rather than looping forever.
159+
HashHelpers.ThrowInvalidOperationException_ConcurrentOperationsNotSupported();
160+
}
161+
else if ((collisionCount & 0xF) == 4)
162+
{
163+
if (seqLock.RetryRead(ref seqCounter)) goto retry;
164+
}
165+
166+
collisionCount++;
167+
}
168+
169+
if (seqLock.RetryRead(ref seqCounter)) goto retry;
170+
value = default;
171+
return false;
172+
}
173+
catch
174+
{
175+
if (seqLock.RetryRead(ref seqCounter)) goto retry;
176+
throw;
177+
}
178+
}
179+
123180
public bool Remove(TKey key)
124181
{
125182
if (key == null) HashHelpers.ThrowKeyArgumentNullException();
@@ -135,11 +192,13 @@ public bool Remove(TKey key)
135192
if (candidate.key.Equals(key))
136193
{
137194
if (lastIndex != -1)
138-
{ // Fixup preceding element in chain to point to next (if any)
195+
{
196+
// Fixup preceding element in chain to point to next (if any)
139197
entries[lastIndex].next = candidate.next;
140198
}
141199
else
142-
{ // Fixup bucket to new head (if any)
200+
{
201+
// Fixup bucket to new head (if any)
143202
_buckets[bucketIndex] = candidate.next + 1;
144203
}
145204

@@ -151,6 +210,7 @@ public bool Remove(TKey key)
151210
_count--;
152211
return true;
153212
}
213+
154214
lastIndex = entryIndex;
155215
entryIndex = candidate.next;
156216

@@ -160,6 +220,7 @@ public bool Remove(TKey key)
160220
// Break out of the loop and throw, rather than looping forever.
161221
HashHelpers.ThrowInvalidOperationException_ConcurrentOperationsNotSupported();
162222
}
223+
163224
collisionCount++;
164225
}
165226

@@ -174,7 +235,8 @@ public ref TValue GetOrFakeValueRef(TKey key)
174235
var collisionCount = 0;
175236
var bucketIndex = key.GetHashCode() & (_buckets.Length - 1);
176237
for (var i = _buckets[bucketIndex] - 1;
177-
(uint)i < (uint)entries.Length; i = entries[i].next)
238+
(uint)i < (uint)entries.Length;
239+
i = entries[i].next)
178240
{
179241
if (key.Equals(entries[i].key))
180242
return ref entries[i].value;
@@ -184,6 +246,7 @@ public ref TValue GetOrFakeValueRef(TKey key)
184246
// Break out of the loop and throw, rather than looping forever.
185247
HashHelpers.ThrowInvalidOperationException_ConcurrentOperationsNotSupported();
186248
}
249+
187250
collisionCount++;
188251
}
189252

@@ -197,19 +260,22 @@ public ref TValue GetOrFakeValueRef(TKey key, out bool found)
197260
var collisionCount = 0;
198261
var bucketIndex = key.GetHashCode() & (_buckets.Length - 1);
199262
for (var i = _buckets[bucketIndex] - 1;
200-
(uint)i < (uint)entries.Length; i = entries[i].next)
263+
(uint)i < (uint)entries.Length;
264+
i = entries[i].next)
201265
{
202266
if (key.Equals(entries[i].key))
203267
{
204268
found = true;
205269
return ref entries[i].value;
206270
}
271+
207272
if (collisionCount == entries.Length)
208273
{
209274
// The chain of entries forms a loop; which means a concurrent update has happened.
210275
// Break out of the loop and throw, rather than looping forever.
211276
HashHelpers.ThrowInvalidOperationException_ConcurrentOperationsNotSupported();
212277
}
278+
213279
collisionCount++;
214280
}
215281

@@ -225,7 +291,8 @@ public ref TValue GetOrAddValueRef(TKey key)
225291
var collisionCount = 0;
226292
var bucketIndex = key.GetHashCode() & (_buckets.Length - 1);
227293
for (var i = _buckets[bucketIndex] - 1;
228-
(uint)i < (uint)entries.Length; i = entries[i].next)
294+
(uint)i < (uint)entries.Length;
295+
i = entries[i].next)
229296
{
230297
if (key.Equals(entries[i].key))
231298
return ref entries[i].value;
@@ -235,12 +302,39 @@ public ref TValue GetOrAddValueRef(TKey key)
235302
// Break out of the loop and throw, rather than looping forever.
236303
HashHelpers.ThrowInvalidOperationException_ConcurrentOperationsNotSupported();
237304
}
305+
238306
collisionCount++;
239307
}
240308

241309
return ref AddKey(key, bucketIndex);
242310
}
243311

312+
public bool TryAdd(TKey key, in TValue value)
313+
{
314+
if (key == null) HashHelpers.ThrowKeyArgumentNullException();
315+
var entries = _entries;
316+
var collisionCount = 0;
317+
var bucketIndex = key.GetHashCode() & (_buckets.Length - 1);
318+
for (var i = _buckets[bucketIndex] - 1;
319+
(uint)i < (uint)entries.Length;
320+
i = entries[i].next)
321+
{
322+
if (key.Equals(entries[i].key))
323+
return false;
324+
if (collisionCount == entries.Length)
325+
{
326+
// The chain of entries forms a loop; which means a concurrent update has happened.
327+
// Break out of the loop and throw, rather than looping forever.
328+
HashHelpers.ThrowInvalidOperationException_ConcurrentOperationsNotSupported();
329+
}
330+
331+
collisionCount++;
332+
}
333+
334+
AddKey(key, bucketIndex) = value;
335+
return true;
336+
}
337+
244338
[MethodImpl(MethodImplOptions.NoInlining)]
245339
ref TValue AddKey(TKey key, int bucketIndex)
246340
{
@@ -259,6 +353,7 @@ ref TValue AddKey(TKey key, int bucketIndex)
259353
bucketIndex = key.GetHashCode() & (_buckets.Length - 1);
260354
// entry indexes were not changed by Resize
261355
}
356+
262357
entryIndex = _count;
263358
}
264359

@@ -312,13 +407,16 @@ public void CopyTo(KeyValuePair<TKey, TValue>[] array, int index)
312407
entry.key,
313408
entry.value);
314409
}
410+
315411
i++;
316412
}
317413
}
318414

319415
public Enumerator GetEnumerator() => new Enumerator(this); // avoid boxing
416+
320417
IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator() =>
321418
new Enumerator(this);
419+
322420
IEnumerator IEnumerable.GetEnumerator() => new Enumerator(this);
323421

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

368-
public void Dispose() { }
466+
public void Dispose()
467+
{
468+
}
369469
}
370470

371471

@@ -386,7 +486,10 @@ public ref TValue ValueRef(uint index)
386486
return ref _entries[(int)index].value;
387487
}
388488

389-
public IndexEnumerator Index { get => new IndexEnumerator(this); }
489+
public IndexEnumerator Index
490+
{
491+
get => new IndexEnumerator(this);
492+
}
390493

391494
public struct IndexEnumerator : IEnumerable<uint>
392495
{
@@ -444,9 +547,10 @@ void IEnumerator.Reset()
444547
_count = _dictionary._count;
445548
}
446549

447-
public void Dispose() { }
550+
public void Dispose()
551+
{
552+
}
448553
}
449-
450554
}
451555
}
452556

@@ -462,9 +566,6 @@ public RefDictionaryDebugView(RefDictionary<K, V> dictionary)
462566
[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
463567
public KeyValuePair<K, V>[] Items
464568
{
465-
get
466-
{
467-
return _dictionary.ToArray();
468-
}
569+
get { return _dictionary.ToArray(); }
469570
}
470571
}

BTDB/Collections/SpanByteNoRemoveDictionary.cs

+48
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
using System.Linq;
1111
using System.Runtime.CompilerServices;
1212
using System.Text;
13+
using BTDB.Locks;
1314

1415
namespace BTDB.Collections;
1516

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

145+
146+
public bool TryGetValueSeqLock(in ReadOnlySpan<byte> key, out TValue value, ref SeqLock seqLock)
147+
{
148+
var hash = CalcHash(key);
149+
var seqCounter = seqLock.StartRead();
150+
retry:
151+
try
152+
{
153+
var entries = _entries;
154+
var collisionCount = 0;
155+
for (var i = _buckets[hash & (_buckets.Length - 1)] - 1;
156+
(uint)i < (uint)entries.Length;
157+
i = entries[i].next)
158+
{
159+
if (Equal(key, hash, entries[i]))
160+
{
161+
value = entries[i].value;
162+
if (seqLock.RetryRead(ref seqCounter)) goto retry;
163+
return true;
164+
}
165+
166+
if (collisionCount == entries.Length)
167+
{
168+
if (seqLock.RetryRead(ref seqCounter)) goto retry;
169+
// The chain of entries forms a loop; which means a concurrent update has happened.
170+
// Break out of the loop and throw, rather than looping forever.
171+
HashHelpers.ThrowInvalidOperationException_ConcurrentOperationsNotSupported();
172+
}
173+
else if ((collisionCount & 0xF) == 4)
174+
{
175+
if (seqLock.RetryRead(ref seqCounter)) goto retry;
176+
}
177+
178+
collisionCount++;
179+
}
180+
181+
if (seqLock.RetryRead(ref seqCounter)) goto retry;
182+
value = default;
183+
return false;
184+
}
185+
catch
186+
{
187+
if (seqLock.RetryRead(ref seqCounter)) goto retry;
188+
throw;
189+
}
190+
}
191+
144192
// Not safe for concurrent _reads_ (at least, if either of them add)
145193
public ref TValue GetOrAddValueRef(in ReadOnlySpan<byte> key)
146194
{

0 commit comments

Comments
 (0)