SplitOrderedList.cs
Go to the documentation of this file.
1 // SplitOrderedList.cs
2 //
3 // Copyright (c) 2010 Jérémie "Garuma" Laval
4 //
5 // Permission is hereby granted, free of charge, to any person obtaining a copy
6 // of this software and associated documentation files (the "Software"), to deal
7 // in the Software without restriction, including without limitation the rights
8 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 // copies of the Software, and to permit persons to whom the Software is
10 // furnished to do so, subject to the following conditions:
11 //
12 // The above copyright notice and this permission notice shall be included in
13 // all copies or substantial portions of the Software.
14 //
15 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 // THE SOFTWARE.
22 //
23 //
24 
25 #if NET_4_0 || INSIDE_SYSTEM_WEB
26 
27 using System;
28 using System.Threading;
29 using System.Collections;
31 using System.Runtime.Serialization;
32 
33 namespace System.Collections.Concurrent
34 {
35  internal class SplitOrderedList<TKey, T>
36  {
37  class Node
38  {
39  public bool Marked;
40  public ulong Key;
41  public TKey SubKey;
42  public T Data;
43  public Node Next;
44 
45  public Node Init (ulong key, TKey subKey, T data)
46  {
47  this.Key = key;
48  this.SubKey = subKey;
49  this.Data = data;
50 
51  this.Marked = false;
52  this.Next = null;
53 
54  return this;
55  }
56 
57  // Used to create dummy node
58  public Node Init (ulong key)
59  {
60  this.Key = key;
61  this.Data = default (T);
62 
63  this.Next = null;
64  this.Marked = false;
65  this.SubKey = default (TKey);
66 
67  return this;
68  }
69 
70  // Used to create marked node
71  public Node Init (Node wrapped)
72  {
73  this.Marked = true;
74  this.Next = wrapped;
75 
76  this.Key = 0;
77  this.Data = default (T);
78  this.SubKey = default (TKey);
79 
80  return this;
81  }
82  }
83 
84  const int MaxLoad = 5;
85  const uint BucketSize = 512;
86 
87  Node head;
88  Node tail;
89 
90  Node[] buckets = new Node [BucketSize];
91  int count;
92  int size = 2;
93 
94  SimpleRwLock slim = new SimpleRwLock ();
95 
96  readonly IEqualityComparer<TKey> comparer;
97 
98  public SplitOrderedList (IEqualityComparer<TKey> comparer)
99  {
100  this.comparer = comparer;
101  head = new Node ().Init (0);
102  tail = new Node ().Init (ulong.MaxValue);
103  head.Next = tail;
104  SetBucket (0, head);
105  }
106 
107  public int Count {
108  get {
109  return count;
110  }
111  }
112 
113  public T InsertOrUpdate (uint key, TKey subKey, Func<T> addGetter, Func<T, T> updateGetter)
114  {
115  Node current;
116  bool result = InsertInternal (key, subKey, default (T), addGetter, out current);
117 
118  if (result)
119  return current.Data;
120 
121  // FIXME: this should have a CAS-like behavior
122  return current.Data = updateGetter (current.Data);
123  }
124 
125  public T InsertOrUpdate (uint key, TKey subKey, T addValue, T updateValue)
126  {
127  Node current;
128  if (InsertInternal (key, subKey, addValue, null, out current))
129  return current.Data;
130 
131  // FIXME: this should have a CAS-like behavior
132  return current.Data = updateValue;
133  }
134 
135  public bool Insert (uint key, TKey subKey, T data)
136  {
137  Node current;
138  return InsertInternal (key, subKey, data, null, out current);
139  }
140 
141  public T InsertOrGet (uint key, TKey subKey, T data, Func<T> dataCreator)
142  {
143  Node current;
144  InsertInternal (key, subKey, data, dataCreator, out current);
145  return current.Data;
146  }
147 
148  bool InsertInternal (uint key, TKey subKey, T data, Func<T> dataCreator, out Node current)
149  {
150  Node node = new Node ().Init (ComputeRegularKey (key), subKey, data);
151 
152  uint b = key % (uint)size;
153  Node bucket;
154 
155  if ((bucket = GetBucket (b)) == null)
156  bucket = InitializeBucket (b);
157 
158  if (!ListInsert (node, bucket, out current, dataCreator))
159  return false;
160 
161  int csize = size;
162  if (Interlocked.Increment (ref count) / csize > MaxLoad && (csize & 0x40000000) == 0)
163  Interlocked.CompareExchange (ref size, 2 * csize, csize);
164 
165  current = node;
166 
167  return true;
168  }
169 
170  public bool Find (uint key, TKey subKey, out T data)
171  {
172  Node node;
173  uint b = key % (uint)size;
174  data = default (T);
175  Node bucket;
176 
177  if ((bucket = GetBucket (b)) == null)
178  bucket = InitializeBucket (b);
179 
180  if (!ListFind (ComputeRegularKey (key), subKey, bucket, out node))
181  return false;
182 
183  data = node.Data;
184 
185  return !node.Marked;
186  }
187 
188  public bool CompareExchange (uint key, TKey subKey, T data, Func<T, bool> check)
189  {
190  Node node;
191  uint b = key % (uint)size;
192  Node bucket;
193 
194  if ((bucket = GetBucket (b)) == null)
195  bucket = InitializeBucket (b);
196 
197  if (!ListFind (ComputeRegularKey (key), subKey, bucket, out node))
198  return false;
199 
200  if (!check (node.Data))
201  return false;
202 
203  node.Data = data;
204 
205  return true;
206  }
207 
208  public bool Delete (uint key, TKey subKey, out T data)
209  {
210  uint b = key % (uint)size;
211  Node bucket;
212 
213  if ((bucket = GetBucket (b)) == null)
214  bucket = InitializeBucket (b);
215 
216  if (!ListDelete (bucket, ComputeRegularKey (key), subKey, out data))
217  return false;
218 
219  Interlocked.Decrement (ref count);
220  return true;
221  }
222 
223  public IEnumerator<T> GetEnumerator ()
224  {
225  Node node = head.Next;
226 
227  while (node != tail) {
228  while (node.Marked || (node.Key & 1) == 0) {
229  node = node.Next;
230  if (node == tail)
231  yield break;
232  }
233  yield return node.Data;
234  node = node.Next;
235  }
236  }
237 
238  Node InitializeBucket (uint b)
239  {
240  Node current;
241  uint parent = GetParent (b);
242  Node bucket;
243 
244  if ((bucket = GetBucket (parent)) == null)
245  bucket = InitializeBucket (parent);
246 
247  Node dummy = new Node ().Init (ComputeDummyKey (b));
248  if (!ListInsert (dummy, bucket, out current, null))
249  return current;
250 
251  return SetBucket (b, dummy);
252  }
253 
254  // Turn v's MSB off
255  static uint GetParent (uint v)
256  {
257  uint t, tt;
258 
259  // Find MSB position in v
260  var pos = (tt = v >> 16) > 0 ?
261  (t = tt >> 8) > 0 ? 24 + logTable[t] : 16 + logTable[tt] :
262  (t = v >> 8) > 0 ? 8 + logTable[t] : logTable[v];
263 
264  return (uint)(v & ~(1 << pos));
265  }
266 
267  // Reverse integer bits and make sure LSB is set
268  static ulong ComputeRegularKey (uint key)
269  {
270  return ComputeDummyKey (key) | 1;
271  }
272 
273  // Reverse integer bits
274  static ulong ComputeDummyKey (uint key)
275  {
276  return ((ulong)(((uint)reverseTable[key & 0xff] << 24) |
277  ((uint)reverseTable[(key >> 8) & 0xff] << 16) |
278  ((uint)reverseTable[(key >> 16) & 0xff] << 8) |
279  ((uint)reverseTable[(key >> 24) & 0xff]))) << 1;
280  }
281 
282  // Bucket storage is abstracted in a simple two-layer tree to avoid too much memory resize
283  Node GetBucket (uint index)
284  {
285  if (index >= buckets.Length)
286  return null;
287  return buckets[index];
288  }
289 
290  Node SetBucket (uint index, Node node)
291  {
292  try {
293  slim.EnterReadLock ();
294  CheckSegment (index, true);
295 
296  AotInterlocked.CompareExchange (ref buckets[index], node, null);
297  return buckets[index];
298  } finally {
299  slim.ExitReadLock ();
300  }
301  }
302 
303  // When we run out of space for bucket storage, we use a lock-based array resize
304  void CheckSegment (uint segment, bool readLockTaken)
305  {
306  if (segment < buckets.Length)
307  return;
308 
309  if (readLockTaken)
310  slim.ExitReadLock ();
311  try {
312  slim.EnterWriteLock ();
313  while (segment >= buckets.Length)
314  Array.Resize (ref buckets, buckets.Length * 2);
315  } finally {
316  slim.ExitWriteLock ();
317  }
318  if (readLockTaken)
319  slim.EnterReadLock ();
320  }
321 
322  Node ListSearch (ulong key, TKey subKey, ref Node left, Node h)
323  {
324  Node leftNodeNext = null, rightNode = null;
325 
326  do {
327  Node t = h;
328  Node tNext = t.Next;
329  do {
330  if (!tNext.Marked) {
331  left = t;
332  leftNodeNext = tNext;
333  }
334  t = tNext.Marked ? tNext.Next : tNext;
335  if (t == tail)
336  break;
337 
338  tNext = t.Next;
339  } while (tNext.Marked || t.Key < key || (tNext.Key == key && !comparer.Equals (subKey, t.SubKey)));
340 
341  rightNode = t;
342 
343  if (leftNodeNext == rightNode) {
344  if (rightNode != tail && rightNode.Next.Marked)
345  continue;
346  else
347  return rightNode;
348  }
349 
350  if (AotInterlocked.CompareExchange (ref left.Next, rightNode, leftNodeNext) == leftNodeNext) {
351  if (rightNode != tail && rightNode.Next.Marked)
352  continue;
353  else
354  return rightNode;
355  }
356  } while (true);
357  }
358 
359  bool ListDelete (Node startPoint, ulong key, TKey subKey, out T data)
360  {
361  Node rightNode = null, rightNodeNext = null, leftNode = null;
362  data = default (T);
363  Node markedNode = null;
364 
365  do {
366  rightNode = ListSearch (key, subKey, ref leftNode, startPoint);
367  if (rightNode == tail || rightNode.Key != key || !comparer.Equals (subKey, rightNode.SubKey))
368  return false;
369 
370  data = rightNode.Data;
371  rightNodeNext = rightNode.Next;
372 
373  if (!rightNodeNext.Marked) {
374  if (markedNode == null)
375  markedNode = new Node ();
376  markedNode.Init (rightNodeNext);
377 
378  if (AotInterlocked.CompareExchange (ref rightNode.Next, markedNode, rightNodeNext) == rightNodeNext)
379  break;
380  }
381  } while (true);
382 
383  if (AotInterlocked.CompareExchange (ref leftNode.Next, rightNodeNext, rightNode) != rightNode)
384  ListSearch (rightNode.Key, subKey, ref leftNode, startPoint);
385 
386  return true;
387  }
388 
389  bool ListInsert (Node newNode, Node startPoint, out Node current, Func<T> dataCreator)
390  {
391  ulong key = newNode.Key;
392  Node rightNode = null, leftNode = null;
393 
394  do {
395  rightNode = current = ListSearch (key, newNode.SubKey, ref leftNode, startPoint);
396  if (rightNode != tail && rightNode.Key == key && comparer.Equals (newNode.SubKey, rightNode.SubKey))
397  return false;
398 
399  newNode.Next = rightNode;
400  if (dataCreator != null)
401  newNode.Data = dataCreator ();
402  if (AotInterlocked.CompareExchange (ref leftNode.Next, newNode, rightNode) == rightNode)
403  return true;
404  } while (true);
405  }
406 
407  bool ListFind (ulong key, TKey subKey, Node startPoint, out Node data)
408  {
409  Node rightNode = null, leftNode = null;
410  data = null;
411 
412  rightNode = ListSearch (key, subKey, ref leftNode, startPoint);
413  data = rightNode;
414 
415  return rightNode != tail && rightNode.Key == key && comparer.Equals (subKey, rightNode.SubKey);
416  }
417 
418  static readonly byte[] reverseTable = {
419  0, 128, 64, 192, 32, 160, 96, 224, 16, 144, 80, 208, 48, 176, 112, 240, 8, 136, 72, 200, 40, 168, 104, 232, 24, 152, 88, 216, 56, 184, 120, 248, 4, 132, 68, 196, 36, 164, 100, 228, 20, 148, 84, 212, 52, 180, 116, 244, 12, 140, 76, 204, 44, 172, 108, 236, 28, 156, 92, 220, 60, 188, 124, 252, 2, 130, 66, 194, 34, 162, 98, 226, 18, 146, 82, 210, 50, 178, 114, 242, 10, 138, 74, 202, 42, 170, 106, 234, 26, 154, 90, 218, 58, 186, 122, 250, 6, 134, 70, 198, 38, 166, 102, 230, 22, 150, 86, 214, 54, 182, 118, 246, 14, 142, 78, 206, 46, 174, 110, 238, 30, 158, 94, 222, 62, 190, 126, 254, 1, 129, 65, 193, 33, 161, 97, 225, 17, 145, 81, 209, 49, 177, 113, 241, 9, 137, 73, 201, 41, 169, 105, 233, 25, 153, 89, 217, 57, 185, 121, 249, 5, 133, 69, 197, 37, 165, 101, 229, 21, 149, 85, 213, 53, 181, 117, 245, 13, 141, 77, 205, 45, 173, 109, 237, 29, 157, 93, 221, 61, 189, 125, 253, 3, 131, 67, 195, 35, 163, 99, 227, 19, 147, 83, 211, 51, 179, 115, 243, 11, 139, 75, 203, 43, 171, 107, 235, 27, 155, 91, 219, 59, 187, 123, 251, 7, 135, 71, 199, 39, 167, 103, 231, 23, 151, 87, 215, 55, 183, 119, 247, 15, 143, 79, 207, 47, 175, 111, 239, 31, 159, 95, 223, 63, 191, 127, 255
420  };
421 
422  static readonly byte[] logTable = {
423  0xFF, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7
424  };
425 
426  struct SimpleRwLock
427  {
428  const int RwWait = 1;
429  const int RwWrite = 2;
430  const int RwRead = 4;
431 
432  int rwlock;
433 
434  public void EnterReadLock ()
435  {
436  SpinWait sw = new SpinWait ();
437  do {
438  while ((rwlock & (RwWrite | RwWait)) > 0)
439  sw.SpinOnce ();
440 
441  if ((Interlocked.Add (ref rwlock, RwRead) & (RwWait | RwWait)) == 0)
442  return;
443 
444  Interlocked.Add (ref rwlock, -RwRead);
445  } while (true);
446  }
447 
448  public void ExitReadLock ()
449  {
450  Interlocked.Add (ref rwlock, -RwRead);
451  }
452 
453  public void EnterWriteLock ()
454  {
455  SpinWait sw = new SpinWait ();
456  do {
457  int state = rwlock;
458  if (state < RwWrite) {
459  if (Interlocked.CompareExchange (ref rwlock, RwWrite, state) == state)
460  return;
461  state = rwlock;
462  }
463  // We register our interest in taking the Write lock (if upgradeable it's already done)
464  while ((state & RwWait) == 0 && Interlocked.CompareExchange (ref rwlock, state | RwWait, state) != state)
465  state = rwlock;
466  // Before falling to sleep
467  while (rwlock > RwWait)
468  sw.SpinOnce ();
469  } while (true);
470  }
471 
472  public void ExitWriteLock ()
473  {
474  Interlocked.Add (ref rwlock, -RwWrite);
475  }
476  }
477  }
478 
479 #if INSIDE_SYSTEM_WEB && !NET_4_0
480  internal struct SpinWait
481  {
482  // The number of step until SpinOnce yield on multicore machine
483  const int step = 10;
484  const int maxTime = 200;
485  static readonly bool isSingleCpu = (Environment.ProcessorCount == 1);
486 
487  int ntime;
488 
489  public void SpinOnce ()
490  {
491  ntime += 1;
492 
493  if (isSingleCpu) {
494  // On a single-CPU system, spinning does no good
495  Thread.Sleep (0);
496  } else {
497  if (ntime % step == 0)
498  Thread.Sleep (0);
499  else
500  // Multi-CPU system might be hyper-threaded, let other thread run
501  Thread.SpinWait (Math.Min (ntime, maxTime) << 1);
502  }
503  }
504  }
505 #endif
506 }
507 
508 #endif
Interlocked reference exchanges do not work with the older Mono AOT compiler so this type fudges arou...