Skip to content

Commit d0c8e82

Browse files
Updated OrderedDictionary and improved testing.
1 parent b71252a commit d0c8e82

8 files changed

Lines changed: 273 additions & 67 deletions

File tree

Lines changed: 68 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,82 @@
11
using Open.Diagnostics;
22
using System;
33
using System.Collections.Generic;
4+
using System.Diagnostics;
45
using System.Linq;
56
using System.Threading.Tasks;
67

78
namespace Open.Collections;
89

910
public class DictionaryParallelBenchmark : CollectionParallelBenchmark<KeyValuePair<int, object>>
1011
{
11-
public DictionaryParallelBenchmark(uint size, uint repeat, Func<IDictionary<int, object>> factory)
12-
: base(size, repeat, factory, i => new KeyValuePair<int, object>(i, new object()))
13-
{
14-
}
15-
16-
protected override IEnumerable<TimedResult> TestOnceInternal()
17-
{
18-
foreach (TimedResult t in base.TestOnceInternal())
19-
{
20-
yield return t;
21-
}
22-
23-
const int mixSize = 100;
24-
var c = (IDictionary<int, object>)Param();
25-
for (int i = 0; i < TestSize; i++) c.Add(_items[i]);
26-
object[] items = Enumerable.Range(0, mixSize).Select(_ => new object()).ToArray();
12+
public DictionaryParallelBenchmark(uint size, uint repeat, Func<IDictionary<int, object>> factory)
13+
: base(size, repeat, factory, i => new KeyValuePair<int, object>(i, new object()))
14+
{
15+
}
16+
17+
protected override IEnumerable<TimedResult> TestOnceInternal()
18+
{
19+
//foreach (TimedResult t in base.TestOnceInternal())
20+
//{
21+
// yield return t;
22+
//}
23+
24+
int testSize;
25+
checked { testSize = (int)TestSize; }
26+
const int mixSize = 100;
27+
var c = (IDictionary<int, object>)Param();
28+
yield return TimedResult.Measure("Fill (.Add(keyValuePair)) (In Parallel)",
29+
() => Parallel.For(0, testSize, i => c.Add(_items[i])));
30+
31+
yield return TimedResult.Measure("Update To Expected Values",
32+
() => Parallel.For(0, testSize, i => c[i] = _items[i].Value));
33+
34+
#if DEBUG
35+
for (int i = 0; i < testSize; ++i)
36+
Debug.Assert(c[i] == _items[i].Value);
37+
#endif
2738

28-
yield return TimedResult.Measure("Random Set/Get", () =>
29-
{
30-
for (int i = 0; i < TestSize; i++)
31-
{
39+
yield return TimedResult.Measure("Enumerate", () =>
40+
{
41+
// ReSharper disable once NotAccessedVariable
42+
int x = 0;
43+
// ReSharper disable once LoopCanBeConvertedToQuery
44+
foreach (var _ in c) { x++; }
45+
Debug.Assert(x == testSize);
46+
});
47+
48+
yield return TimedResult.Measure("Enumerate (In Parallel)",
49+
() => Parallel.ForEach(c, _ => { }));
50+
51+
yield return TimedResult.Measure(".Contains(item) (In Parallel)",
52+
() => Parallel.For(0, testSize * 2, i =>
53+
{
54+
int n = i * 2;
55+
bool has = c.ContainsKey(n);
56+
Debug.Assert(has == (n < testSize));
57+
}));
58+
59+
object[] items = Enumerable.Range(0, mixSize).Select(_ => new object()).ToArray();
60+
yield return TimedResult.Measure("Random Set/Get", () =>
61+
{
62+
for (int i = 0; i < testSize; i++)
63+
{
3264
int i1 = i;
33-
Parallel.For(0, mixSize, x =>
34-
{
35-
if (x % 2 == 0)
36-
{
37-
c[i1] = items[x];
38-
}
39-
else
40-
{
65+
Parallel.For(0, mixSize, x =>
66+
{
67+
if (x % 2 == 0)
68+
{
69+
c[i1] = items[x];
70+
}
71+
else
72+
{
4173
object _ = c[i1];
42-
}
43-
});
44-
}
45-
});
46-
}
47-
48-
public static TimedResult[] Results(uint size, uint repeat, Func<IDictionary<int, object>> factory)
49-
=> new DictionaryParallelBenchmark(size, repeat, factory).Result;
74+
}
75+
});
76+
}
77+
});
78+
}
79+
80+
public static TimedResult[] Results(uint size, uint repeat, Func<IDictionary<int, object>> factory)
81+
=> new DictionaryParallelBenchmark(size, repeat, factory).Result;
5082
}

benchmarking/Program.cs

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using Open.Collections.Synchronized;
33
using Open.Diagnostics;
44
using System;
5+
using System.Collections.Concurrent;
56
using System.Collections.Generic;
67
using System.Diagnostics;
78
using System.Threading.Tasks;
@@ -19,9 +20,10 @@ static void Main()
1920
//TestEntry.Test1();
2021
//TestEntry.Test2();
2122
//QueueTests();
22-
ListTests();
23+
//ListTests();
24+
DictionaryTests();
2325

24-
Console.Beep();
26+
Console.Beep();
2527
}
2628

2729
static void OutputList(int[][] list)
@@ -181,4 +183,30 @@ static void HashSetTests()
181183
report.Test(1000, 4 * 4);
182184
report.Test(2000, 8 * 4);
183185
}
186+
187+
static void DictionaryTests()
188+
{
189+
Console.WriteLine("::: Synchronized Dictionary :::\n");
190+
var report = new BenchmarkConsoleReport<Func<IDictionary<int, object>>>(100000, DictionaryParallelBenchmark.Results);
191+
192+
report.AddBenchmark("ConcurrentDictionary",
193+
_ => () => new ConcurrentDictionary<int, object>());
194+
195+
report.AddBenchmark("LockSynchronized Dictionary",
196+
_ => () => new LockSynchronizedDictionaryWrapper<int, object>());
197+
//report.AddBenchmark("ReadWriteSynchronized Dictionary",
198+
// _ => () => new ReadWriteSynchronizedDictionaryWrapper<int, object>());
199+
200+
report.AddBenchmark("LockSynchronized OrderedDictionary",
201+
_ => () => new LockSynchronizedDictionaryWrapper<int, object>(new OrderedDictionary<int, object>()));
202+
//report.AddBenchmark("ReadWriteSynchronized OrderedDictionary",
203+
// _ => () => new ReadWriteSynchronizedDictionaryWrapper<int, object>(new OrderedDictionary<int, object>()));
204+
205+
report.Pretest(200, 200); // Run once through first to scramble/warm-up initial conditions.
206+
207+
report.Test(100, 4);
208+
report.Test(250, 4);
209+
report.Test(1000, 4 * 4);
210+
report.Test(2000, 8 * 4);
211+
}
184212
}
Lines changed: 66 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,34 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Diagnostics;
4+
using System.Diagnostics.CodeAnalysis;
5+
using System.Runtime.CompilerServices;
46

57
namespace Open.Collections;
68

79
/// <summary>
810
/// A minimal implementation of <see cref="IOrderedDictionary{TKey, TValue}"/> that is inherently not thread safe.
911
/// </summary>
10-
public class SimpleOrderedDictionary<TKey, TValue>
12+
public class OrderedDictionary<TKey, TValue>
1113
: DictionaryWrapper<TKey, TValue>, IOrderedDictionary<TKey, TValue>
1214
{
1315
private readonly List<TKey> _keys;
1416
private readonly List<TValue> _values;
17+
private readonly Dictionary<TKey, int> _indexes;
1518

16-
public SimpleOrderedDictionary(int capacity = 0)
19+
public OrderedDictionary(int capacity = 0)
1720
: base(capacity)
1821
{
1922
_keys = new(capacity);
2023
_values = new(capacity);
24+
_indexes = new(capacity);
2125
}
2226

2327
/// <inheritdoc />
2428
public override TValue this[TKey key]
2529
{
2630
get => InternalSource[key];
27-
set
28-
{
29-
int i = GetIndex(key);
30-
InternalSource[key] = value;
31-
_values[i] = value;
32-
}
31+
set => SetValue(key, value);
3332
}
3433

3534
/// <inheritdoc />
@@ -39,19 +38,39 @@ public override TValue this[TKey key]
3938
public override ICollection<TValue> Values => _values.AsReadOnly();
4039

4140
/// <inheritdoc />
42-
public new int Add(TKey key, TValue value)
41+
[ExcludeFromCodeCoverage]
42+
public override int Count
43+
=> _keys.Count;
44+
45+
private int AddToLists(TKey key, TValue value)
4346
{
44-
base.Add(key, value);
47+
int i = _keys.Count;
4548
_keys.Add(key);
4649
_values.Add(value);
47-
50+
_indexes[key] = i;
51+
Debug.Assert(_keys.Count == i + 1);
4852
Debug.Assert(_keys.Count == InternalSource.Count);
4953
Debug.Assert(_keys.Count == _values.Count);
54+
return i;
55+
}
5056

51-
return _keys.Count;
57+
[ExcludeFromCodeCoverage]
58+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
59+
protected override void AddInternal(KeyValuePair<TKey, TValue> item)
60+
{
61+
InternalSource.Add(item);
62+
AddToLists(item.Key, item.Value);
5263
}
5364

54-
void IDictionary<TKey, TValue>.Add(TKey key, TValue value) => Add(key, value);
65+
/// <inheritdoc />
66+
public new int Add(TKey key, TValue value)
67+
{
68+
base.Add(key, value);
69+
return AddToLists(key, value);
70+
}
71+
72+
void IDictionary<TKey, TValue>.Add(TKey key, TValue value)
73+
=> Add(key, value);
5574

5675
/// <inheritdoc />
5776
public TKey GetKeyAt(int index) => _keys[index];
@@ -62,28 +81,52 @@ public override TValue this[TKey key]
6281
/// <inheritdoc />
6382
public void Insert(int index, TKey key, TValue value)
6483
{
84+
if (index == _keys.Count)
85+
{
86+
Add(key, value);
87+
return;
88+
}
89+
6590
base.Add(key, value);
91+
_indexes.Clear(); // clear the index cache and let it rebuild organically.
6692
_keys.Insert(index, key);
6793
_values.Insert(index, value);
6894
Debug.Assert(_keys.Count == InternalSource.Count);
6995
Debug.Assert(_keys.Count == _values.Count);
7096
}
7197

72-
private int GetIndex(TKey key)
98+
private int GetIndex(TKey key, bool addToIndex = true)
7399
{
74-
int i = _keys.IndexOf(key);
75-
return i == -1 ? throw new Exception("Collection is out of sync possibly due to unsynchronized access by multiple threads.") : i;
100+
if (_indexes.TryGetValue(key, out int i))
101+
return i;
102+
103+
i = _keys.IndexOf(key);
104+
if (i == -1)
105+
{
106+
Debugger.Break();
107+
throw new Exception("Collection is out of sync possibly due to unsynchronized access by multiple threads.");
108+
}
109+
110+
if (addToIndex) _indexes[key] = i;
111+
return i;
112+
}
113+
114+
private void RemoveIndex(int index, TKey key)
115+
{
116+
_keys.RemoveAt(index);
117+
_values.RemoveAt(index);
118+
if (index == _keys.Count) _indexes.Remove(key); // Removed from end?
119+
else _indexes.Clear(); // clear the index cache and let it rebuild organically.
120+
Debug.Assert(_keys.Count == InternalSource.Count);
121+
Debug.Assert(_keys.Count == _values.Count);
76122
}
77123

78124
/// <inheritdoc />
79125
public override bool Remove(TKey key)
80126
{
81127
if (!base.Remove(key)) return false;
82-
int i = GetIndex(key);
83-
_keys.RemoveAt(i);
84-
_values.RemoveAt(i);
85-
Debug.Assert(_keys.Count == InternalSource.Count);
86-
Debug.Assert(_keys.Count == _values.Count);
128+
int i = GetIndex(key, false);
129+
RemoveIndex(i, key);
87130
return true;
88131
}
89132

@@ -92,16 +135,13 @@ public void RemoveAt(int index)
92135
{
93136
var key = _keys[index];
94137
InternalSource.Remove(key);
95-
_keys.RemoveAt(index);
96-
_values.RemoveAt(index);
97-
Debug.Assert(_keys.Count == InternalSource.Count);
98-
Debug.Assert(_keys.Count == _values.Count);
138+
RemoveIndex(index, key);
99139
}
100140

101141
/// <inheritdoc />
102142
public int SetValue(TKey key, TValue value)
103143
{
104-
if (!InternalSource.TryGetValue(key, out _))
144+
if (!InternalSource.ContainsKey(key))
105145
return Add(key, value);
106146

107147
int i = GetIndex(key);

source/Synchronized/LockSynchronizedDictionaryWrapper.cs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,19 @@ public class LockSynchronizedDictionaryWrapper<TKey, TValue>
1111
/// <inheritdoc />
1212
public LockSynchronizedDictionaryWrapper(IDictionary<TKey, TValue> dictionary) : base(dictionary) { }
1313

14+
public LockSynchronizedDictionaryWrapper() : this(new Dictionary<TKey, TValue>()) { }
15+
1416
/// <inheritdoc />
1517
[ExcludeFromCodeCoverage]
1618
public virtual TValue this[TKey key]
1719
{
1820
get => InternalSource[key];
19-
set => InternalSource[key] = value;
21+
set
22+
{
23+
// With a dictionary, setting can be like adding.
24+
// Collection size might change. Gotta be careful.
25+
lock (Sync) InternalSource[key] = value;
26+
}
2027
}
2128

2229
/// <inheritdoc />
@@ -34,7 +41,7 @@ public virtual ICollection<TValue> Values
3441
[MethodImpl(MethodImplOptions.AggressiveInlining)]
3542
public virtual void Add(TKey key, TValue value)
3643
{
37-
lock(Sync) InternalSource.Add(key, value);
44+
lock (Sync) InternalSource.Add(key, value);
3845
}
3946

4047
/// <inheritdoc />

source/Synchronized/ReadWriteSynchronizedDictionaryWrapper.cs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,26 @@ public class ReadWriteSynchronizedDictionaryWrapper<TKey, TValue>
1313
/// <inheritdoc />
1414
public ReadWriteSynchronizedDictionaryWrapper(IDictionary<TKey, TValue> dictionary) : base(dictionary) { }
1515

16+
public ReadWriteSynchronizedDictionaryWrapper() : this(new Dictionary<TKey, TValue>()) { }
17+
1618
/// <inheritdoc />
1719
[ExcludeFromCodeCoverage]
1820
public virtual TValue this[TKey key]
1921
{
2022
get => InternalSource[key];
21-
set => InternalSource[key] = value;
23+
set
24+
{
25+
// With a dictionary, setting can be like adding.
26+
// Collection size might change. Gotta be careful.
27+
using var upgradable = RWLock.UpgradableReadLock();
28+
if(InternalSource.ContainsKey(key))
29+
{
30+
InternalSource[key] = value;
31+
return;
32+
}
33+
using var write = RWLock.WriteLock();
34+
InternalSource[key] = value;
35+
}
2236
}
2337

2438
/// <inheritdoc />

0 commit comments

Comments
 (0)