Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,020 changes: 1,020 additions & 0 deletions binding/Binding.Shared/NonBlocking/ConcurrentDictionary/ConcurrentDictionary.cs

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
// Copyright (c) Vladimir Sadov. All rights reserved.
//
// This file is distributed under the MIT License. See LICENSE.md for details.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Threading;

namespace NonBlocking
{
internal abstract partial class DictionaryImpl<TKey, TKeyStore, TValue>
: DictionaryImpl<TKey, TValue>
{

internal override IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
{
return new SnapshotKV(this);
}

internal override IDictionaryEnumerator GetdIDictEnumerator()
{
return new SnapshotIDict(this);
}

private class Snapshot : IDisposable
{
private readonly DictionaryImpl<TKey, TKeyStore, TValue> _table;
private int _idx;
protected TKey _curKey, _nextK;
protected object _curValue, _nextV;

public Snapshot(DictionaryImpl<TKey, TKeyStore, TValue> dict)
{
this._table = dict;

// linearization point.
// if table is quiescent and has no copy in progress,
// we can simply iterate over its table.
while (true)
{
if (_table._newTable == null)
{
break;
}

// there is a copy in progress, finish it and try again
_table.HelpCopy(copy_all: true);
this._table = (DictionaryImpl<TKey, TKeyStore, TValue>)(this._table._topDict._table);
}

// Warm-up the iterator
MoveNext();
}

public bool MoveNext()
{
if (_nextV == NULLVALUE)
{
return false;
}

_curKey = _nextK;
_curValue = _nextV;
_nextV = NULLVALUE;

var entries = this._table._entries;
while (_idx < entries.Length)
{ // Scan array
var nextEntry = entries[_idx++];

if (nextEntry.value != null)
{
var nextKstore = nextEntry.key;
if (nextKstore == null)
{
// slot was deleted.
continue;
}

var nextK = _table.keyFromEntry(nextKstore);

object nextV = _table.TryGetValue(nextK);
if (nextV != null)
{
_nextK = nextK;

// PERF: this would be nice to have as a helper,
// but it does not get inlined
if (default(TValue) == null && nextV == NULLVALUE)
{
_nextV = default(TValue);
}
else
{
_nextV = nextV;
}


break;
}
}
}

return _curValue != NULLVALUE;
}

public void Reset()
{
_idx = 0;
}

public void Dispose()
{
}
}

private sealed class SnapshotKV : Snapshot, IEnumerator<KeyValuePair<TKey, TValue>>
{
public SnapshotKV(DictionaryImpl<TKey, TKeyStore, TValue> dict) : base(dict) { }

public KeyValuePair<TKey, TValue> Current
{
get
{
var curValue = this._curValue;
if (curValue == NULLVALUE)
{
throw new InvalidOperationException();
}

var curValueUnboxed = default(TValue) != null ?
Unsafe.As<Boxed<TValue>>(curValue).Value :
(TValue)curValue;

return new KeyValuePair<TKey, TValue>(this._curKey, curValueUnboxed);
}
}

object IEnumerator.Current => Current;
}

private sealed class SnapshotIDict : Snapshot, IDictionaryEnumerator
{
public SnapshotIDict(DictionaryImpl<TKey, TKeyStore, TValue> dict) : base(dict) { }

public DictionaryEntry Entry
{
get
{
var curValue = this._curValue;
if (curValue == NULLVALUE)
{
throw new InvalidOperationException();
}

var curValueUnboxed = default(TValue) != null ?
Unsafe.As<Boxed<TValue>>(curValue).Value :
(TValue)curValue;

return new DictionaryEntry(this._curKey, curValueUnboxed);
}
}

public object Key
{
get
{
return Entry.Key;
}
}

public object Value
{
get
{
return Entry.Value;
}
}

object IEnumerator.Current => Entry;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
// Copyright (c) Vladimir Sadov. All rights reserved.
//
// This file is distributed under the MIT License. See LICENSE.md for details.

using System.Collections.Generic;
using System.Runtime.CompilerServices;

namespace NonBlocking
{
internal abstract class DictionaryImpl
{
internal DictionaryImpl() { }

internal enum ValueMatch
{
Any, // sets new value unconditionally, used by index set and TryRemove(key)
NullOrDead, // set value if original value is null or dead, used by Add/TryAdd
NotNullOrDead, // set value if original value is alive, used by Remove
OldValue, // sets new value if old value matches
}

internal sealed class Prime
{
internal object originalValue;

public Prime(object originalValue)
{
this.originalValue = originalValue;
}
}

internal static readonly object TOMBSTONE = new object();
internal static readonly Prime TOMBPRIME = new Prime(TOMBSTONE);
internal static readonly object NULLVALUE = new object();

// represents a trivially copied empty entry
// we insert it in the old table during rehashing
// to reduce chances that more entries are added
protected const int TOMBPRIMEHASH = 1 << 31;

// we cannot distigush zero keys from uninitialized state
// so we force them to have this special hash instead
protected const int ZEROHASH = 1 << 30;

// all regular hashes have both these bits set
// to be different from either 0, TOMBPRIMEHASH or ZEROHASH
// having only these bits set in a case of Ref key means that the slot is permanently deleted.
protected const int SPECIAL_HASH_BITS = TOMBPRIMEHASH | ZEROHASH;

protected const int REPROBE_LIMIT = 4;
protected const int REPROBE_LIMIT_SHIFT = 1;
// Heuristic to decide if we have reprobed toooo many times. Running over
// the reprobe limit on a 'get' call acts as a 'miss'; on a 'put' call it
// can trigger a table resize. Several places must have exact agreement on
// what the reprobe_limit is, so we share it here.
protected static int ReprobeLimit(int lenMask)
{
// 1/2 of table with some extra
return REPROBE_LIMIT + (lenMask >> REPROBE_LIMIT_SHIFT);
}

protected static bool EntryValueNullOrDead(object entryValue)
{
return entryValue == null || entryValue == TOMBSTONE;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected static int ReduceHashToIndex(int fullHash, int lenMask)
{
var h = (uint)fullHash;

// hashcodes often exhibit clustering behavior (i.e. ...,42,43,44,45,46,47...)
// unchanged that would cause clustering in the table
// some clustering is good, since it improves locality of sequential accesses
// excessive clustering may result in long reprobes in case of collisions.

// we will use lower LBITS bits as-is and mix up other bits to break clusters.
const int LBITS = 6;

uint upper = (h >> LBITS) * 2654435769u;
upper &= ~((1u << LBITS) - 1u);
h += upper;

return (int)h & lenMask;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static object ToObjectValue<TValue>(TValue value)
{
if (default(TValue) != null)
{
return new Boxed<TValue>(value);
}

return (object)value ?? NULLVALUE;
}

internal static DictionaryImpl<TKey, TValue> CreateRef<TKey, TValue>(ConcurrentDictionary<TKey, TValue> topDict, int capacity)
where TKey : class
{
var result = new DictionaryImplRef<TKey, TKey, TValue>(capacity, topDict);
return result;
}
}
}
Loading