Created
July 27, 2021 10:21
-
-
Save mjs3339/a01ad453c171bfd6098cb885c52c3e10 to your computer and use it in GitHub Desktop.
Concurrent Dictionary Class without Blocking
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
using System; | |
using System.Collections; | |
using System.Collections.Generic; | |
using System.Diagnostics; | |
using System.Linq; | |
using System.Threading; | |
[DebuggerDisplay("Count = {" + nameof(Count) + "}")] | |
public class CcDictionary<TKey, TValue> : IEnumerable<KeyValuePair<TKey, TValue>> | |
{ | |
private readonly int _size; | |
private volatile int[] _activeThreads; | |
private DicA<TKey, TValue>[] _array; | |
private volatile int _bP; | |
internal IEqualityComparer<TKey> _comparer; | |
public volatile int NumberOfActiveThreads; | |
public CcDictionary() : this(1024, null) | |
{ | |
} | |
public CcDictionary(int size) : this(size, null) | |
{ | |
} | |
public CcDictionary(int size, IEqualityComparer<TKey> comparer) | |
{ | |
ThreadPool.GetMaxThreads(out var nW, out var nI); | |
_array = new DicA<TKey, TValue>[nW]; | |
_size = size; | |
NumberOfActiveThreads = 0; | |
_bP = 0; | |
_activeThreads = new int[Environment.ProcessorCount]; | |
_activeThreads.Fill(-1); | |
if (comparer == null) | |
_comparer = EqualityComparer<TKey>.Default; | |
else | |
_comparer = comparer; | |
} | |
public CcDictionary(IEnumerable<KeyValuePair<TKey, TValue>> collection, int concurrencyLevel = 0) | |
{ | |
ThreadPool.GetMaxThreads(out var nW, out var nI); | |
_array = new DicA<TKey, TValue>[nW]; | |
var col = collection.ToList(); | |
_size = col.Count; | |
NumberOfActiveThreads = 0; | |
_bP = 0; | |
_activeThreads = new int[Environment.ProcessorCount]; | |
_activeThreads.Fill(-1); | |
_comparer = EqualityComparer<TKey>.Default; | |
var ccl = Environment.ProcessorCount; | |
if (concurrencyLevel != 0) | |
ccl = concurrencyLevel; | |
col.AsParallel().WithDegreeOfParallelism(ccl).ForAll(i => | |
{ | |
Add(i.Key, i.Value); | |
}); | |
} | |
public int Count | |
{ | |
get | |
{ | |
var totalCount = 0; | |
for (var i = 0; i < _activeThreads.Length; ++i) | |
if (_activeThreads[i] != -1) | |
if (_array[_activeThreads[i]] != null) | |
totalCount += _array[_activeThreads[i]].Count; | |
return totalCount; | |
} | |
} | |
public TValue this[TKey key] | |
{ | |
get | |
{ | |
ProcessThread(); | |
var rv = FindKey(key); | |
return rv.idx != -1 ? _array[rv.trd][key] : default; | |
} | |
set => Add(key, value, true); | |
} | |
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() | |
{ | |
return GetEnum(); | |
} | |
IEnumerator IEnumerable.GetEnumerator() | |
{ | |
return GetEnum(); | |
} | |
public bool TryGetValue(TKey key, out TValue value) | |
{ | |
var (idx, trd) = FindKey(key); | |
if (idx == -1) | |
{ | |
value = default; | |
return false; | |
} | |
value = _array[trd][key]; | |
return true; | |
} | |
public void Clear() | |
{ | |
ThreadPool.GetMaxThreads(out var nW, out var nI); | |
_array = new DicA<TKey, TValue>[nW]; | |
NumberOfActiveThreads = 0; | |
_bP = 0; | |
_activeThreads = new int[Environment.ProcessorCount]; | |
_activeThreads.Fill(-1); | |
} | |
public void AddRange(IEnumerable<KeyValuePair<TKey, TValue>> collection, int concurrencyLevel = 0) | |
{ | |
var ccl = Environment.ProcessorCount; | |
if (concurrencyLevel != 0) | |
ccl = concurrencyLevel; | |
collection.AsParallel().WithDegreeOfParallelism(ccl).ForAll(i => | |
{ | |
Add(i.Key, i.Value); | |
}); | |
} | |
public void Add(TKey key, TValue value, bool updateValue = false) | |
{ | |
var id = ProcessThread(); | |
var idx = FindKey(key); | |
if (idx.idx == -1) | |
_array[id].Add(key, value); | |
else if (updateValue) | |
_array[idx.trd].Values[idx.idx] = value; | |
} | |
private int ProcessThread() | |
{ | |
var id = Thread.CurrentThread.ManagedThreadId; | |
if (_array[id] == null) | |
{ | |
_array[id] = new DicA<TKey, TValue>(_size, _comparer); | |
Interlocked.Increment(ref NumberOfActiveThreads); | |
if (_bP >= _activeThreads.Length) | |
{ | |
var nAtA = new int[_activeThreads.Length << 1]; | |
nAtA.Fill(-1); | |
for (var i = 0; i < _activeThreads.Length; ++i) | |
if (_activeThreads[i] != -1) | |
nAtA[i] = _activeThreads[i]; | |
_activeThreads = nAtA; | |
} | |
_activeThreads[_bP] = id; | |
Interlocked.Increment(ref _bP); | |
} | |
return id; | |
} | |
public bool TryAdd(TKey key, TValue value) | |
{ | |
Add(key, value); | |
return true; | |
} | |
public bool ContainsKey(TKey key) | |
{ | |
for (var i = 0; i < _activeThreads.Length; ++i) | |
if (_activeThreads[i] != -1) | |
if (_array[_activeThreads[i]] != null) | |
if (_array[_activeThreads[i]].ContainsKey(key)) | |
return true; | |
return false; | |
} | |
public bool ContainsValue(TValue value) | |
{ | |
var eComparer = EqualityComparer<TValue>.Default; | |
for (var i = 0; i < _activeThreads.Length; ++i) | |
if (_activeThreads[i] != -1) | |
if (_array[_activeThreads[i]] != null) | |
for (var j = 0; j < _array[_activeThreads[i]].Count; ++j) | |
if (eComparer.Equals(_array[_activeThreads[i]].Values[j], value)) | |
return true; | |
return false; | |
} | |
public (int idx, int trd) FindKey(TKey key) | |
{ | |
for (var i = 0; i < _activeThreads.Length; ++i) | |
if (_activeThreads[i] != -1) | |
if (_array[_activeThreads[i]] != null) | |
{ | |
var idx = _array[_activeThreads[i]].FindKeyIndex(key); | |
if (idx != -1) | |
return (idx, _activeThreads[i]); | |
} | |
return (-1, -1); | |
} | |
public bool Remove(TKey key) | |
{ | |
var (idx, trd) = FindKey(key); | |
if (idx == -1) | |
return false; | |
_array[_activeThreads[trd]].Remove(key); | |
return true; | |
} | |
private IEnumerator<KeyValuePair<TKey, TValue>> GetEnum() | |
{ | |
foreach (var i in ToArray()) | |
yield return new KeyValuePair<TKey, TValue>(i.Key, i.Value); | |
} | |
public KeyValuePair<TKey, TValue>[] ToArray() | |
{ | |
var totalCount = 0; | |
for (var i = 0; i < _activeThreads.Length; ++i) | |
if (_activeThreads[i] != -1) | |
if (_array[_activeThreads[i]] != null) | |
totalCount += _array[_activeThreads[i]].Count; | |
var ta = new KeyValuePair<TKey, TValue>[totalCount]; | |
var ptr = 0; | |
for (var i = 0; i < _activeThreads.Length; ++i) | |
if (_activeThreads[i] != -1) | |
if (_array[_activeThreads[i]] != null) | |
foreach (var v in _array[_activeThreads[i]]) | |
ta[ptr++] = new KeyValuePair<TKey, TValue>(v.Key, v.Value); | |
return ta; | |
} | |
public class DicA<TKey, TValue> : IEnumerable<KeyValuePair<TKey, TValue>> | |
{ | |
public MSet15<TKey> Keys; | |
public int Resizes; | |
public TValue[] Values; | |
public DicA() : this(101, EqualityComparer<TKey>.Default) | |
{ | |
} | |
public DicA(int size) : this(size, EqualityComparer<TKey>.Default) | |
{ | |
} | |
public DicA(int size, IEqualityComparer<TKey> comparer) | |
{ | |
if (comparer == null) | |
comparer = EqualityComparer<TKey>.Default; | |
Keys = new MSet15<TKey>(size); | |
Values = new TValue[size]; | |
Keys.Comparer = comparer; | |
} | |
public DicA(IEnumerable<KeyValuePair<TKey, TValue>> collection, IEqualityComparer<TKey> comparer = null) | |
{ | |
if (comparer == null) | |
comparer = EqualityComparer<TKey>.Default; | |
Keys.Comparer = comparer; | |
foreach (var kp in collection) | |
Add(kp.Key, kp.Value); | |
} | |
public int Count => Keys.Count; | |
public TValue this[TKey key] | |
{ | |
get | |
{ | |
var pos = Keys.FindEntry(key); | |
return pos == -1 ? default : Values[pos]; | |
} | |
set => Add(key, value); | |
} | |
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() | |
{ | |
for (var i = 0; i < Count; i++) | |
if (Keys.Slots[i].HashCode > 0) | |
yield return new KeyValuePair<TKey, TValue>(Keys.Slots[i].Value, Values[i]); | |
} | |
IEnumerator IEnumerable.GetEnumerator() | |
{ | |
return GetEnumerator(); | |
} | |
public bool Add(TKey key, TValue value) | |
{ | |
if (!Keys.Add(key)) | |
{ | |
if (Values.Length != Keys.Slots.Length) | |
{ | |
var nValues = new TValue[Keys.Slots.Length]; | |
Array.Copy(Values, nValues, Values.Length); | |
Values = nValues; | |
Resizes++; | |
} | |
Values[Keys.Position] = value; | |
return false; | |
} | |
Values[Keys.Position] = value; | |
return true; | |
} | |
public void Remove(TKey key) | |
{ | |
var pos = Keys.FindEntry(key); | |
if (pos != -1) | |
{ | |
Values[pos] = default; | |
Keys.Remove(key); | |
} | |
} | |
public bool ContainsKey(TKey key) | |
{ | |
return Keys.FindEntry(key) != -1; | |
} | |
public int FindKeyIndex(TKey key) | |
{ | |
return Keys.FindEntry(key); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment